<?php

namespace bizley\podium;

use bizley\podium\log\DbTarget;
use bizley\podium\maintenance\Maintenance;
use bizley\podium\models\Activity;
use bizley\podium\slugs\PodiumSluggableBehavior;
use Yii;
use yii\base\Action;
use yii\base\Application;
use yii\base\BootstrapInterface;
use yii\base\InvalidConfigException;
use yii\base\Module;
use yii\caching\Cache;
use yii\console\Application as ConsoleApplication;
use yii\db\Connection;
use yii\i18n\Formatter;
use yii\rbac\DbManager;
use yii\web\Application as WebApplication;
use yii\web\GroupUrlRule;
use yii\web\Response;
use yii\web\User;

/**
 * Podium Module
 * Yii 2 Forum Module
 *
 * @author Paweł Bizley Brzozowski <pawel@positive.codes>
 * @version 0.7 (beta)
 * @license Apache License 2.0
 *
 * https://github.com/bizley/yii2-podium
 * Please report all issues at GitHub
 * https://github.com/bizley/yii2-podium/issues
 *
 * Podium requires Yii 2
 * http://www.yiiframework.com
 * https://github.com/yiisoft/yii2
 *
 * For Podium documentation go to
 * https://github.com/bizley/yii2-podium/wiki
 *
 * @property PodiumCache $podiumCache
 * @property PodiumConfig $podiumConfig
 * @property Formatter $formatter
 * @property DbManager $rbac
 * @property User $user
 * @property Connection $db
 * @property Cache $cache
 * @property boolean $installed
 * @property string $version
 * @property PodiumComponent $podiumComponent
 * @property array $loginUrl
 * @property array $registerUrl
 * @property string $slugGenerator
 */
class Podium extends Module implements BootstrapInterface
{
    /**
     * @var int Admin account ID if $userComponent is not set to true.
     */
    public $adminId;

    /**
     * @var array List of IPs that are allowed to access installation mode of this module.
     * Each array element represents a single IP filter which can be either an IP address or an address with wildcard
     * (e.g. `192.168.0.*`) to represent a network segment.
     * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed by localhost.
     */
    public $allowedIPs = ['127.0.0.1', '::1'];

    /**
     * @var bool|string|array Module user component.
     * Since version 0.5 it can be:
     * - true for own Podium component configuration,
     * - string with inherited component ID,
     * - array with custom configuration (look at registerIdentity() to see what Podium uses).
     */
    public $userComponent = true;

    /**
     * @var bool|string|array Module RBAC component.
     * Since version 0.5 it can be:
     * - true for own Podium component configuration,
     * - string with inherited component ID,
     * - array with custom configuration (look at registerAuthorization() to see what Podium uses).
     */
    public $rbacComponent = true;

    /**
     * @var bool|string|array Module formatter component.
     * It can be:
     * - true for own Podium component configuration,
     * - string with inherited component ID,
     * - array with custom configuration (look at registerFormatter() to see what Podium uses).
     * @since 0.5
     */
    public $formatterComponent = true;

    /**
     * @var string|array Module db component.
     * It can be:
     * - string with inherited component ID,
     * - array with custom configuration.
     * @since 0.5
     */
    public $dbComponent = 'db';

    /**
     * @var bool|string|array Module cache component.
     * It can be:
     * - false for not using cache,
     * - string with inherited component ID,
     * - array with custom configuration.
     * @since 0.5
     */
    public $cacheComponent = false;

    /**
     * @var string Module inherited user password_hash field name.
     * This is used for profile updating confirmation.
     * Default value is 'password_hash'.
     */
    public $userPasswordField = 'password_hash';

    /**
     * @var string Module inherited username field name.
     * When provided value of this field is used as default Podium nickname and can not be changed from within Podium.
     * If you want to use it and your User name's model field is 'username' set userNameField to 'username'.
     * @since 0.8
     */
    public $userNameField;

    /**
     * @var string Default route for Podium.
     * @since 0.2
     */
    public $defaultRoute = 'forum';

    /**
     * @var bool Value of identity Cookie 'secure' parameter.
     * @since 0.2
     */
    public $secureIdentityCookie = false;

    /**
     * @var callable Callback that will be called to determine the type of Podium access for user.
     * The signature of the callback should be as follows:
     *      function ($user)
     * where $user is the user component.
     * The callback should return an integer value indicating access type:
     *  1 => member access,
     *  0 => guest access,
     * -1 => no access.
     * @since 0.6
     */
    public $accessChecker;

    /**
     * @var callable Callback that will be called in case Podium access has been denied for user.
     * The signature of the callback should be as follows:
     *      function ($user)
     * where $user is the user component.
     * @since 0.6
     */
    public $denyCallback;

    /**
     * @var string Qualified name of class responsible for generating Podium slugs.
     * You can implement your own generator by extending bizley\podium\slugs\PodiumSluggableBehavior -
     * see this class for details.
     * @since 0.8
     */
    public $slugGenerator;

    /**
     * @inheritdoc
     */
    protected function defaultVersion()
    {
        return '0.7';
    }

    /**
     * Initializes the module for Web application.
     * Sets Podium alias (@podium) and layout.
     * Registers user identity, authorization, translations, formatter, db, and cache.
     * @throws InvalidConfigException
     */
    public function init()
    {
        parent::init();
        $this->setAliases(['@podium' => '@vendor/bizley/podium/src']);
        if (Yii::$app instanceof WebApplication) {
            $this->podiumComponent->registerComponents();
            $this->layout = 'main';
            if (!is_string($this->slugGenerator)) {
                $this->slugGenerator = PodiumSluggableBehavior::className();
            }
        } else {
            $this->podiumComponent->registerConsoleComponents();
        }
    }

    /**
     * Bootstrap method to be called during application bootstrap stage.
     * Adding routing rules and log target.
     * @param Application $app the application currently running
     */
    public function bootstrap($app)
    {
        if ($app instanceof WebApplication) {
            $this->addUrlManagerRules($app);
            $this->setPodiumLogTarget($app);
        } elseif ($app instanceof ConsoleApplication) {
            $this->controllerNamespace = 'bizley\podium\console';
        }
    }

    /**
     * Registers user activity after every action.
     * @see Activity::add()
     * @param Action $action the action just executed
     * @param mixed $result the action return result
     * @return mixed the processed action result
     */
    public function afterAction($action, $result)
    {
        if (Yii::$app instanceof WebApplication && !in_array($action->id, ['import', 'run', 'update', 'level-up'], true)) {
            Activity::add();
        }
        return parent::afterAction($action, $result);
    }

    /**
     * Adds UrlManager rules.
     * @param Application $app the application currently running
     * @since 0.2
     */
    protected function addUrlManagerRules($app)
    {
        $app->urlManager->addRules([new GroupUrlRule([
                'prefix' => $this->id,
                'rules' => require __DIR__ . '/url-rules.php',
            ])], true);
    }

    /**
     * Sets Podium log target.
     * @param Application $app the application currently running
     * @since 0.2
     */
    protected function setPodiumLogTarget($app)
    {
        $dbTarget = new DbTarget;
        $dbTarget->logTable = '{{%podium_log}}';
        $dbTarget->categories = ['bizley\podium\*'];
        $dbTarget->logVars = [];
        $app->log->targets['podium'] = $dbTarget;
    }

    private $_cache;

    /**
     * Returns Podium cache instance.
     * @return PodiumCache
     * @since 0.5
     */
    public function getPodiumCache()
    {
        if ($this->_cache === null) {
            $this->_cache = new PodiumCache;
        }
        return $this->_cache;
    }

    private $_config;

    /**
     * Returns Podium configuration instance.
     * @return PodiumConfig
     * @since 0.5
     */
    public function getPodiumConfig()
    {
        if ($this->_config === null) {
            $this->_config = new PodiumConfig;
        }
        return $this->_config;
    }

    /**
     * Returns Podium installation flag.
     * @return bool
     */
    public function getInstalled()
    {
        return Maintenance::check();
    }

    /**
     * Appends module ID to the route.
     * @param string $route
     * @return string
     * @since 0.5
     */
    public function prepareRoute($route)
    {
        return '/' . $this->id . (substr($route, 0, 1) === '/' ? '' : '/') . $route;
    }

    /**
     * Redirects to Podium main controller's action.
     * @return Response
     */
    public function goPodium()
    {
        return Yii::$app->response->redirect([$this->prepareRoute('forum/index')]);
    }

    /**
     * Returns login URL.
     * @return array|null
     * @since 0.6
     */
    public function getLoginUrl()
    {
        if ($this->userComponent !== true) {
            return null;
        }
        return [$this->prepareRoute('account/login')];
    }

    /**
     * Returns registration URL.
     * @return array|null
     * @since 0.6
     */
    public function getRegisterUrl()
    {
        if ($this->userComponent !== true) {
            return null;
        }
        return [$this->prepareRoute('account/register')];
    }

    private $_component;

    /**
     * Returns Podium component service.
     * @return PodiumComponent
     * @since 0.6
     */
    public function getPodiumComponent()
    {
        if ($this->_component === null) {
            $this->_component = new PodiumComponent($this);
        }
        return $this->_component;
    }

    /**
     * Returns instance of RBAC component.
     * @return DbManager
     * @throws InvalidConfigException
     * @since 0.5
     */
    public function getRbac()
    {
        return $this->podiumComponent->getComponent('rbac');
    }

    /**
     * Returns instance of formatter component.
     * @return Formatter
     * @throws InvalidConfigException
     * @since 0.5
     */
    public function getFormatter()
    {
        return $this->podiumComponent->getComponent('formatter');
    }

    /**
     * Returns instance of user component.
     * @return User
     * @throws InvalidConfigException
     * @since 0.5
     */
    public function getUser()
    {
        return $this->podiumComponent->getComponent('user');
    }

    /**
     * Returns instance of db component.
     * @return Connection
     * @throws InvalidConfigException
     * @since 0.5
     */
    public function getDb()
    {
        return $this->podiumComponent->getComponent('db');
    }

    /**
     * Returns instance of cache component.
     * @return Cache
     * @throws InvalidConfigException
     * @since 0.5
     */
    public function getCache()
    {
        return $this->podiumComponent->getComponent('cache');
    }
}
