<?php

try {
    require_once dirname(__FILE__) . '/application.php';
    require_once dirname(__FILE__) . '/setup.php';
    require_once dirname(__FILE__) . '/lib/import.php';
    require_once dirname(__FILE__) . '/lib/schema.php';

    class Install_Factory extends Setup
    {
        public function __construct()
        {
            parent::__construct();
            $_SESSION = [];

            if ($_SERVER['REQUEST_METHOD'] === 'GET' && $this->isInstalled()) {
                die('system has already installed.');
            }

            switch ($this->getPhase()) {
                case '0':
                    $Step = new Install_Step_0($this->Post);
                    break;
                case '1':
                    $Step = new Install_Step_1($this->Post);
                    break;
                case '2':
                    $Step = new Install_Step_2($this->Post);
                    break;
                case '3':
                    include dirname(__FILE__) . '/lib/db_default.php';
                    $dbHost = $this->Post->get('db_host');
                    $dbName = $this->Post->get('db_name');
                    $dbCreate = $this->Post->get('db_create');
                    $dbUser = $this->Post->get('db_user');
                    $dbPass = $this->Post->get('db_pass');
                    $dbPrefix = $this->Post->get('db_prefix');
                    $dbCharset = $this->Post->get('db_charset');

                    if (empty($dbHost) && isset($dbDefaultHost)) {
                        $this->Post->set('db_host', $dbDefaultHost);
                    }
                    if (empty($dbCreate) && isset($dbDefaultCreate)) {
                        $this->Post->set('db_create', $dbDefaultCreate);
                    }
                    if (empty($dbName) && isset($dbDefaultName)) {
                        $this->Post->set('db_name', $dbDefaultName);
                    }
                    if (empty($dbUser) && isset($dbDefaultUser)) {
                        $this->Post->set('db_user', $dbDefaultUser);
                    }
                    if (empty($dbPass) && isset($dbDefaultPass)) {
                        $this->Post->set('db_pass', $dbDefaultPass);
                    }
                    if (empty($dbPrefix) && isset($dbDefaultPrefix)) {
                        $this->Post->set('db_prefix', $dbDefaultPrefix);
                    }
                    if (empty($dbCharset) && isset($dbDefaultCharset)) {
                        $this->Post->set('db_charset', $dbDefaultCharset);
                    }
                    $Step = new Install_Step_3($this->Post);
                    break;
                case '4':
                    $Step = new Install_Step_4($this->Post);
                    break;
                case '5':
                    $Step = new Install_Step_5($this->Post);
                    break;
                case '6':
                    $Step = new Install_Step_6($this->Post);
                    break;
                default:
                    die('Invalid step is selected. Please initialize system, and retry installation.');
                    break;
            }

            $flg = $this->Post->get('do');
            if ($Step->validate() && !empty($flg)) {
                $Step->business();
            }
            print SetupCommon::getTpl($Step->results());
            Cache::allFlush();
        }

        protected function getPhase()
        {
            return $this->Post->get('phase', '0');
        }
    }

    class Install_Step
    {
        public $Post;
        public $Error;
        public $Notice;
        public $Tpl;
        public $Step;

        public function __construct($post)
        {
            $this->Tpl = SetupCommon::setTpl(dirname(__FILE__) . '/tpl/install.html');
            $this->Post = $post;
        }

        protected function fileExists($name, $path)
        {
            if (!Storage::exists($path)) {
                $this->Error[$name] = true;
            }
        }

        protected function buildError()
        {
            if (!empty($this->Error)) {
                $root = [$this->Step, $this->Post->get('phase')];
                foreach ($this->Error as $key => $val) {
                    if (is_array($val)) {
                        foreach ($val as $k => $v) {
                            $loop = array_merge([$key . ':loop'], $root);
                            $this->Tpl->add($loop, [$key => $k]);
                        }
                    }
                    $error = array_merge([$key . ':error'], $root);
                    $this->Tpl->add($error);
                }
                return true;
            } else {
                return false;
            }
        }

        protected function buildNotice()
        {
            if (!empty($this->Notice)) {
                $root = [$this->Step, $this->Post->get('phase')];
                foreach ($this->Notice as $key => $val) {
                    if (is_array($val)) {
                        foreach ($val as $k => $v) {
                            $loop = array_merge([$key . ':loop'], $root);
                            $this->Tpl->add($loop, [$key => $k]);
                        }
                    }
                    $notice = array_merge([$key . ':notice'], $root);
                    $this->Tpl->add($notice);
                }
                return true;
            } else {
                return false;
            }
        }

        public function validate(): bool
        {
            return true;
        }

        public function business(): void
        {
        }

        public function results()
        {
            $flg = $this->Post->get('do');

            if (!empty($flg)) {
                if (!empty($this->Error)) {
                    $this->Step = 'step#error';
                    $this->buildError();
                } else {
                    $this->Step = 'step#result';
                    $this->buildNotice();
                }
            } else {
                $this->Step = 'step#apply';
            }
            $step = $this->Step;
            $phase = $this->Post->get('phase', '0');
            $this->Tpl->add([$step, $phase], Tpl::buildField($this->Post, $this->Tpl, [$step, $phase]));
            $this->Tpl->add($phase);

            return $this->Tpl;
        }

        protected function writeConfig($key, $val)
        {
            $val = str_replace('$', '\$', $val);
            $val = str_replace("'", "\'", $val);
            $pattern = '/define\(\'' . $key . '\'\s*,\s*.*?\s*\);/';
            $replace = "define('$key', '$val');";

            // config.server.php はStorage::getでは取得できないため、file_get_contentsを使用
            $config = file_get_contents(PATH_CONFIG);
            if ($config === false) {
                $this->Error['config_read'] = true;
                return;
            }
            $config = preg_replace($pattern, $replace, $config);

            $file = fopen(PATH_CONFIG, 'w+');
            if ($file === false) {
                $this->Error['config_open'] = true;
            } else {
                if ($config) {
                    $write = fwrite($file, $config);
                    if (!$write) {
                        $this->Error['config_write'] = true;
                    }
                } else {
                    $this->Error['config_write'] = true;
                }
                fclose($file);
            }
        }

        protected function checkInput()
        {
            $fds = $this->Post->listFields();
            foreach ($fds as $fd) {
                $val = $this->Post->get($fd, null);
                if (!empty($val) || $fd === 'db_create' || $fd === 'db_pass') {
                    continue;
                }
                $this->Error[$fd . '_input'] = true;
            }
        }
    }

    class Install_Step_0 extends Install_Step
    {
        public function results()
        {
            $this->Step = 'step#apply';
            $txt = Storage::get(dirname(__FILE__) . '/tpl/terms_of_service.txt');
            $vars = [
                'terms_of_service' => $txt,
            ];
            $this->Tpl->add(['step#apply', '0'], $vars);
            $this->Tpl->add('0', $vars);

            return $this->Tpl;
        }
    }

    class Install_Step_1 extends Install_Step
    {
        public function validate(): bool
        {
            // GD
            if (!function_exists('gd_info')) {
                $this->Error['gd_exists'] = true;
            }
            // SPL
            if (!function_exists('spl_autoload_register')) {
                $this->Error['spl_exists'] = true;
            }
            // hash_hmac
            if (!function_exists('hash_hmac')) {
                $this->Notice['hash_exists'] = true;
            }
            // imagerotate
            if (!function_exists('imagerotate')) {
                $this->Notice['imagerotate_exists'] = true;
            }
            // simplexml
            if (!function_exists('simplexml_load_string')) {
                $this->Notice['simplexml_exists'] = true;
            }

            // exists
            $this->fileExists('config_exists', PATH_CONFIG);
            $this->fileExists('license_exists', PATH_LICENSE);
            $this->fileExists('archives_exists', PATH_ARCHIVES);
            $this->fileExists('cache_exists', PATH_CACHE);
            $this->fileExists('themes_exists', PATH_THEMES);
            $this->fileExists('htaccess_exists', PATH_HTACCESS);

            // permission
            if (!SetupCommon::checkPermission(PATH_ARCHIVES)) {
                $this->Error['archives_permission'] = true;
            }
            if (!SetupCommon::checkPermission(PATH_MEDIA)) {
                $this->Error['media_permission'] = true;
            }
            if (!SetupCommon::checkPermission(PATH_STORAGE)) {
                $this->Error['storage_permission'] = true;
            }
            if (!SetupCommon::checkPermission(PATH_CACHE)) {
                $this->Error['cache_permission'] = true;
            }
            if (!SetupCommon::checkPermission(PATH_CONFIG, '666')) {
                $this->Error['config_permission'] = true;
            }

            return empty($this->Error) ? true : false;
        }
    }

    class Install_Step_2 extends Install_Step
    {
        public function validate(): bool
        {
            if (!$this->Post->get('domain')) {
                $this->Error['domain_input'] = true;
            } elseif (!preg_match(REGEX_VALID_DOMAIN, $this->Post->get('domain'))) {
                $this->Error['domain_invalid'] = true;
            }

            return empty($this->Error) ? true : false;
        }

        public function business(): void
        {
            $domain = $this->Post->get('domain');
            $this->writeConfig('DOMAIN', $domain);
        }
    }

    class Install_Step_3 extends Install_Step
    {
        public function validate(): bool
        {
            $this->checkInput();
            $dsn = [
                'host' => $this->Post->get('db_host'),
                'user' => $this->Post->get('db_user'),
                'pass' => $this->Post->get('db_pass'),
                'name' => $this->Post->get('db_name'),
            ];
            $DB = new Acms\Services\Database\Engine\PdoEngine();

            if (empty($this->Error)) {
                if (!$DB->checkConnection(dsn($dsn))) {
                    $this->Error['server_connect'] = true;
                    return false;
                }
                if (!$this->Post->get('db_create') && !$DB->checkConnectDatabase(dsn($dsn))) {
                    $this->Error['database_connect'] = true;
                    return false;
                }

                $DB->setThrowException(true);
                try {
                    $dsn['name'] = '';
                    $DB->connect(dsn($dsn));
                    $charset = $this->Post->get('db_charset');
                    $collation = $this->Post->get('db_collation');

                    if (strtolower($charset) === 'utf8mb4') {
                        $charsetName = 'utf8mb4';
                    } else {
                        $charsetName = 'utf8'; // デフォルト値
                    }

                    if (!!$this->Post->get('db_create')) {
                        $sql = [
                            'sql' => 'CREATE DATABASE `' . $this->Post->get('db_name') . '` DEFAULT CHARACTER SET ' . $charsetName . ' COLLATE ' . $collation,
                            'params' => [],
                        ];
                        $DB->query($sql, 'exec');
                    } else {
                        $sql = [
                            'sql' => 'ALTER DATABASE `' . $this->Post->get('db_name') . '` DEFAULT CHARACTER SET ' . $charsetName . ' COLLATE ' . $collation,
                            'params' => [],
                        ];
                        $DB->query($sql, 'exec');
                    }
                } catch (\Exception $e) {
                    $this->Error['create_database'] = true;
                    return false;
                }
                $DB->setThrowException(false);
                return true;
            }
            return false;
        }

        public function business(): void
        {
            $this->writeConfig('DB_HOST', $this->Post->get('db_host'));
            $this->writeConfig('DB_NAME', $this->Post->get('db_name'));
            $this->writeConfig('DB_USER', $this->Post->get('db_user'));
            $this->writeConfig('DB_PASS', $this->Post->get('db_pass'));
            $this->writeConfig('DB_PREFIX', $this->Post->get('db_prefix'));

            $db_charset = $this->Post->get('db_charset');
            if ($db_charset == 'utf8mb4') {
                $this->writeConfig('DB_CHARSET', 'UTF-8');
                $this->writeConfig('DB_CONNECTION_CHARSET', 'utf8mb4');
            } else {
                $this->writeConfig('DB_CHARSET', $this->Post->get('db_charset'));
            }
        }
    }

    class Install_Step_4 extends Install_Step
    {
        public function business(): void
        {
            $Schema = new Schema(dsn(), DB_NAME, DB_PREFIX);
            $Schema->defSetYaml('schema');

            if (!!$Schema->existsDefinedTable()) {
                /**
                 * Compare Tables
                 */
                $diff = $Schema->compareTables();
                if (!empty($diff)) {
                    foreach ($diff as $tb) {
                        $this->Error['exists_table'][$tb] = true;
                    }
                }

                /**
                 * Compare Columns
                 */
                $tbs = Schema::listUp($Schema->schema);
                foreach ($tbs as $tb) {
                    $Schema->compareColumns($tb);
                }

                if (!empty($Schema->addRam)) {
                    foreach ($Schema->addRam as $col) {
                        $this->Error['shortage_column'][$col] = true;
                    }
                }

                if (!empty($Schema->changeRam)) {
                    foreach ($Schema->changeRam as $col) {
                        $this->Error['disparity_column'][$col] = true;
                    }
                }

                /**
                 * Finally Detect Error
                 */
                if (empty($this->Error)) {
                    $this->Notice['exists_table'] = true;
                }
            } else {
                $tbs = Schema::listUp($Schema->define);
                $fixedIndex = $Schema->defLoadYaml('index');
                $resultTbs = $Schema->createTables($tbs, $fixedIndex);

                foreach ($resultTbs as $tb => $result) {
                    if ($result) {
                        $this->Notice['create_table'][$tb] = true;
                    } else {
                        $this->Error['exists_table'][$tb] = true;
                    }
                }
            }
            if (!SetupCommon::checkAlterPerm()) {
                $this->Notice['alter_privilege'] = true;
                $this->Post->set('mysqlErr', DB::errorCode() . ': ' . implode(', ', DB::errorInfo()));
            }
        }
    }

    class Install_Step_5 extends Install_Step
    {
        /**
         * @var array $ids
         */
        protected $ids = [];

        public function validate(): bool
        {
            $this->checkInput();

            $code = $this->Post->get('user_code');
            $mail = $this->Post->get('user_mail');
            $pass = $this->Post->get('user_pass');

            if (!preg_match(REGEX_VALID_ID, $code)) {
                $this->Error['user_code_invalid'] = true;
            }
            if (!preg_match(REGEX_VALID_MAIL, $mail)) {
                $this->Error['user_mail_invalid'] = true;
            }
            if (!preg_match(REGEX_VALID_PASSWD, $pass)) {
                $this->Error['user_pass_invalid'] = true;
            }

            return empty($this->Error) ? true : false;
        }

        public function business(): void
        {
            @set_time_limit(0);
            $theme = $this->Post->get('select_theme');
            $_path = dirname(__FILE__) . '/bin/';

            $parsed = SetupCommon::themeParser($theme);

            if (empty($parsed['key'])) {
                $theme = $parsed['val'];
                $children = null;
            } else {
                $theme = $parsed['key'];
                $children = $parsed['val'];
            }

            $blog = [
                'name' => $this->Post->get('blog_name'),
                'code' => '',
                'domain' => DOMAIN,
            ];
            $user = [
                'code' => $this->Post->get('user_code'),
                'name' => $this->Post->get('user_name'),
                'pass' => $this->Post->get('user_pass'),
                'mail' => $this->Post->get('user_mail'),
            ];
            $path = [
                'yaml' => $_path . $theme . '/' . $theme . '.yaml',
                'themes' => $_path . $theme . '/themes/',
                'archives' => $_path . $theme . '/archives/',
                'media' => $_path . $theme . '/media/',
                'storage' => $_path . $theme . '/storage/',
            ];

            // setup directory
            $this->Post->set(
                'setupdir',
                'setup_' . substr(str_shuffle('1234567890abcdefghijklmnopqrstuvwxyz'), 0, 6)
            );

            if (Storage::exists($path['yaml'])) {
                Theme_Install::initSeq();
                $this->registerNewIDs($path['yaml']);
                if ($children) {
                    $this->recursiveRegisterNewIds($_path, $children);
                }
                /**
                 * Install Base Theme
                 */
                new Theme_Install($path, $this->ids, $blog, $user);

                /**
                 * boot install children recursivity
                 */
                if ($children) {
                    $this->recursiveInstall($children);
                }
            } else {
                $this->Error['yaml_exist'] = true;
            }
        }

        private function recursiveRegisterNewIds(string $basePath, array $children): void
        {
            foreach ($children as $key => $val) {
                if (is_array($val)) {
                    $this->registerNewIDs("{$basePath}{$key}/{$key}.yaml");
                    $this->recursiveRegisterNewIds($basePath, $val);
                } else {
                    $this->registerNewIDs("{$basePath}{$val}/{$val}.yaml");
                }
            }
        }

        private function registerNewIDs(string $yamlPath): void
        {
            $tables = [
                'blog', 'column', 'entry', 'category', 'rule', 'module', 'comment', 'user',
                'form', 'schedule', 'media', 'config_set',
            ];
            foreach ($tables as $table) {
                $yaml = Config::yamlLoad($yamlPath);
                if (isset($yaml[$table]) && is_array($yaml[$table])) {
                    foreach ($yaml[$table] as $record) {
                        if (!isset($record[$table . '_id'])) {
                            continue;
                        }
                        $id = $record[$table . '_id'];
                        if (isset($this->ids[$table][$id])) {
                            continue;
                        }
                        if ($table === 'column') {
                            $this->ids[$table][$id] = uuidv4();
                        } else {
                            $this->ids[$table][$id] = DB::query(SQL::nextval($table . '_id', dsn()), 'seq');
                        }
                    }
                }
            }
        }

        private function recursiveInstall($children)
        {
            foreach ($children as $key => $val) {
                if (is_array($val)) {
                    $theme = $key;
                    $this->addInstall($theme);
                    $this->recursiveInstall($val);
                } else {
                    $theme = $val;
                    $this->addInstall($theme);
                }
            }
        }

        private function addInstall($theme)
        {
            $basePath = dirname(__FILE__) . '/bin/';

            $blog_name_file = $basePath . $theme . '/config.yaml';
            $name = explode('@', $theme)[0];

            $blogData = Config::yamlLoad($blog_name_file);
            $blogName = $blogData['name'] ?? $name;
            $blogCode = $blogData['code'] ?? $name;

            $blog = [
                'name' => $blogName,
                'code' => $blogCode,
                'domain' => DOMAIN,
            ];

            $path = [
                'yaml' => $basePath . $theme . '/' . $theme . '.yaml',
                'themes' => $basePath . $theme . '/themes/',
                'archives' => $basePath . $theme . '/archives/',
                'media' => $basePath . $theme . '/media/',
                'storage' => $basePath . $theme . '/storage/',
            ];

            $installer = new Theme_Install_Child($path, $this->ids, $blog);

            return $installer;
        }
    }

    class Install_Step_6 extends Install_Step
    {
        public function validate(): bool
        {
            $newDirName = $this->Post->get('setup_dir_name');
            if (Storage::move(ROOT_DIR . 'setup', ROOT_DIR . $newDirName) && $newDirName !== '') {
                header('Location: ' . HTTP_ROOT . LOGIN_SEGMENT . '/');
                die();
            }

            $this->Error['error_rename_setup_dir'] = true;
            return false;
        }
    }

    class Theme_Install extends Import
    {
        public function __construct($path, $ids, $blog, $user)
        {
            parent::__construct();
            $this->setIds($ids);

            $array = Config::yamlLoad($path['yaml']);

            $oldBID = isset($array['blog'][0]['blog_id']) ? $array['blog'][0]['blog_id'] : 1;
            $blog['id'] = $this->getRAM('blog', $oldBID);
            $blog['parent'] = 0;
            $this->generateBlog($blog);
            $this->generateUser($user);

            if (array_key_exists('config_set', $array)) {
                $this->insertConfigSet($array['config_set']);
            }
            if (array_key_exists('media', $array)) {
                $this->insertMedia($array['media']);
            }
            if (array_key_exists('media_tag', $array)) {
                $this->insertMediaTag($array['media_tag']);
            }
            if (array_key_exists('category', $array)) {
                $this->insertCategory($array['category']);
            }
            if (array_key_exists('geo', $array)) {
                $this->insertGeo($array['geo']);
            }
            if (array_key_exists('entry', $array)) {
                $this->insertEntry($array['entry']);
            }
            if (array_key_exists('entry_sub_category', $array)) {
                $this->insertSubCategory($array['entry_sub_category']);
            }
            if (array_key_exists('column', $array)) {
                $this->insertColumn($array['column']);
            }
            $this->fixPrimaryImage();
            if (array_key_exists('tag', $array)) {
                $this->insertTag($array['tag']);
            }
            if (array_key_exists('comment', $array)) {
                $this->insertComment($array['comment']);
            }
            if (array_key_exists('schedule', $array)) {
                $this->insertSchedule($array['schedule']);
            }
            if (array_key_exists('module', $array)) {
                $this->insertModule($array['module']);
            }
            if (array_key_exists('rule', $array)) {
                $this->insertRule($array['rule']);
            }
            if (array_key_exists('config', $array)) {
                $this->insertConfig($array['config']);
            }
            if (array_key_exists('layout_grid', $array)) {
                $this->insertLayout($array['layout_grid']);
            }
            if (array_key_exists('form', $array)) {
                $this->insertForm($array['form']);
            }
            if (array_key_exists('dashboard', $array)) {
                $this->insertDashboard($array['dashboard']);
            }
            if (array_key_exists('field', $array)) {
                $this->insertField($array['field']);
            }
            if (array_key_exists('blog', $array)) {
                $this->updateBlogConfigSet($array['blog']);
            }
            $this->copyArchives($path['archives'], ROOT_DIR . ARCHIVES_DIR);
            $this->copyArchives($path['media'], ROOT_DIR . MEDIA_LIBRARY_DIR);
            $this->copyArchives($path['storage'], ROOT_DIR . MEDIA_STORAGE_DIR);

            $this->copyThemes($path['themes'], ROOT_DIR . 'themes/');
            $this->generateFulltext();
        }
    }

    class Theme_Install_Child extends Import
    {
        public function __construct($path, $ids, $blog)
        {
            parent::__construct();
            $this->setIds($ids);

            $array = Config::yamlLoad($path['yaml']);
            $oldBID = isset($array['blog'][0]['blog_id']) ? $array['blog'][0]['blog_id'] : 1;
            $oldParentBID = isset($array['blog'][0]['blog_parent']) ? $array['blog'][0]['blog_parent'] : 0;
            $blog['id'] = $this->getRAM('blog', $oldBID);
            $blog['parent'] = $this->getRAM('blog', $oldParentBID);
            $this->generateBlog($blog);
            $this->UID = 1;

            if (array_key_exists('config_set', $array)) {
                $this->insertConfigSet($array['config_set']);
            }
            if (array_key_exists('media', $array)) {
                $this->insertMedia($array['media']);
            }
            if (array_key_exists('media_tag', $array)) {
                $this->insertMediaTag($array['media_tag']);
            }
            if (array_key_exists('category', $array)) {
                $this->insertCategory($array['category']);
            }
            if (array_key_exists('geo', $array)) {
                $this->insertGeo($array['geo']);
            }
            if (array_key_exists('entry', $array)) {
                $this->insertEntry($array['entry']);
            }
            if (array_key_exists('entry_sub_category', $array)) {
                $this->insertSubCategory($array['entry_sub_category']);
            }
            if (array_key_exists('column', $array)) {
                $this->insertColumn($array['column']);
            }
            $this->fixPrimaryImage();
            if (array_key_exists('tag', $array)) {
                $this->insertTag($array['tag']);
            }
            if (array_key_exists('comment', $array)) {
                $this->insertComment($array['comment']);
            }
            if (array_key_exists('schedule', $array)) {
                $this->insertSchedule($array['schedule']);
            }
            if (array_key_exists('module', $array)) {
                $this->insertModule($array['module']);
            }
            if (array_key_exists('rule', $array)) {
                $this->insertRule($array['rule']);
            }
            if (array_key_exists('config', $array)) {
                $this->insertConfig($array['config']);
            }
            if (array_key_exists('layout_grid', $array)) {
                $this->insertLayout($array['layout_grid']);
            }
            if (array_key_exists('form', $array)) {
                $this->insertForm($array['form']);
            }
            if (array_key_exists('dashboard', $array)) {
                $this->insertDashboard($array['dashboard']);
            }
            if (array_key_exists('field', $array)) {
                $this->insertField($array['field']);
            }
            if (array_key_exists('blog', $array)) {
                $this->updateBlogConfigSet($array['blog']);
            }
            $this->copyArchives($path['archives'], ROOT_DIR . ARCHIVES_DIR);
            $this->copyArchives($path['media'], ROOT_DIR . MEDIA_LIBRARY_DIR);
            $this->copyArchives($path['storage'], ROOT_DIR . MEDIA_STORAGE_DIR);
            $this->copyThemes($path['themes'], ROOT_DIR . 'themes/');
            $this->generateFulltext();
        }
    }

    new Install_Factory();

    App::checkException();
} catch (Exception $e) {
    App::showError($e, false);
}
