<?php

namespace Acms\Services\Blog;

use SQL;
use DB;
use Acms\Services\Facades\Common;
use Symfony\Component\Yaml\Yaml;

class Import
{
    /**
     * @var array
     */
    protected $yaml;

    /**
     * @var int
     */
    protected $bid;

    /**
     * @var int
     */
    protected $uid;

    /**
     * @var array
     */
    protected $ids;

    /**
     * @var array
     */
    protected $errors;

    /**
     * @var array
     */
    protected $mediaFieldFix = array();

    /**
     * Import constructor
     */
    public function __construct()
    {
        $this->uid = SUID;
    }

    /**
     * import blog data
     *
     * @param int $bid
     * @param string $yaml
     *
     * @return array
     */
    public function run($bid, $yaml)
    {
        $this->bid = $bid;
        $this->yaml = Yaml::parse($yaml);
        $this->ids = array();
        $this->errors = array();

        $this->dropData();
        $this->registerNewIDs();

        $tables = array(
            'category', 'entry', 'tag',
            'module', 'layout_grid',
            'rule', 'config', 'column', 'config_set',
            'dashboard', 'field', 'media', 'media_tag',
        );
        foreach ( $tables as $table ) {
            $this->insertData($table);
        }

        $this->generateFulltext();

        return $this->errors;
    }

    private function generateFulltext()
    {
        $DB = DB::singleton(dsn());
        foreach (array('category', 'entry') as $type) {
            $SQL = SQL::newSelect($type);
            $SQL->addSelect($type.'_id');
            $SQL->addWhereOpr($type.'_blog_id', $this->bid);
            $all = $DB->query($SQL->get(dsn()), 'all');

            foreach ( $all as $row ) {
                $id = $row[$type.'_id'];
                switch ( $type ) {
                    case 'category':
                        Common::saveFulltext('cid', $id, Common::loadCategoryFulltext($id));
                        break;
                    case 'entry':
                        Common::saveFulltext('eid', $id, Common::loadEntryFulltext($id));
                        break;
                }
            }
        }
    }

    /**
     * @param string $table
     *
     * @return void
     */
    private function insertData($table)
    {
        if ( !$this->existsYaml($table) ) {
            return;
        }
        foreach ( $this->yaml[$table] as $record ) {
            $SQL = SQL::newInsert($table);
            foreach ( $record as $field => $value ) {
                $value = $this->fix($table, $field, $value);
                if ( is_callable(array($this, $table . 'Fix')) ) {
                    $value = call_user_func_array(array($this, $table . 'Fix'), array($field, $value, $record));
                }

                if ( $value !== false ) {
                    $SQL->addInsert($field, $value);
                }
            }
            try {
                DB::query($SQL->get(dsn()), 'exec');
            } catch ( \Exception $e ) {
                $this->errors[] = $e->getMessage();
            }
        }
        foreach ($this->mediaFieldFix as $data) {
            $SQL = SQL::newUpdate('field');
            $SQL->addUpdate('field_value', $data['value']);
            $SQL->addWhereOpr('field_sort', $data['sort']);
            $SQL->addWhereOpr('field_key', $data['name']);
            $SQL->addWhereOpr('field_eid', $data['eid']);
            $SQL->addWhereOpr('field_cid', $data['cid']);
            $SQL->addWhereOpr('field_uid', $data['uid']);
            $SQL->addWhereOpr('field_bid', $data['bid']);
            $SQL->addWhereOpr('field_mid', $data['mid']);
            DB::query($SQL->get(dsn()), 'exec');
        }
    }

    /**
     * @param $table
     * @param $field
     * @param $value
     *
     * @return int
     */
    private function fix($table, $field, $value)
    {
        $key = substr($field, strlen($table . '_'));
        if ( $key === 'id' ) {
            $value = $this->getNewID($table, $value);
        } else if ( $key === 'category_id' ) {
            $value = $this->getNewID('category', $value);
        } else if ( $key === 'user_id' ) {
            $value = $this->getNewID('user', $value);
        } else if ( $key === 'entry_id' ) {
            $value = $this->getNewID('entry', $value);
        } else if ( $key === 'rule_id' ) {
            $value = $this->getNewID('rule', $value);
        } else if ( $key === 'module_id' ) {
            $value = $this->getNewID('module', $value);
        } else if ( $key === 'media_id') {
            $value = $this->getNewID('media', $value);
        } else if ( $key === 'set_id' || $key === 'config_set_id' ) {
            $value = $this->getNewID('config_set', $value);
        } else if ( $key === 'blog_id' ) {
            $value = $this->bid;
        }
        if ( in_array($key, array(
                'id', 'category_id', 'user_id', 'entry_id',
                'rule_id', 'module_id', 'blog_id'
            )) && empty($value) && $value !== '0'
        ) {
            $value = null;
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function configFix($field, $value, $record)
    {
        if ($record['config_key'] === 'media_banner_mid' && $field === 'config_value') {
            return $this->getNewID('media', $value);
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function entryFix($field, $value, $record)
    {
        if ( $field === 'entry_current_rev_id' ) {
            $value = 0;
        } else if ( $field === 'entry_last_update_user_id' ) {
            $value = $this->uid;
        } else if ( $field === 'entry_primary_image' && !empty($value) ) {
            $value = $this->getNewID('column', $value);
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function columnFix($field, $value, $record)
    {
        if (strncmp($record['column_type'], 'custom', 6) === 0 && $field === 'column_field_6') {
            $data = acmsDangerUnserialize($value);
            if ($data instanceof \Field) {
                $fixMediaField = array();
                foreach ($data->listFields() as $fd) {
                    foreach ($data->getArray($fd, true) as $i => $val) {
                        if (strpos($fd, '@media') !== false) {
                            $sourceFd = substr($fd, 0, -6);
                            if (!isset($fixMediaField[$sourceFd])) {
                                $fixMediaField[$sourceFd] = array();
                            }
                            $val = $this->getNewID('media', $val);
                            $fixMediaField[$sourceFd][] = $val;
                        } else {
                            $val = preg_replace('@([\d]{3})/(.*)\.([^\.]{2,6})@ui', sprintf("%03d", BID) . '/$2.$3', $val, -1);
                        }
                        if ($i === 0) {
                            $data->set($fd, $val);
                        } else {
                            $data->add($fd, $val);
                        }
                    }
                }
                // fix media id
                foreach ($fixMediaField as $fd => $mediaIds) {
                    foreach ($mediaIds as $j => $mid) {
                        if ($j === 0) {
                            $data->set($fd, $mid);
                        } else {
                            $data->add($fd, $mid);
                        }
                    }
                }
                return acmsSerialize($data);
            }
        } else if (strncmp($record['column_type'], 'media', 5) === 0 && $field === 'column_field_1') {
            $value = $this->getNewID('media', $value);
        } else if (strncmp($record['column_type'], 'module', 5) === 0 && $field === 'column_field_1') {
            $value = $this->getNewID('module', $value);
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function categoryFix($field, $value, $record)
    {
        if ( $field === 'category_parent' ) {
            $value = $this->getNewID('category', $value);
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function fulltextFix($field, $value, $record)
    {
        if ( $field === 'fulltext_eid' ) {
            $value = $this->getNewID('entry', $value);
        } else if ( $field === 'fulltext_cid' ) {
            $value = $this->getNewID('category', $value);
        } else if ( $field === 'fulltext_uid' ) {
            $value = $this->getNewID('user', $value);
        }
        if ( empty($value) ) {
            if ( $field === 'fulltext_ngram' ) {
                $value = '';
            } else {
                $value = null;
            }
        }
        if ( $field === 'fulltext_bid' && !empty($value) ) {
            return false;
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function moduleFix($field, $value, $record)
    {
        if ( $field === 'module_eid' ) {
            $value = $this->getNewID('entry', $value);
        } else if ( $field === 'module_cid' ) {
            $value = $this->getNewID('category', $value);
        } else if ( $field === 'module_uid' ) {
            $value = $this->getNewID('user', $value);
        } else if ( $field === 'module_bid' ) {
            $value = empty($value) ? null : $value;
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function layout_gridFix($field, $value, $record)
    {
        if ( $field === 'layout_grid_mid' ) {
            $value = $this->getNewID('module', $value);
        }
        return $value;
    }


    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function ruleFix($field, $value, $record)
    {
        if ( $field === 'rule_eid' ) {
            $value = $this->getNewID('entry', $value);
        } else if ( $field === 'rule_cid' ) {
            $value = $this->getNewID('category', $value);
        } else if ( $field === 'rule_uid' ) {
            $value = $this->getNewID('user', $value);
        } else if ( $field === 'rule_aid' ) {
            $value = $this->getNewID('alias', $value);
        }
        return $value;
    }

    /**
     * @param string $field
     * @param string $value
     * @param array $record
     *
     * @return mixed
     */
    private function fieldFix($field, $value, $record)
    {
        if ( $field === 'field_eid' ) {
            $value = $this->getNewID('entry', $value);
        } else if ( $field === 'field_cid' ) {
            $value = $this->getNewID('category', $value);
        } else if ( $field === 'field_uid' ) {
            $value = $this->getNewID('user', $value);
        } else if ( $field === 'field_mid' ) {
            $value = $this->getNewID('module', $value);
        } else if ( $field === 'field_bid' && !empty($value)) {
            $value = $this->bid;
        } else if ($field === 'field_value' && preg_match('/@media$/', $record['field_key'])) {
            $value = $this->getNewID('media', $value);
            $this->mediaFieldFix[] = array(
                'name' => substr($record['field_key'], 0, -6),
                'value' => $value,
                'sort' => $record['field_sort'],
                'eid' => $this->getNewID('entry', $record['field_eid']),
                'cid' => $this->getNewID('category', $record['field_cid']),
                'uid' => $this->getNewID('user', $record['field_uid']),
                'bid' => empty($record['field_bid']) ? null : $this->bid,
                'mid' => $this->getNewID('module', $record['field_mid']),
            );
        }
        return $value;
    }

    /**
     * @param string $table
     * @param int $id
     *
     * @return int
     */
    private function getNewID($table, $id)
    {
        if ( !isset($this->ids[$table][$id])  ) {
            return $id;
        }
        return $this->ids[$table][$id];
    }

    /**
     * @return void
     */
    private function registerNewIDs()
    {
        $tables = array(
            'category', 'column', 'alias',
            'entry', 'fulltext', 'media',
            'module', 'rule', 'media', 'config_set',
        );

        foreach ( $tables as $table ) {
            $this->registerNewID($table);
        }
    }

    /**
     * @param string $table
     *
     * @return void
     */
    private function registerNewID($table)
    {
        if ( !$this->existsYaml($table) ) {
            return;
        }
        foreach ( $this->yaml[$table] as $record ) {
            if ( !isset($record[$table . '_id']) ) {
                continue;
            }
            $id = $record[$table . '_id'];
            if ( isset($this->ids[$table][$id]) ) {
                continue;
            }
            $this->ids[$table][$id] = DB::query(SQL::nextval($table . '_id', dsn()), 'seq');
        }
    }

    /**
     * check yaml data
     *
     * @param $table
     *
     * @return bool
     */
    private function existsYaml($table)
    {
        if ( !isset($this->yaml[$table]) ) {
            return false;
        }
        $data = $this->yaml[$table];
        if ( !is_array($data) ) {
            return false;
        }
        return true;
    }

    /**
     * drop blog data
     *
     * @return void
     */
    private function dropData()
    {
        $tables = array(
            'category', 'entry', 'column', 'tag',
            'fulltext', 'field', 'media', 'media_tag',
            'approval', 'cache_reserve', 'column_rev', 'entry_rev',
            'field_rev', 'tag_rev',
            'dashboard', 'module', 'layout_grid', 'rule', 'config', 'config_set',
        );

        foreach ( $tables as $table ) {
            $this->clearTable($table);
        }
    }

    /**
     * clear table in database
     *
     * @param string $table
     *
     * @return void
     */
    private function clearTable($table)
    {
        $SQL = SQL::newDelete($table);
        if ( preg_match('/^(.*)\_rev$/', $table, $match) ) {
            $SQL->addWhereOpr($match[1].'_blog_id', $this->bid);
        } else {
            $SQL->addWhereOpr($table.'_blog_id', $this->bid);
        }
        if ($table === 'field') {
            $SQL->addWhereOpr('field_uid', null);
        }
        DB::query($SQL->get(dsn()), 'exec');
    }
}
