<?php
class ApplicationController extends Rails\ActionController\Base
{
    public function __call($method, $params)
    {
        if (preg_match("/^(\w+)_only$/", $method, $m)) {
            if (current_user()->{'is_' . $m[1] . '_or_higher'}()) {
                return true;
            } else {
                $this->access_denied();
                return false;
            }
        }
        
        # For many actions, GET invokes the HTML UI, and a POST actually invokes
        # the action, so we often want to require higher access for POST (so the UI
        # can invoke the login dialog).
        elseif (preg_match("/^post_(\w+)_only$/", $method, $m)) {
            if (!$this->request()->isPost())
                return true;
            elseif (current_user()->{'is_' . $m[1] . '_or_higher'}())
                return true;
            else {
                $this->access_denied();
                return false;
            }
        }
        
        return parent::__call($method, $params);
    }
    
    /**
     * This is found in SessionHelper in Moebooru
     */
    public function page_number()
    {
        if (!isset($this->page_number))
            $this->page_number = $this->params()->page ?: 1;
        return $this->page_number;
    }
    
    # LoginSystem {
    protected function access_denied()
    {
        $previous_url = $this->params()->url || $this->request()->fullPath();
        
        $this->respondTo([
            'html' => function()use($previous_url) {
                $this->notice('Access denied');
                $this->redirectTo("user#login", array('url' => $previous_url));
            },
            'xml'  => function() {
                $this->render(array('xml' => array('success' => false, 'reason' => "access denied"), 'root' => "response", 'status' => 403));
            },
            'json' => function() {
                $this->render(array('json' => array('success' => false, 'reason' => "access denied"), 'status' => 403));
            }
        ]);
    }
    
    public function user_can_see_posts()
    {
        if (!current_user()->can_see_posts()) {
            $this->access_denied();
        }
    }

    protected function set_current_user()
    {
        $user = null;
        $AnonymousUser = array(
            'id'                       => 0,
            'level'                    => 0,
            'name'                     => "Anonymous",
            'show_samples'             => true,
            'language'                 => '',
            'secondary_languages'      => '',
            'pool_browse_mode'         => 1,
            'always_resize_images'     => true,
            'ip_addr'                  => $this->request()->remoteIp()
        );
        
        if (!current_user() && $this->session()->user_id) {
            $user = User::where(['id' => $this->session()->user_id])->first();
        } else {
            if ($this->cookies()->login && $this->cookies()->pass_hash) {
                $user = User::authenticate_hash($this->cookies()->login, $this->cookies()->pass_hash);
            } elseif (isset($this->params()->login) && isset($this->params()->password_hash)) {
                $user = User::authenticate($this->params()->login, $this->params()->password_hash);
            } elseif (isset($this->params()->user['name']) && isset($this->params()->user['password'])) {
                $user = User::authenticate($this->params()->user['name'], $this->params()->user['password']);
            }
            $user && $user->updateAttribute('last_logged_in_at', date('Y-m-d H:i:s'));
        }
        if ($user) {
            if ($user->is_blocked() && $user->ban && $user->ban->expires_at < date('Y-m-d H:i:s')) {
                $user->updateAttribute('level', CONFIG()->starting_level);
                Ban::destroyAll("user_id = ".$user->id);
            }
            $this->session()->user_id = $user->id;
        } else {
            $user = new User();
            $user->assignAttributes($AnonymousUser, ['without_protection' => true]);
        }
        
        User::set_current_user($user);
        $this->current_user = $user;
        
        # For convenient access in activerecord models
        $user->ip_addr = $this->request()->remoteIp();
        
        Moebooru\Versioning\Versioning::init_history();
        
        if (!current_user()->is_anonymous())
            current_user()->log($this->request()->remoteIp());
    }

    # iTODO:
    protected function set_country()
    {
        current_user()->country = '--';
        // current_user()->country = Rails::cache()->fetch(['type' => 'geoip', 'ip' => $this->request()->remote_ip()], ['expires_in' => '+1 month']) do
            // begin
                // GeoIP->new(Rails.root.join('db', 'GeoIP.dat').to_s).country($this->request()->remote_ip()).country_code2
            // rescue
                // '--'
            // end
        // end
    }
    
    # } RespondToHelpers {
    
    protected function respond_to_success($notice, $redirect_to_params, array $options = array())
    {
        $extra_api_params = isset($options['api']) ? $options['api'] : array();

        $this->respondTo(array(
            'html' => function() use ($notice, $redirect_to_params) {
                $this->notice($notice);
                $this->redirectTo($redirect_to_params);
            },
            'json' => function() use ($extra_api_params) {
                $this->render(array('json' => array_merge($extra_api_params, array('success' => true))));
            },
            'xml' => function() use ($extra_api_params) {
                $this->render(array('xml' => array_merge($extra_api_params, array('success' => true)), 'root' => "response"));
            }
        ));
    }

    protected function respond_to_error($obj, $redirect_to_params, $options = array())
    {
        !is_array($redirect_to_params) && $redirect_to_params = array($redirect_to_params);
        $extra_api_params = isset($options['api']) ? $options['api'] : array();
        $status = isset($options['status']) ? $options['status'] : 500;

        if ($obj instanceof Rails\ActiveRecord\Base) {
            $obj = $obj->errors()->fullMessages(", ");
            $status = 420;
        }
        
        if ($status == 420)
            $status = "420 Invalid Record";
        elseif ($status == 421)
            $status = "421 User Throttled";
        elseif ($status == 422)
            $status = "422 Locked";
        elseif ($status == 423)
            $status = "423 Already Exists";
        elseif ($status == 424)
            $status = "424 Invalid Parameters";

        $this->respondTo(array(
            'html' => function()use($obj, $redirect_to_params) {
                $this->notice("Error: " . $obj);
                $this->redirectTo($redirect_to_params);
            },
            
            'json' => function()use($obj, $extra_api_params, $status) {
                $this->render(array('json' => array_merge($extra_api_params, array('success' => false, 'reason' => $obj)), 'status' => $status));
            },
            
            'xml' => function()use($obj, $extra_api_params, $status) {
                $this->render(array('xml' => array_merge($extra_api_params, array('success' => false, 'reason' => $obj)), 'root' => "response", 'status' => $status));
            }
        ));
    }

    protected function respond_to_list($inst_var_name, array $formats = array())
    {
        $inst_var = $this->$inst_var_name;
        
        $this->respondTo(array(
            'html',
            isset($formats['atom']) ? 'atom' : null,
            'json' => function() use ($inst_var) {
                $this->render(array('json' => $inst_var->toJson()));
            },
            'xml'  => function() use ($inst_var, $inst_var_name) {
                $this->render(array('xml' => $inst_var, 'root' => $inst_var_name));
            }
        ));
    }

    protected function _render_error($record)
    {
        $this->record = $record;
        $this->render(['inline' => '<?= $this->record->errors()->fullMessages("<br />") ?>', 'layout' => "bare", 'status' => 500]);
    }
    # }
    
  // protected :build_cache_key
  // protected :get_cache_key
    
    public function get_ip_ban()
    {
        $ban = IpBans::where("ip_addr = ?", $this->request()->remoteIp())->first();
        return $ban ?: null;
    }
    
    protected function check_ip_ban()
    {
         if ($this->request()->controller() == "banned" and $this->request()->action() == "index") {
            return;
        }
        
        $ban = $this->get_ip_ban();
        if (!$ban) {
            return;
        }

        if ($ban->expires_at && $ban->expires_at < date('Y-m-d H:i:s')) {
            IpBans::destroyAll("ip_addr = '{$this->request()->remoteIp()}'");
            return;
        }

        $this->redirectTo('banned#index');
    }

    protected function save_tags_to_cookie()
    {
        if ($this->params()->tags || (is_array($this->params()->post) && isset($this->params()->post['tags']))) {
            $post_tags = isset($this->params()->post['tags']) ? (string)$this->params()->post['tags'] : '';
            $tags = TagAlias::to_aliased(explode(' ', (strtolower($this->params()->tags ?: $post_tags))));
            if ($recent_tags = trim($this->cookies()->recent_tags))
                $tags = array_merge($tags, explode(' ', $recent_tags));
            $this->cookies()->recent_tags = implode(" ", array_slice($tags, 0, 20));
        }
    }

    public function set_cache_headers()
    {
        $this->response()->headers()->add("Cache-Control", "max-age=300");
    }

    # iTODO:
    public function cache_action()
    {
        // if ($this->request()->method() == 'get' && !preg_match('/Googlebot/', $this->request()->env()) && $this->params()->format != "xml" && $this->params()->format != "json") {
            // list($key, $expiry) = $this->get_cache_key($this->controller_name(), $this->action_name(), $this->params(), 'user' => current_user());

            // if ($key && count($key) < 200) {
                // $cached = Rails::cache()->read($key);

                // if ($cached) {
                    // $this->render(['text' => $cached, 'layout' => false]);
                    // return;
                // }
            // }

            // $this->yield();

            // if ($key && strpos($this->response->headers['Status'], '200') === 0) {
                // Rails::cache()->write($key, $this->response->body, ['expires_in' => $expiry]);
            // }
        // } else {
            // $this->yield();
        // }
    }

    protected function init_cookies()
    {
        if ($this->request()->format() == "xml" || $this->request()->format() == "json") {
            return;
        }

        $forum_posts = ForumPost::where("parent_id IS NULL")->order("updated_at DESC")->limit(10)->take();
        $this->cookies()->current_forum_posts = json_encode(array_map(function($fp) {
            if (current_user()->is_anonymous()) {
                $updated = false;
            } else {
                $updated = $fp->updated_at > current_user()->last_forum_topic_read_at;
            }
            return [$fp->title, $fp->id, $updated, ceil($fp->response_count / 30.0)];
        }, $forum_posts->members()));

        $this->cookies()->country = current_user()->country;

        if (!current_user()->is_anonymous()) {
            $this->cookies()->user_id = (string)current_user()->id;
            
            $this->cookies()->user_info = current_user()->user_info_cookie();

            $this->cookies()->has_mail = (current_user()->has_mail ? "1" : "0");
            
            $this->cookies()->forum_updated = (current_user()->is_privileged_or_higher() && ForumPost::updated(current_user()) ? "1" : "0");
            
            $this->cookies()->comments_updated = (current_user()->is_privileged_or_higher() && Comment::updated(current_user()) ? "1" : "0");
            
            if (current_user()->is_janitor_or_higher()) {
                $mod_pending = Post::where("status = 'flagged' OR status = 'pending'")->count();
                $this->cookies()->mod_pending = (string)$mod_pending;
            }

            if (current_user()->is_blocked()) {
                if (current_user()->ban)
                    $this->cookies()->block_reason = "You have been blocked. Reason: ".current_user()->ban->reason.". Expires: ".substr(current_user()->ban->expires_at, 0, 10);
                else
                    $this->cookies()->block_reason = "You have been blocked.";
            } else
                $this->cookies()->block_reason = "";
            
            $this->cookies()->resize_image = (current_user()->always_resize_images ? "1" : "0");

            $this->cookies()->show_advanced_editing = (current_user()->show_advanced_editing ? "1" : "0");
            $this->cookies()->my_tags = current_user()->my_tags;
            $this->cookies()->blacklisted_tags = json_encode(current_user()->blacklisted_tags_array());
            $this->cookies()->held_post_count = (string)current_user()->held_post_count();
        } else {
            $this->cookies()->delete('user_info');
            $this->cookies()->delete('login');
            $this->cookies()->blacklisted_tags = json_encode(CONFIG()->default_blacklists);
        }
        
        if ($this->session()->notice) {
            $this->cookies()->notice = $this->session()->notice;
            $this->session()->delete('notice');
        }
    }
    
    protected function set_title($title = null)
    {
        if (!$title)
            $title = CONFIG()->app_name;
        else
            $title .= ' | ' . CONFIG()->app_name;
        $this->page_title = $title;
    }
    
    protected function notice($str)
    {
        $this->session()->notice = $str;
    }
    
    protected function set_locale()
    {
        if ($this->params()->locale and in_array($this->params()->locale, CONFIG()->available_locales)) {
            $this->cookies()->locale = [ 'value' => $this->params()->locale, 'expires' => '+1 year' ];
            $this->I18n()->setLocale($this->params()->locale);
        } elseif ($this->cookies()->locale and in_array($this->cookies()->locale, CONFIG()->available_locales)) {
            $this->I18n()->setLocale($this->cookies()->locale);
        } else
            $this->I18n()->setLocale(CONFIG()->default_locale);
    }

    protected function sanitize_params()
    {
        if ($this->params()->page) {
            if ($this->params()->page < 1)
                $this->params()->page = 1;
        } else
            $this->params()->page = 1;
    }

    protected function admin_only()
    {
        if (!current_user()->is_admin())
            $this->access_denied();
    }
    
    protected function member_only()
    {
        if (!current_user()->is_member_or_higher())
            $this->access_denied();
    }
    
    protected function post_privileged_only()
    {
        if (!current_user()->is_privileged_or_higher())
            $this->access_denied();
    }
    
    protected function post_member_only()
    {
        if (!current_user()->is_member_or_higher())
            $this->access_denied();
    }
    
    protected function no_anonymous()
    {
        if (current_user()->is_anonymous())
            $this->access_denied();
    }

    protected function sanitize_id()
    {
        $this->params()->id = (int)$this->params()->id;
    }
    
    # iTODO:
    protected function filters()
    {
        return [
            'before' => [
                'set_current_user',
                'set_country',
                'set_locale',
                'set_title',
                'sanitize_params',
                'check_ip_ban'
            ],
            'after' => [
                'init_cookies'
            ]
        ];
    }
}