<?php
/**
 * 用对象数据创建度量。
 * Create a metric.
 *
 * @param  object $metric
 * @access public
 * @return int|false
 */
class zentaobizMetric extends metricModel
{
    public function create($metric)
    {
        $this->dao->insert(TABLE_METRIC)->data($metric)
            ->autoCheck()
            ->checkIF(!empty($metric->name), 'name', 'unique', "`deleted` = '0'")
            ->checkIF(!empty($metric->code), 'code', 'unique', "`deleted` = '0'")
            ->exec();
        if(dao::isError()) return false;
        $metricID = $this->dao->lastInsertID();

        $this->loadModel('action')->create('metric', $metricID, 'opened', '', '', $this->app->user->account);

        return $metricID;
    }

    /**
     * 更新度量项。
     * Update a metric.
     *
     * @param  int    $id
     * @param  object $metric
     * @access public
     * @return int|false
     */
    public function update($id, $metric)
    {
        $oldMetric = $this->getByID($id);

        $this->dao->update(TABLE_METRIC)->data($metric)
            ->autoCheck()
            ->checkIF(!empty($metric->name) && $metric->name != $oldMetric->name, 'name', 'unique', "`deleted` = '0'")
            ->checkIF(!empty($metric->code) && $metric->code != $oldMetric->code, 'code', 'unique', "`deleted` = '0'")
            ->where('id')->eq($id)
            ->exec();

        if(dao::isError()) return false;

        $changes = common::createChanges($oldMetric, $metric);
        if($changes)
        {
            $actionID = $this->loadModel('action')->create('metric', $id, 'edited', '', '', $this->app->user->account);
            $this->action->logHistory($actionID, $changes);
        }

        return $changes;
    }

    /**
     * 根据度量项信息生成度量项php模板内容。
     * Generante php template content from metric information.
     *
     * @param  int    $metricID
     * @access public
     * @return array
     */
    public function getMetricPHPTemplate(int $metricID): array
    {
        $metric = $this->getByID($metricID);

        $metric->nameEN  = ucfirst(str_replace('_', ' ', $metric->code));
        $metric->scope   = $this->lang->metric->scopeList[$metric->scope];
        $metric->object  = $this->lang->metric->objectList[$metric->object];
        $metric->purpose = $this->lang->metric->purposeList[$metric->purpose];

        $replaceFields = array('name', 'nameEN', 'code', 'scope', 'object', 'purpose', 'unit', 'desc', 'definition');

        $content = file_get_contents($this->app->getModuleRoot() . DS . 'metric' . DS . 'template' . DS . 'metric.php.tmp');

        foreach($replaceFields as $replaceField)
        {
            $replaceContent = $this->replaceCRLF($metric->$replaceField);
            $content = str_replace("{{{$replaceField}}}", $replaceContent, $content);
        }

        return array("{$metric->code}.php", $content);
    }

    /**
     * 更新度量项。
     * Updata metric.
     *
     * @param  object $metric
     * @access public
     * @return void
     */
    public function updateMetric(object $metric)
    {
        $this->dao->update(TABLE_METRIC)->data($metric)
            ->where('id')->eq($metric->id)
            ->exec();
    }

    /**
     * 检查度量项计算文件是否存在。
     * Check if the calculator file exists or not.
     *
     * @param  string $code
     * @access public
     * @return array
     */
    public function checkCustomCalcExists($code)
    {
        $calcName  = $this->getCustomCalcFile($code);
        $fileExist = file_exists($calcName);

        return $this->getVerifyError(__FUNCTION__, $fileExist);
    }

    /**
     * 检查度量项计算文件语法错误。
     * Check syntax error of the calculator file.
     *
     * @param  string $code
     * @access public
     * @return array
     */
    public function checkCustomCalcSyntax($code)
    {
        $calcName = $this->getCustomCalcFile($code);

        $fileContent = file_get_contents($calcName);
        $fileContent = $this->removeFirstLine($fileContent);
        $fileContent = $this->calcAddReturn($fileContent);

        try
        {
            eval($fileContent);
        }
        catch(ParseError $e)
        {
            $errorLine = $e->getLine();
            return $this->getVerifyError(__FUNCTION__, false, "Line {$errorLine} error: {$e->getMessage()}");
        }

        return $this->getVerifyError(__FUNCTION__, true);
    }

    /**
     * 检查度量项计算文件是否定义了必要的类。
     * Check whether the necessary class exist in the file.
     *
     * @param  string $code
     * @access public
     * @return array
     */
    public function checkCustomCalcClassName($code)
    {
        if(!$this->checkCustomCalcExists($code)[0]) return $this->checkCustomCalcExists($code);

        $evalFile = $this->getCustomEvalPath($code);
        $codes    = $this->genClassNameCheckCode($code);

        list($result, $output) = $this->execTmpCalc($evalFile, $codes);

        return $this->getVerifyError(__FUNCTION__, !empty($result));
    }

    /**
     * 检查度量项计算文件中是否编写了必要的方法。
     * Check whether the necessary methods exist in the file.
     *
     * @param  string $code
     * @access public
     * @return array
     */
    public function checkCustomCalcClassMethod($code)
    {
        if(!$this->checkCustomCalcExists($code)[0]) return $this->checkCustomCalcExists($code);

        $evalFile = $this->getCustomEvalPath($code);
        $codes    = $this->genClassMethodCheckCode($code);

        list($result, $output) = $this->execTmpCalc($evalFile, $codes);

        $methodNameList = json_decode($result);

        foreach($this->config->metric->necessaryMethodList as $method)
        {
            if(!in_array($method, $methodNameList)) return $this->getVerifyError(__FUNCTION__, false, $method);
        }

        return $this->getVerifyError(__FUNCTION__, true);
    }

    /**
     * 检查度量项计算文件是否可以运行。
     * Check whether the calculator file can run.
     *
     * @param  string  $code
     * @access public
     * @return array
     */
    public function checkCustomCalcRuntime($code)
    {
        try
        {
            $currentDebug = $this->config->debug;
            $this->config->debug = 2;
            $output = $this->runCustomCalc($code);
            $this->config->debug = $currentDebug;
        }
        catch(Error $e)
        {
            return $this->getVerifyError(__FUNCTION__, false, strip_tags($e->getMessage()));
        }
        catch(Exception $e)
        {
            return $this->getVerifyError(__FUNCTION__, false, strip_tags($e->getMessage()));
        }

        if(dao::isError()) return $this->getVerifyError(__FUNCTION__, false, dao::getError());

        return $this->getVerifyError(__FUNCTION__, true);
    }

    /**
     * 运行用户自定义的度量项文件。
     * Run metric file by custom, get result.
     *
     * @param  string $code
     * @access public
     * @return array|false
     */
    public function runCustomCalc($code)
    {
        $metric = $this->dao->select('id,code,scope,purpose')->from(TABLE_METRIC)->where('code')->eq($code)->fetch();
        if(!$metric) return false;

        $calcPath = $this->getCustomCalcRoot() . $code . '.php';
        if(!is_file($calcPath)) return false;

        include_once $this->getBaseCalcPath();
        include_once $calcPath;
        $calculator = new $metric->code;

        $statement = $this->getDataStatement($calculator);
        $rows = $statement->fetchAll();

        foreach($rows as $row) $calculator->calculate($row);
        return $calculator->getResult();
    }

    /**
     * 将用户定义的度量文件从临时目录移动到系统目录中。
     * Move custom calculator file to calc directory
     *
     * @param  object $metric
     * @access public
     * @return bool
     */
    public function moveCalcFile($metric)
    {
        $tmpCalc = $this->getCustomCalcFile($metric->code);
        $newCalc = $this->getCalcRoot() . $metric->scope . DS . $metric->purpose . DS . $metric->code . '.php';

        return rename($tmpCalc, $newCalc);
    }

    /**
     * 执行临时的度量项计算文件。
     * Execute the temporary metric calculator file.
     *
     * @param  string  $filename
     * @param  string  $content
     * @access private
     * @return array
     */
    private function execTmpCalc($filename, $content)
    {
        file_put_contents($filename, $content);

        $output = array();
        $result = exec("php $filename", $output);

        unlink($filename);

        return array($result, $output);
    }

    /**
     * 生成度量计算文件类名检查代码。
     * Generate code to check class name of calculator file.
     *
     * @param  string  $code
     * @access private
     * @return string
     */
    private function genClassNameCheckCode($code)
    {
        $codes = array();
        $codes[] = "<?php";
        $codes[] = "require_once \"{$this->getBaseCalcPath()}\";";
        $codes[] = "require_once \"{$this->getCustomCalcFile($code)}\";";
        $codes[] = "echo class_exists('{$code}');";

        return implode("\n", $codes);
    }

    /**
     * 生成度量计算文件类方法检查代码。
     * Generate code to check class method of calculator file.
     *
     * @param  string  $code
     * @access private
     * @return string
     */
    private function genClassMethodCheckCode($code)
    {
        $codes = array();
        $codes[] = "<?php";
        $codes[] = "require_once \"{$this->getBaseCalcPath()}\";";
        $codes[] = "require_once \"{$this->getCustomCalcFile($code)}\";";

        $codes[] = "\$classReflection = new ReflectionClass('{$code}');";
        $codes[] = "\$methodList = \$classReflection->getMethods();";
        $codes[] = "\$methodNameList = array();";
        $codes[] = "foreach(\$methodList as \$index => \$reflectionMethod)";
        $codes[] = "{";
        $codes[] = "    if(\$reflectionMethod->class == '{$code}') \$methodNameList[\$index] = \$reflectionMethod->name;";
        $codes[] = "}";
        $codes[] = "echo json_encode(\$methodNameList);";

        return implode("\n", $codes);
    }

    /**
     * 获取验证错误信息。
     * Get verify error message.
     *
     * @param  string   $funcName
     * @param  bool     $result
     * @param  string   $extra
     * @access private
     * @return array
     */
    private function getVerifyError($funcName, $result, $extra = '')
    {
        $verifyCustom = $this->lang->metric->verifyCustom;
        $verifyError  = isset($verifyCustom->$funcName) ? $verifyCustom->$funcName['error'] : null;

        if(!empty($extra)) $verifyError = "$verifyError $extra";

        return array($result, $result ? null : $verifyError);
    }

    /**
     * 获取自定义的度量项计算文件的路径。
     * Get path of custom calculator file.
     *
     * @param  string  $code
     * @access private
     * @return string
     */
    private function getCustomCalcFile($code)
    {
        return $this->getCustomCalcRoot() . $code . '.php';
    }

    /**
     * 获取自定义的度量项计算文件的临时路径。
     * Get path of temporary calculator file.
     *
     * @param  string  $code
     * @access private
     * @return void
     */
    private function getCustomEvalPath($code)
    {
        return tempnam(sys_get_temp_dir(), "{$code}_");
    }

    /**
     * 获取用户自定义的度量项计算文件的根目录。
     * Get root of custom metric calculator.
     *
     * @access public
     * @return string
     */
    private function getCustomCalcRoot()
    {
        return $this->app->getTmpRoot() . 'metric' .DS;
    }

    /**
     * 删除第一行内容。
     * Remove the first line.
     *
     * @param  string $content
     * @access private
     * @return string
     */
    private function removeFirstLine($content)
    {
        $lines = explode("\n", $content);
        array_shift($lines);
        $content = implode("\n", $lines);

        return $content;
    }

    /**
     * 在内容的第一行添加return。
     * Add return to the first line.
     *
     * @param  string    $content
     * @access private
     * @return string
     */
    private function calcAddReturn($content)
    {
        $lines = explode("\n", $content);
        array_unshift($lines, "return;");
        $content = implode("\n", $lines);

        return $content;
    }
}
