<?php
/**
 * Created by PhpStorm.
 * User: stanley-king
 * Date: 2016/10/7
 * Time: 下午12:44
 */

namespace search;

use algorithm;
use brand_helper;
use category_helper;
use Log;
use special_manager;
use commonid_helper;


class brand_dict extends words
{
    private $mBrandCommon;
    private $mBrandHots;

    public function __construct()
    {
        parent::__construct();
        $this->mBrandCommon = new one_multi();
        $this->mBrandHots  = new one_multi();
        $this->init();
    }
    private function init()
    {
        $brands = brand_helper::brands();
        foreach ($brands as $val)
        {
            $datas = $val['data'];
            foreach ($datas as $item) {
                $name = $item['brand_name'];
                $bid = intval($item['brand_id']);
                $this->parase($name,$bid);
            }
        }
    }
    public function commons($brand_id)
    {
        return $this->mBrandCommon->get($brand_id);
    }
    public function hots($brand_id) {
        return $this->mBrandHots->get($brand_id);
    }
    public function add_brand($brand_id, $common_id, $hot_id)
    {
        $this->mBrandCommon->add($brand_id,$common_id);
        $this->mBrandHots->add($brand_id,$hot_id);
    }
}

class category_dict extends words
{
    private $mGcHot;   //一个GC对应一个HotID
    private $mHoTree;  //HOT的父子关系

    private $mHotBrand;
    private $mHotCommon;

    public function __construct()
    {
        parent::__construct();
        $this->mGcHot   = new one_one();
        $this->mHoTree = new array_tree();
        $this->mHotBrand = new one_multi();
        $this->mHotCommon = new one_multi();

        $this->init();
    }
    public function init()
    {
        $cats = category_helper::instance()->categories();
        foreach ($cats as $item)
        {
            $hot_id = intval($item['hot_id']);
            $this->mHoTree->add($hot_id,0);

            $name = $item['name'];
            $this->parase($name,$hot_id);

            foreach($item['subitem'] as $sub_item) {
                $subhot_id = intval($sub_item['hot_id']);

                $this->mHoTree->add($subhot_id,$hot_id);

                $cids = category_helper::instance()->cids($subhot_id);
                $this->add_gc_hot($cids,$subhot_id);

                $subname = $sub_item['name'];
                $this->parase($subname,$subhot_id);
            }
        }
    }
    public function brands($hotid)
    {
        if($this->mHoTree->is_parent($hotid) == false) {
            return $this->mHotBrand->get($hotid);
        }
        else
        {
            $sub_hots = $this->mHoTree->subs($hotid);

            $pos = 0;
            $brands = [];
            foreach ($sub_hots as $hot)
            {
                if($pos == 0) {
                    $brands = $this->mHotBrand->get($hot);
                } else {
                    $tmp = $this->mHotBrand->get($hot);
                    $brands = algorithm::set_union($brands,$tmp);
                }
                $pos++;
            }
            return $brands;
        }
    }

    public function commons($hotid)
    {
        if($this->mHoTree->is_parent($hotid) == false) {
            return $this->mHotCommon->get($hotid);
        }
        else
        {
            $sub_hots = $this->mHoTree->subs($hotid);

            $pos = 0;
            $cids = [];
            foreach ($sub_hots as $hot)
            {
                if($pos == 0) {
                    $cids = $this->mHotCommon->get($hot);
                } else {
                    $tmp = $this->mHotCommon->get($hot);
                    $cids = algorithm::set_union($cids,$tmp);
                }
                $pos++;
            }
            return $cids;
        }
    }

    public function add_hot($hotid,$brand,$common)
    {
        $this->mHotBrand->add($hotid,$brand);
        $this->mHotCommon->add($hotid,$common);
    }

    public function hotid($gc_id) {
        return $this->mGcHot->get($gc_id);
    }

    private function add_gc_hot($gc_ids,$hot_id)
    {
        foreach ($gc_ids as $cid) {
            $cid = intval($cid);
            $this->mGcHot->add($cid,$hot_id);
        }
    }
}

class goods_dict extends words
{
    private $cid_brands;
    private $cid_hots;

    public function __construct()
    {
        parent::__construct();
        $this->cid_brands = new one_one();
        $this->cid_hots = new one_one();
    }
    public function add_goods($common_id,$brand,$hot) {
        $this->cid_brands->add($common_id,$brand);
        $this->cid_hots->add($common_id,$hot);
    }

    public function hots($cids)
    {
        $values = [];
        foreach ($cids as $cid) {
            $hot = $this->cid_hots->get($cid);
            if(algorithm::binary_search($values,$hot) == false) {
                $pos = algorithm::lower_bonud($values,$hot);
                algorithm::array_insert($values,$pos,$hot);
            }
        }

        return $values;
    }

    public function brands($cids)
    {
        $values = [];
        foreach ($cids as $cid) {
            $brand = $this->cid_brands->get($cid);
            if(algorithm::binary_search($values,$brand) == false) {
                $pos = algorithm::lower_bonud($values,$brand);
                algorithm::array_insert($values,$pos,$brand);
            }
        }

        return $values;
    }
}

class special_dict extends words
{
    private $spid_names;
    public function __construct()
    {
        parent::__construct();
        $this->spid_names = new one_one();
        $this->init();
    }
    private function init()
    {
        $mod_special = Model();

        $i = 0;
        while (true)
        {
            $start = $i * 1000;
            $items = $mod_special->table('mb_special')
                                 ->field('*')
                                 ->where(['special_id' => ['gt', 0],'state' => 0,'reader_type' => 0])
                                 ->order('special_id asc')
                                 ->limit("{$start},1000")
                                 ->select();
            $i++;

            if (empty($items)) {
                break;
            }
            else
            {
                foreach ($items as $item)
                {
                    $spid = intval($item['special_id']);
                    $keywords = $item['keywords'];
                    $title = $item['share_title'];
                    if(!empty($keywords)) {
                        $this->parase($keywords,$spid);
                        $this->parase($title,$spid);
                        $this->spid_names->add($spid,$title);
                    }
                }
            }
        }
    }
    public function name($val)
    {
        return $this->spid_names->get($val);
    }
}

class goods_info
{
    private $mGoods;
    public function __construct()
    {
        $this->mGoods = [];
    }

    public function add($commonid, $price, $click, $salenum)
    {
        $commonid = intval($commonid);
        if(!array_key_exists($commonid,$this->mGoods)) {
            $this->mGoods[$commonid]['price'] = intval($price * 100 + 0.5);
            $this->mGoods[$commonid]['click'] = intval($click);
            $this->mGoods[$commonid]['salenum'] = intval($salenum);
        }
    }
    public function goods($commonid)
    {
        return $this->mGoods[$commonid];
    }

    static public function click_asc($left,$right)
    {
        $t_l = intval($left['click']);
        $t_r = intval($right['click']);

        if($t_l > $t_r) return 1;
        elseif($t_l < $t_r) return -1;
        else return 0;
    }
    static public function click_desc($left,$right)
    {
        $t_l = intval($left['click']);
        $t_r = intval($right['click']);

        if($t_l > $t_r) return -1;
        elseif($t_l < $t_r) return 1;
        else return 0;
    }

    static public function salenum_asc($left,$right)
    {
        $t_l = intval($left['salenum']);
        $t_r = intval($right['salenum']);

        if($t_l > $t_r) return 1;
        elseif($t_l < $t_r) return -1;
        else return 0;
    }
    static public function salenum_desc($left,$right)
    {
        $t_l = intval($left['salenum']);
        $t_r = intval($right['salenum']);

        if($t_l > $t_r) return -1;
        elseif($t_l < $t_r) return 1;
        else return 0;
    }
    static public function price_asc($left,$right)
    {
        $t_l = intval($left['price']);
        $t_r = intval($right['price']);

        if($t_l > $t_r) return 1;
        elseif($t_l < $t_r) return -1;
        else return 0;
    }
    static public function price_desc($left,$right)
    {
        $t_l = intval($left['price']);
        $t_r = intval($right['price']);

        if($t_l > $t_r) return -1;
        elseif($t_l < $t_r) return 1;
        else return 0;
    }
}

class searcher
{
    const filter = "goods_commonid,gc_id,brand_id,goods_mobile_name,goods_jingle,goods_spec,goods_click,goods_salenum,goods_price";

    private $name_dict;
    private $jingle_dict;
    private $spec_dict;
    private $special_dict;

    private $brand_dict;
    private $category_dict;
    private $goods_dict;

    private $goods_info;
    private $price_cids;
    private $promot_cids;

    private static $stInstance;

    public static function instance()
    {
        if(self::$stInstance == null) {
            self::$stInstance = new searcher();
        }
        return self::$stInstance;
    }

    private function get_spec($arSpec)
    {
        $specs = unserialize($arSpec);
        if(isset($specs) && is_array($specs))
        {
            $spec_val = '';
            foreach ($specs as $key => $val) {
                $spec_val .= " ";
                $spec_val .= $val;
            }
            return trim($spec_val);
        } else {
            return "";
        }
    }

    public function init()
    {
        $this->brand_dict = new brand_dict();
        $this->category_dict = new category_dict();
        $this->name_dict = new words();
        $this->jingle_dict = new words();
        $this->spec_dict = new words();
        $this->goods_dict = new goods_dict();
        $this->goods_info = new goods_info();
        $this->price_cids = new valtokey();
        $this->special_dict = new special_dict();
        $this->promot_cids = [];

        $mod_goods = Model('goods');
        global $config;
        $exbrands = $config['exclude_brands'];

        if(!empty($exbrands)) {
            $items = $mod_goods->getGoodsOnlineList(['brand_id' => ['not in',$exbrands]], self::filter, '', '', false);
        } else {
            $items = $mod_goods->getGoodsOnlineList([], self::filter, '', '', false);
        }

        foreach ($items as $item)
        {
            $common_id = intval($item['goods_commonid']);
            $gc_id = intval($item['gc_id']);
            $brand_id = intval($item['brand_id']);
            $hot_id = intval($this->category_dict->hotid($gc_id));

            $this->name_dict->parase($item['goods_mobile_name'],$common_id);
            $this->jingle_dict->parase($item['goods_jingle'],$common_id);
            $spec = $this->get_spec($item['goods_spec']);
            if(!empty($spec)) {
                $this->spec_dict->parase($spec,$common_id);
                $this->goods_dict->parase($spec,$common_id);
            }

            $this->goods_dict->parase($item['goods_mobile_name'],$common_id);
            $this->goods_dict->parase($item['goods_jingle'],$common_id);

            $this->brand_dict->add_brand($brand_id,$common_id,$hot_id);
            $this->goods_dict->add_goods($common_id,$brand_id,$hot_id);
            $this->category_dict->add_hot($hot_id,$brand_id,$common_id);

            $this->goods_info->add($common_id,$item['goods_price'],$item['goods_click'],$item['goods_salenum']);
            $this->price_cids->add($common_id,intval($item['goods_price'] * 100 + 0.5));
        }
        $this->price_cids->finish();
        $this->read_promotion();
    }

    private function read_promotion()
    {
        global $config;
        $special = $config['bonus_match_goods'];
        if(empty($special)) return false;
        $special_id = intval($special['special_id']);
        if($special_id <= 0) return false;

        special_manager::instance()->special($special_id,$goods_ids);
        if(empty($goods_ids)) return false;

        $cids = [];
        foreach ($goods_ids as $gid) {
            $cid = commonid_helper::instance()->common_id($gid);
            if($cid != false) {
                $cids[] = $cid;
            }
        }
        sort($cids);
        $this->promot_cids = $cids;
        return true;
    }

    private function get_name($dict, $words)
    {
        if(empty($words)) {
            return array();
        }

        $ids = $this->get_ids($dict, $words);

        $result = [];
        foreach ($ids as $id)
        {
            $name = $dict->name($id);

            if($name != false) {
                $result[] = $name;
            }
        }

        return $result;
    }

    public function get_word($input)
    {
        $words = filter::filter($input);
        if(empty($words)) return false;

        $goods_name = $this->get_name($this->name_dict,$words);
        $jingle_name = $this->get_name($this->jingle_dict,$words);
        $spec_name = $this->get_name($this->spec_dict,$words);
        $brands = $this->get_name($this->brand_dict,$words);
        $cats = $this->get_name($this->category_dict,$words);
        $specials = $this->get_name($this->special_dict,$words);

        $result = array_merge($brands,$cats,$specials,$spec_name,$goods_name,$jingle_name);
        return $result;
    }

    public function keyword($keyword)
    {
        if(empty($keyword)) return false;
        $words = filter::filter($keyword);

        if(empty($words)) return [];

        $cids = $this->get_ids($this->goods_dict,$words);
        $brands = $this->get_ids($this->brand_dict,$words);
        foreach ($brands as $brand_id) {
            $tmp = $this->brand_dict->commons($brand_id);
            $cids = algorithm::set_union($cids,$tmp);
        }

        $cats = $this->get_ids($this->category_dict,$words);
        foreach ($cats as $hot_id) {
            $tmp = $this->category_dict->commons($hot_id);
            $cids = algorithm::set_union($cids,$tmp);
        }

        return $cids;
    }

    private function format($cids,$bids,$hotids,$spids)
    {
        if(empty($cids)) {
            return [];
        }

        $brands = [];
        if(!empty($bids))
        {
            foreach ($bids as $bid)
            {
                $title = $this->brand_dict->name($bid);
                if($title != false) {
                    $item['title'] = $title;
                    $item['data'] = $bid;
                    $item['type'] = 'brand';
                    $brands[] = $item;
                }
            }
        }

        $hots = [];
        if(!empty($hotids))
        {
            foreach ($hotids as $hot)
            {
                $title = $this->category_dict->name($hot);
                if($title != false) {
                    $item['title'] = $title;
                    $item['data'] = $hot;
                    $item['type'] = 'category';
                    $hots[] = $item;
                }
            }
        }

        return ['cids' => $cids,'brands' => $brands,'hots' => $hots,'spids' => $spids];
    }

    private function keyword_serarch($keyword, $hot, $brand)
    {
        $cids = $this->keyword($keyword);

        if($brand > 0) {
            $brand_name = brand_helper::instance()->name($brand);
        } else {
            $brand_name = '';
        }

        if($hot > 0) {
            $hot_name = category_helper::instance()->name($hot);
        } else {
            $hot_name = '';
        }

        $keywords = $keyword . $brand_name . $hot_name;
        $spids = $this->get_ids($this->special_dict,filter::filter($keywords));

        if(empty($cids)) {
            return $this->format($cids,null,null,$spids);
        }

        if($brand != 0)
        {
            $bcids = $this->brand_dict->commons($brand);
            $cids = algorithm::set_intersection($cids,$bcids);
            if(empty($cids)) {
                return $this->format($cids,null,null,$spids);
            }

            if($hot != 0)
            {
                $hcids = $this->category_dict->commons($hot);
                $cids = algorithm::set_intersection($cids,$hcids);
                if(empty($cids)) {
                    return $this->format($cids,null,null,$spids);
                } else {
                    return $this->format($cids,null,null,$spids);
                }
            }
            else {
                $hots = $this->goods_dict->hots($cids);
                return $this->format($cids,null,$hots,$spids);
            }
        }
        else
        {
            if($hot != 0)
            {
                $hcids = $this->category_dict->commons($hot);
                $cids = algorithm::set_intersection($cids, $hcids);

                if (empty($cids)) {
                    return array();
                } else {
                    $brands = $this->goods_dict->brands($cids);
                    return $this->format($cids,$brands,null,$spids);
                }
            }
            else
            {
                $brands = $this->goods_dict->brands($cids);
                return $this->format($cids,$brands,null,$spids);
            }
        }
    }

    private function hb_search($hot,$brand)
    {
        if($brand > 0) {
            $brand_name = brand_helper::instance()->name($brand);
        } else {
            $brand_name = '';
        }

        if($hot > 0) {
            $hot_name = category_helper::instance()->name($hot);
        } else {
            $hot_name = '';
        }

        $keywords = $brand_name . $hot_name;
        $spids = $this->get_ids($this->special_dict,filter::filter($keywords));

        if($brand != 0)
        {
            $cids = $this->brand_dict->commons($brand);
            if(empty($cids)) return array();

            if($hot != 0)
            {
                $hcids = $this->category_dict->commons($hot);
                $cids = algorithm::set_intersection($cids,$hcids);
                if(empty($cids)) {
                    return array();
                }
                else {
                    return $this->format($cids,null,null,$spids);
                }
            } else {
                $hots = $this->goods_dict->hots($cids);
                return $this->format($cids,null,$hots,$spids);
            }
        }
        else
        {
            if($hot != 0)
            {
                $cids = $this->category_dict->commons($hot);
                if(empty($cids)) {
                    return array();
                } else {
                    $brands = $this->goods_dict->brands($cids);
                    return $this->format($cids,$brands,null,$spids);
                }
            } else {
                return array();
            }
        }
    }

    public function get_result($params)
    {
        $keyword = $params['keyword'];
        $brand   = $params['brand_id'];
        $hot     = $params['hot_id'];

        if(empty($keyword) && $brand == 0 && $hot == 0) {
            return array();
        }

        if(isset($keyword) && !empty($keyword)) {
            $result = $this->keyword_serarch($keyword,$hot,$brand);
        } else {
            $result = $this->hb_search($hot,$brand);
        }

        if(!empty($result))
        {
            $order = $params['order'];
            $sort  = $params['sort'];

            $page_count = 1;
            if(!isset($params['page_no'])) {
                $page_no = null;
            } else {
                $page_no = $params['page_no'];
            }

            if(!isset($params['page_size'])) {
                $page_size = null;
            } else {
                $page_size = intval($params['page_size']);
                $page_size = $page_size <= 0 ? 10 : $page_size;
                $page_count = intval(count($result['cids']) / $page_size) + ((count($result['cids']) % $page_size) == 0 ? 0 : 1);
            }

            $cids = $this->sort_goods($result['cids'],$order,$sort,$page_no,$page_size);
            $result['cids'] = $cids;
            $result['page_count'] = $page_count;
        }

        return $result;
    }

    private function sort_goods($cids,$order,$sort,$page_no,$page_size)
    {
        $goods = [];
        foreach ($cids as $cid) {
            $val = $this->goods_info->goods($cid);
            $val['cid'] = $cid;
            $goods[] = $val;
        }

        if($sort == 'asc')
        {
            if($order == 'click') {
                uasort($goods,['\search\goods_info','click_asc']);
            }
            elseif($order == 'salenum') {
                uasort($goods,['\search\goods_info','salenum_asc']);
            }
            elseif($order == 'price') {
                uasort($goods,['\search\goods_info','price_asc']);
            }
            else {
                Log::record("searcher sort_goods not order param");
            }
        }
        else
        {
            if($order == 'click') {
                uasort($goods,['\search\goods_info','click_desc']);
            }
            elseif($order == 'salenum') {
                uasort($goods,['\search\goods_info','salenum_desc']);
            }
            elseif($order == 'price') {
                uasort($goods,['\search\goods_info','price_desc']);
            }
            else {
                Log::record("searcher sort_goods not order param");
            }
        }

        $tmp = [];
        foreach ($goods as $val) {
            $tmp[] = $val;
        }
        $goods = $tmp;

        $count = count($goods);
        $result = [];
        if($page_no == null || $page_size == null)
        {
            foreach ($goods as $val) {
                $result[] = $val['cid'];
            }
        }
        else
        {
            $page_no = $page_no <= 0 ? 1 : $page_no;
            for ($start = ($page_no -1) * $page_size; $page_size > 0 && $start < $count; $page_size--,$start++) {
                $result[] = $goods[$start]['cid'];
            }
        }

        return $result;
    }

    public function match_price($params)
    {
        $price = $params['price'];
        $page_no   = intval($params['page_no']);
        $page_size = intval($params['page_size']);

        if($page_no <= 0 || $page_size <= 0) {
            return false;
        }
        $start = ($page_no - 1) * $page_size;

        $result = $this->price_cids->findless(intval($price * 100 + 0.5),$start,$page_size);
        $result['page_count'] = intval($result['total'] / $page_size) + ($result['total'] % $page_size == 0 ? 0 : 1);

        return $result;
    }

    public function promote_goods($params)
    {
        $price = $params['price'];
        $count   = intval($params['count']);

        $items = $this->price_cids->findall(intval($price * 100 + 0.5));
        sort($items);

        $match_cids = $items;
        $pro_cids = algorithm::set_intersection($match_cids,$this->promot_cids);
        if(count($pro_cids) < $count) {
            $cids = $this->random_goods($match_cids,$count);
        } else {
            $cids = $this->random_goods($pro_cids,$count);
        }

        return ['total' => 1,'cids' => $cids,'total' => count($cids)];
    }

    private function random_goods($cids,$count)
    {
        $length = count($cids);
        if($length <= $count) return $cids;

        $result = [];
        for ($i = 0; $i < $count;)
        {
            $pos = mt_rand(0,$length - 1);
            $val = $cids[$pos];

            if(!in_array($val,$result)) {
                $result[] = $val;
                $i++;
            }
        }

        return $result;
    }


    private function get_ids($dict, $words)
    {
        $i = 0;
        $ids = [];
        foreach ($words as $word)
        {
            $tmp = $dict->find($word);
            if ($i == 0) {
                $ids = $tmp;
            } else {
                $ids = algorithm::set_intersection($tmp, $ids);
                if (empty($ids)) break;
            }
            ++$i;
        }
        return $ids;
    }
}