<?php

namespace App\Services;

use App\Feature;
use App\Traits\ServiceAppAop;


class FeatureService
{
    use ServiceAppAop;
    protected $modelClass=Feature::class;
    public function getMapArray()
    {
        $features = Feature::query()->get();
        $map = [];
        foreach ($features as $feature){
            $map[$feature->id] = ["type"=>$feature->type,"logic"=>$feature->logic,"describe"=>$feature->describe];
        }
        return $map;
    }

    /**
     *  将特征翻译为数组显示
     *      $str : "1&2|(3&4)"
     *      array:[
     *          "strategyGroupStartSign" => false,  //是否为策略组起点,这将在解析时解析为 (
                "calculation" => "",                //运算规则，目前仅有 &,| 翻译后填入
                "type"=>"",                         //特征类型
                "id"=>"",                           //特征ID
                "logic"=>"",                        //特征逻辑
                "describe"=>"",                     //特征信息
                "strategyGroupEndSign" => false,    //是否为策略组终点，这将在解析时解析为 )
     *      ]
     *
     * @param string $str
     * @return array
     */
    public function translationFeature($str)
    {
        if (!$str)return null;
        $result = [];
        preg_match_all('/\d+|[\&\|\(\)]/',$str,$result); //初次匹配以数字&|()为分隔符生成数组
        $sign = 0;  //为第二次切割做起点标记
        $model = [];//第二次切割数组
        $ids = [];//记录出现的特征ID，统一查询
        foreach ($result[0] as $index => &$item){
            if (is_numeric($item)){
                $model[] = array_slice($result[0],$sign,($index-$sign)+1);
                $sign = $index+1;
                $ids[] = $item;
                continue;
            }
            if ($index == count($result[0])-1 && $item == ')'){
                $model[] = [")"];
            }
        }//以数字为标准切割策略组
        $features = Feature::query()->find($ids);//查询出现的特征
        $featureMap = [];
        foreach ($features as $index => $feature){
            $featureMap[$feature->id] = $index;
        }//为查询的特征重组为key-val形式数组留做引用
        foreach ($model as $index => &$m){
            $arr = [
                "strategyGroupStartSign" => false,//是否为策略组起点,这将在解析时解析为 (
                "calculation" => "",//运算规则，目前仅有 &,| 翻译后填入
                "type"=>"",  //特征类型
                "id"=>"",  //特征ID
                "logic"=>"",  //特征逻辑
                "describe"=>"",  //特征信息
                "strategyGroupEndSign" => false,//是否为策略组终点，这将在解析时解析为 )
            ];//最终对象组模型,策略组将几组特征组合引用
            foreach ($m as $string){
                if (is_numeric($string)){//填入特征信息
                    if (isset($featureMap[$string])){
                        $arr["type"] = Feature::TYPE[$features[$featureMap[$string]]->type];
                        $arr["id"] = $features[$featureMap[$string]]->id;
                        $arr["logic"] = $features[$featureMap[$string]]->logic;
                        $arr["describe"] = $features[$featureMap[$string]]->describe;
                    }
                    continue;
                }
                switch ($string){//特殊字符的翻译
                    case "(":
                        $arr["strategyGroupStartSign"] = true;
                        break;
                    case ")":
                        $model[$index-1]["strategyGroupEndSign"] = true;
                        break;
                    case "&":
                        $arr["calculation"] = "并且";
                        break;
                    case "|":
                        $arr["calculation"] = "或";
                        break;
                }
            }
            if (!$arr["id"]){
                unset($model[$index]);
                continue;
            }
            $m = $arr;//变更当前指针为翻译结果
        }
        return $model;
    }

    /**
     *  与translationFeature相反,将一组与translationFeature参数解析为简述
     *      $features:[
     *          "strategyGroupStartSign" => false,  //是否为策略组起点,这将在解析时解析为 (
                "calculation" => "",                //运算规则，目前仅有 &,| 翻译后填入
                "type"=>"",                         //特征类型
                "id"=>"",                           //特征ID
                "logic"=>"",                        //特征逻辑
                "describe"=>"",                     //特征信息
                "strategyGroupEndSign" => false,    //是否为策略组终点，这将在解析时解析为 )
     *      ]
     *      array:[
     *          "feature"   //简述结果
     *          "map"       //简述中出现的Map特征对象重组为key-val数组
     *      ]
     * @param array $features
     * @return array
     */
    public function analysisFeature($features)
    {
        $str = "";
        $map = [];
        foreach ($features as &$feature){
            if (!$feature["type"] || !$feature["logic"] || !$feature["describe"])continue;
            $typeMap = array_flip(Feature::TYPE);
            $f = Feature::query()->firstOrCreate([
                "type"=>$typeMap[$feature["type"]],  //特征类型
                "logic"=>$feature["logic"],  //特征逻辑
                "describe"=>$feature["describe"],  //特征信息
            ]);
            $feature["id"] = $f->id;
            $map[$feature["id"]] = $f;
            if ($feature["calculation"] == '并且')$str .= '&';
            if ($feature["calculation"] == '或') $str .= '|';
            if ($feature["strategyGroupStartSign"])$str .= "(";
            $str .= $feature["id"];
            if ($feature["strategyGroupEndSign"])$str .= ")";
        }
        return ['feature'=>$str,"map"=>$map];
    }

    /**
     * 格式化简述特征为可阅读显示
     *      $features : 特征对象集 重组为key-val数组数据，key为ID
     *      $value : 简述特征 例: "1&2|(3&4)"
     *      $columnMapping : 特征type属性字段映射 例:["商品名称"=>"commodity_name"],指定该参数会使特征格式化为SQL段
     *      string : 例："商品名称 包含 衣服 并且(订单类型 等于 创建订单 或者 承运商 不包含 顺丰)"
     *
     * @param array $features
     * @param string $value
     * @param array $columnMapping
     * @return string
     */
    public function formatFeature(array $features, $value, $columnMapping = null)
    {
        if (!$features)return $value;
        preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
        foreach ($result[0] as &$str){
            if (is_numeric($str) && isset($features[$str])){
                $column = Feature::TYPE[$features[$str]["type"]];
                $logic = $features[$str]["logic"];
                $describe = $features[$str]["describe"];
                if ($columnMapping){
                    $column = $columnMapping[$column] ?? $column;
                    switch ($logic){
                        case "包含":
                            $logic = " like ";
                            $describe = "%".$describe."%";
                            break;
                        case "不包含":
                            $logic = " not like ";
                            $describe = "%".$describe."%";
                            break;
                        case "等于":
                            $logic = " = ";
                            break;
                        case "小于":
                            $logic = " < ";
                            break;
                        case "大于":
                            $logic = " > ";
                            break;
                        case "小于等于":
                            $logic = " <= ";
                            break;
                        case "大于等于":
                            $logic = " >= ";
                            break;
                    }
                    $str = $column.$logic."'".$describe."'";
                }else $str = $column.' '.$logic.' '.$describe;
            }
            if ($str == "&"){
                if ($columnMapping) $str = " and ";
                else $str = " 并且 ";
            }
            if ($str == "|"){
                if ($columnMapping) $str = " or ";
                else $str = " 或 ";
            }
        }
        return implode("",$result[0]);
    }

    /**
     *  匹配特征 true匹配成功 false匹配失败
     *
     * @param string $value
     * @param array $columnMapping
     * @param array $matchObject
     *
     * @return bool
     */
    public function matchFeature($value, $columnMapping, $matchObject) :bool
    {
        if (!$value)return true;

        preg_match_all('/\d+|[\&\|\(\)]/',$value,$result);
        if (implode("",$result[0]) != $value)return false;

        foreach ($result[0] as &$str) {
            switch ($str){
                case "&":
                    $str = "&&";
                    break;
                case "|":
                    $str = "||";
                    break;
                default:
                    $str = $this->match($str, $value, $columnMapping, $matchObject);
            }
        }
        $is = implode("",$result[0]);
        return eval("return $is;");
    }

    /**
     * 匹配实例
     *
     * @param string|integer $id
     * @param string $value
     * @param array $columnMapping
     * @param array $matchObject
     *
     * @return string
     */
    private function match($id, $value, $columnMapping, $matchObject):string
    {
        if (!is_numeric($id))return $id; //非特征返回本身

        $features = app(CacheService::class)->getOrExecute("feature:".$value,function ()use($value){
            preg_match_all('/\d+/',$value,$ids);
            if ($ids[0])$fs = Feature::query()->whereIn("id",$ids[0])->get();
            else return false;
            $features = [];
            foreach ($fs as $f)$features[$f->id] = $f;
            return $features;
        });

        if (!isset($features[$id]))return 'false'; //特征不存在返回 false
        if (!($columnMapping[$features[$id]["type"]] ?? false))return 'false';
        return $this->getMatchBool($features[$id],$matchObject,explode(".",$columnMapping[$features[$id]["type"]]));
    }

    /**
     * 获取匹配结果
     *
     * @param array $feature
     * @param array|\stdClass $matchObject
     * @param array $column
     *
     * @return string
     */
    private function getMatchBool($feature, $matchObject, $column)
    {
        /*递归： 1.列数组为空 对象不存在列 返回false
                2.为数组时，递归数组内对象，任意一个符合返回true,全部不符合返回false
                3.为正常值时，进入核心匹配，必然返回一个值
                4.为对象时，向下递归，splice列数组
        */
        if (!$column)return 'false';
        if (!isset($matchObject[$column[0]]))return 'false';
        if (is_array($matchObject)){
            foreach ($matchObject as $object){
                if ($this->getMatchBool($feature,$object,$column) === 'true')return 'true';
            }
            return 'false';
        }
        $value = $matchObject[$column[0]];
        if (count($column)==1 && (is_string($value) || is_numeric($value) || is_null($value))){
            $logic = $feature["logic"];
            $describe = $feature["describe"];
            switch ($logic) {
                case "包含":
                    if (mb_strpos($value,$describe) !== false)return 'true';
                    break;
                case "不包含":
                    if (mb_strpos($value,$describe) === false)return 'true';
                    break;
                case "等于":
                    if ($value == $describe)return 'true';
                    break;
                case "小于":
                    if ($value < $describe)return 'true';
                    break;
                case "大于":
                    if ($value > $describe)return 'true';
                    break;
                case "小于等于":
                    if ($value <= $describe)return 'true';
                    break;
                case "大于等于":
                    if ($value >= $describe)return 'true';
                    break;
            }
            return 'false';
        };
        array_splice($column,0,1);
        return $this->getMatchBool($feature, $value, $column);
    }

}
