<?php
require_once dirname(__DIR__) . '/zendasmath/basic/describe.php';

/**
 * Checks whether all columns selected by a method contain empty columns.
 *
 * @param  object $dataframe
 * @param  array  $cols
 * @param  string $errorLang
 * @access public
 * @return array
 */
function checkNullColumns($dataframe, $cols, $errorLang)
{
    $emptyLine = array();
    foreach($cols as $col)
    {
        $column = $dataframe->col($col);
        if($column->isEmpty) $emptyLine[] = $dataframe->columns[$col];
    }

    if($emptyLine) return array('result' => 'fail', 'message' => sprintf($errorLang, implode(',', $emptyLine)));

    return array('result' => 'success');
}

/**
 * Check whether the column lengths entered are same.
 *
 * @param  object $dataframe
 * @param  array  $cols
 * @param  string $errorLang
 * @access public
 * @return array
 */
function checkColumnsLengthSame($dataframe, $cols, $errorLang)
{
    $lengthArr = array();
    foreach($cols as $col) $lengthArr[] = Describe::getDataLength($dataframe->col($col)->data);

    if(count(array_count_values($lengthArr)) != 1) return array('result' => 'fail', 'message' => $errorLang);

    return array('result' => 'success');
}

/**
 * Check the input column for null values.
 *
 * @param  object $dataframe
 * @param  array  $cols
 * @param  string $errorLang
 * @access public
 * @return array
 */
function checkColumnsHasEmpty($dataframe, $cols, $errorLang)
{
    $hasEmptyLine = array();
    foreach($cols as $col)
    {
        $column    = $dataframe->col($col);
        $length    = Describe::getDataLength($column->data);
        $sliceData = array_slice($column->data, 0, $length);

        if(count($sliceData) != count(Describe::notNull($sliceData))) $hasEmptyLine[] = $dataframe->columns[$col];
    }

    if($hasEmptyLine) return array('result' => 'fail', 'message' => sprintf($errorLang, implode(',', $hasEmptyLine)));

    return array('result' => 'success');
}

/**
 * Check if the input column contains strings
 *
 * @param  object $dataframe
 * @param  array  $cols
 * @param  string $errorLang
 * @access public
 * @return array
 */
function checkColumnsHasString($dataframe, $cols, $errorLang)
{
    $stringLine = array();
    foreach($cols as $col)
    {
        $column = $dataframe->col($col);
        if($column->haveString) $stringLine[] = $dataframe->columns[$col];
    }

    if($stringLine) return array('result' => 'fail', 'message' => sprintf($errorLang, implode(',', $stringLine)));

    return array('result' => 'success');
}

/**
 * getHighlight
 *
 * @param  array  $cols
 * @access public
 * @return string
 */
function getHighlight($cols)
{
    global $dasLang;

    $points = array();
    foreach($cols as $col => $rows) foreach($rows as $row) $points[] = "$col,$row";

    $points = array_unique($points);
    $points = implode(';', $points);
    return "<span class='btn btn-link highlight-points' data-points='{$points}' title='{$dasLang->highlight}'><i class='icon icon-active'></i></a></span>";
}

/**
 * CheckData class
 */
class checkData
{
    public $datasets;

    public $checkUnionUniqueList;

    public $checkColumnUniqueDataset;

    /**
     * 构造函数
     *
     * @param array $data
     * @access public
     * @return void
     */
    public function __construct()
    {
        $this->datasets = array();
    }

    /**
     * Set data for validate.
     *
     * @param array $data
     * @access public
     * @return void
     */
    public function setData($data, $name, $validates)
    {
        if(!is_array($data)) return;

        $dataset = new stdclass();
        $dataset->name      = $name;
        $dataset->data      = $data;
        $dataset->validates = $validates;

        $this->datasets[] = $dataset;
    }

    /**
     * Set data array list for validate.
     *
     * @param array $dataList
     * @access public
     * @return void
     */
    public function setDataList($dataList, $nameList, $validates)
    {
        if(!is_array($dataList) and !is_array(current($dataList))) return;

        foreach($nameList as $index => $name)
        {
            $dataset = new stdclass();
            $dataset->name      = $name;
            $dataset->data      = $dataList[$index];
            $dataset->validates = $validates;

            $this->datasets[] = $dataset;
        }
    }

    /**
     * Check data validate.
     *
     * @access public
     * @return bool | string
     */
    public function check()
    {
        global $dasLang;
        $lang = $dasLang;

        $this->toString();
        $this->parseType();
        $this->trimEmpty();

        /* Simple check. */
        $simpleCheck = array('continuous', 'number', 'notCommon', 'unique', 'int', 'positive', 'nonnegative');
        foreach($this->datasets as $dataset)
        {
            foreach($simpleCheck as $key)
            {
                $check = $this->notEmpty($dataset);
                if(!$check) return sprintf($lang->common->notEmptyError, $dataset->name);

                if(in_array($key, $dataset->validates))
                {
                    list($otherCheck, $index) = $this->$key($dataset);
                    $langKey = "{$key}Error";
                    if(!$otherCheck) return sprintf($lang->common->$langKey, $dataset->name, $index + 1);
                }
            }
        }

        /* Row equal check. */
        $checkRowEqList = array();
        $checkNames     = array();
        foreach($this->datasets as $dataset)
        {
            if(in_array('rowEqual', $dataset->validates))
            {
                $checkRowEqList[] = $dataset;
                $checkNames[]     = $dataset->name;
            }
        }

        $check = $this->rowEqual($checkRowEqList);
        if(!$check) return sprintf($lang->common->rowEqualError, implode(',', $checkNames));

        /* Column unique count check. */
        foreach($this->datasets as $dataset)
        {
            foreach($dataset->validates as $validate)
            {
                if(strpos($validate, 'columnUnique') !== false)
                {
                    $this->checkColumnUniqueDataset = $dataset;
                    $code = str_replace('columnUnique', 'return $this->columnUnique()', $validate);
                    $check = eval($code . ';');
                    if(!$check) return sprintf($lang->common->columnUniqueError, $dataset->name, str_replace('columnUnique', '', $validate));
                }
            }
        }

        /* Union unique count check. */
        $checkUnionList = array();
        $checkNames     = array();
        $unionValid     = null;
        foreach($this->datasets as $dataset)
        {
            foreach($dataset->validates as $validate)
            {
                if(strpos($validate, 'unionUnique') !== false)
                {
                    $checkUnionList[] = $dataset;
                    $checkNames[]     = $dataset->name;
                    $unionValid       = $validate;
                }
            }
        }

        if(count($checkUnionList) != 0)
        {
            $this->checkUnionUniqueList = $checkUnionList;
            $code = str_replace('unionUnique', 'return $this->unionUnique()', $unionValid);
            $check = eval($code . ';');
            if(!$check) return sprintf($lang->common->unionUniqueError, implode(',', $checkNames), str_replace('unionUnique', '', $unionValid));
        }

        /* Number count check. */
        foreach($this->datasets as $dataset)
        {
            foreach($dataset->validates as $validate)
            {
                if(strpos($validate, 'N') !== false)
                {
                    $this->checkNumberCount = $dataset->data;
                    $code = str_replace('N', 'return count($this->checkNumberCount)', $validate);
                    $check = eval($code . ';');
                    if(!$check) return sprintf($lang->common->numberCountError, $dataset->name, str_replace('N', '', $validate));
                }
            }
        }

        return true;
    }

    /**
     * Get column unique.
     *
     * @access public
     * @return int
     */
    public function columnUnique()
    {
        $unique = array_unique($this->checkColumnUniqueDataset->data);

        return count($unique);
    }

    /**
     * Get union column unique.
     *
     * @access public
     * @return int
     */
    public function unionUnique()
    {
        $data = array();
        foreach($this->checkUnionUniqueList as $dataset)
        {
            $data = array_merge($data, $dataset->data);
        }
        $unique = array_unique($dataset->data);

        return count($unique);
    }

    /**
     * Check row equal.
     *
     * @param array $datasets
     * @access public
     * @return bool
     */
    public function rowEqual($datasets)
    {
        $count = -1;
        foreach($datasets as $dataset)
        {
            if($count == -1)
            {
                $count = count($dataset->data);
            }
            else
            {
                if($count != count($dataset->data)) return false;
            }
        }

        return true;
    }

    /**
     * Check not empty.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function notEmpty($dataset)
    {
        return count($dataset->data) !== 0;
    }

    /**
     * Check continuous.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function continuous($dataset)
    {
        $index = $this->findIndex($dataset->type, 'empty');

        return array(!in_array('empty', $dataset->type), $index);
    }

    /**
     * Check continuous.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function number($dataset)
    {
        $unique = array_unique($dataset->type);
        $index  = $this->findIndex($dataset->type, 'number', false);

        return array((count($unique) == 1 and current($unique) == 'number'), $index);
    }

    /**
     * Check not common.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function notCommon($dataset)
    {
        $unique = array_unique($dataset->data);
        return array(count($unique) !== 1, 0);
    }

    /**
     * Check unique.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function unique($dataset)
    {
        $unique = array_unique($dataset->data);
        return array(count($dataset->data) === count($unique), 0);
    }

    /**
     * Check integer.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function int($dataset)
    {
        $checkNumber = $this->number($dataset);
        if(!$checkNumber[0]) return $checkNumber;

        foreach($dataset->data as $index => $value)
        {
            if(floor($value) != $value) return array(false, $index);
        }
        return array(true, 0);
    }

    /**
     * Check positive.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function positive($dataset)
    {
        $checkNumber = $this->number($dataset);
        if(!$checkNumber[0]) return $checkNumber;

        foreach($dataset->data as $index => $value)
        {
            if($value <= 0) return array(false, $index);
        }
        return array(true, 0);
    }

    /**
     * Check nonnegative.
     *
     * @param array $dataset
     * @access public
     * @return bool
     */
    public function nonnegative($dataset)
    {
        $checkNumber = $this->number($dataset);
        if(!$checkNumber[0]) return $checkNumber;

        foreach($dataset->data as $index => $value)
        {
            if($value < 0) return array(false, $index);
        }
        return array(true, 0);
    }

    /**
     * Covert data item to string method.
     *
     * @access private
     * @return void
     */
    private function toString()
    {
        foreach($this->datasets as $index => $dataset)
        {
            $data = array();
            foreach($dataset->data as $value)
            {
                $value = self::cast2Number($value, 'string');
                if(is_numeric($value) or (!is_numeric($value) and !empty($value)))
                {
                    $data[] = "$value";
                }
                else
                {
                    $data[] = '';
                }
            }

            $dataset->data = $data;
            $this->datasets[$index] = $dataset;
        }
    }

    /**
     * Parse data item type.
     *
     * @access private
     * @return void
     */
    private function parseType()
    {
        foreach($this->datasets as $index => $dataset)
        {
            $types = array();
            foreach($dataset->data as $value)
            {
                $type = '';
                if(is_numeric($value)) $type = 'number';
                if(!is_numeric($value) and !empty($value)) $type = 'text';
                if(!is_numeric($value) and empty($value)) $type = 'empty';

                $types[] = $type;
            }

            $dataset->type = $types;
            $this->datasets[$index] = $dataset;
        }
    }

    /**
     * Trim empty data from end
     *
     * @access private
     * @return
     */
    private function trimEmpty()
    {
        foreach($this->datasets as $index => $dataset)
        {
            while(end($dataset->type) == 'empty')
            {
                array_pop($dataset->data);
                array_pop($dataset->type);
            }

            $this->datasets[$index] = $dataset;
        }
    }

    /**
     * Find array element with index.
     *
     * @param  array $array
     * @param  string $needle
     * @param  bool $matched
     * @access private
     * @return int
     */
    private function findIndex($array, $needle, $matched = true)
    {
        foreach($array as $index => $value)
        {
            if($matched && $value == $needle) return $index;
            if(!$matched && $value != $needle) return $index;
        }
        return -1;
    }

    /**
     * Cast string to number
     *
     * @param  string $str
     * @param  string $type float|string
     * @static
     * @access public
     * @return float|null
     */
    static public function cast2Number($str, $type = 'float')
    {
        $isPercent = false;
        $result = $str;
        if(is_string($result))
        {
            $result = trim($result);
            $result = str_replace(' ', '', $result);

            if(strpos($result, '%') !== false)
            {
                $result = trim($result, '%');
                $isPercent = true;
            }
        }

        if($isPercent) $result /= 100;

        $result = $type == 'float' ? (float)$result : (string)$result;

        return $result;
    }
}
