<?php


/**
 * Клас  'type_Key' - Ключ към ред от MVC модел
 *
 *
 * @category  ef
 * @package   type
 *
 * @author    Milen Georgiev <milen@download.bg> и Yusein Yuseinov <yyuseinov@gmail.com>
 * @copyright 2006 - 2015 Experta OOD
 * @license   GPL 3
 *
 * @since     v 0.1
 */
class type_Key extends type_Int
{
    /**
     * Клас за <td> елемент, който показва данни от този тип
     */
    public $tdClass = '';
    
    
    /**
     * Хендлър на класа
     *
     * @var string
     */
    public $handler;
    
    
    /**
     * Параметър определящ максималната широчина на полето
     */
    public $maxFieldSize = 0;
    
    
    /**
     * Дали да се подготвят SelectOpt
     */
    protected $prepareSelOpt = true;
    
    
    /**
     * Името на selectOpt
     */
    public $selectOpt = 'SelectOpt';
    
    
    /**
     * Инициализиране на типа
     */
    public function init($params = array())
    {
        parent::init($params);
    }
    
    
    /**
     * Конвертира стойността от вербална към (int) - ключ към core_Interfaces
     */
    public function toVerbal_($value)
    {
        if ($value === null || $value === '') {
            
            return;
        }
        
        if ($this->params['mvc']) {
            $mvc = &cls::get($this->params['mvc']);
            
            if (($part = $this->getSelectFld()) && $part != '*') {
                $rec = $this->fetchVal($value);
                
                if (!$rec && $value == 0) {
                    
                    return;
                }
                
                if (!$rec) {
                    
                    return '??????????????';
                }
                
                $v = $mvc->getVerbal($rec, $part);
                
                // Ако е указано - правим превод
                if ($this->params['translate']) {
                    $v = tr($v);
                }
                
                if (isset($this->params['makeLink'])) {
                    if (method_exists($mvc, 'getSingleUrlArray')) {
                        $v = ht::createLink($v, $mvc->getSingleUrlArray($rec->id), false, "ef_icon={$mvc->singleIcon}");
                    }
                }
                
                return $v;
            }
            if ($this->params['title']) {
                $field = $this->params['title'];
                $value = $mvc->fetch($value)->{$field};
                
                if (!$value) {
                    
                    return '??????????????';
                }
                
                $value = $mvc->fields[$field]->type->toVerbal($value);
            } else {
                if (isset($this->params['makeLink'])) {
                    if (method_exists($mvc, 'getHyperlink')) {
                        
                        return $mvc->getHyperlink($value, true);
                    }
                }
                
                $value = $mvc->getTitleById($value);
            }
        }
        
        // Ако е указано - правим превод
        if ($this->params['translate']) {
            $value = tr($value);
        }
        
        return $value;
    }
    
    
    /**
     * Връща вътрешното представяне на вербалната стойност
     */
    public function fromVerbal_($value)
    {
        if (empty($value)) {
            
            return;
        }
        
        $key = self::getKeyFromTitle($value);
        
        $oValue = $value;
        
        if (!isset($key)) {
            $mvc = &cls::get($this->params['mvc']);
            
            $maxSuggestions = $this->getMaxSuggestions();
            
            $options = $this->options;
            
            if (!$this->handler) {
                $this->handler = md5(implode(',', array_keys($this->options)) . '|' . core_Lg::getCurrent());
            }
            
            $selOptCache = unserialize(core_Cache::get($this->selectOpt, $this->handler));
            
            if ($selOptCache === false) {
                $options = $this->prepareOptions();
                $selOptCache = unserialize(core_Cache::get($this->selectOpt, $this->handler));
            }
            
            if (($field = $this->getSelectFld()) && (!countR($options))) {
                $options = $this->prepareOptions();
            }
            
            $value = null;
            
            if (($selOptCache !== false) && countR((array) $selOptCache)) {
                foreach ((array) $selOptCache as $id => $titleArr) {
                    if ($value == $titleArr['title']) {
                        $value = $id;
                        break;
                    }
                }
            }
        } else {
            $value = $this->prepareKey($key);
        }
        
        $rec = $this->fetchVal($value);
        
        if (!$rec) {
            if (($this->params['allowEmpty']) && ($oValue == ' ')) {
                
                return $value;
            }
            
            Mode::setPermanent('keyStopAutocomplete', true);
            
            $this->error = 'Несъществуващ обект';
            
            return false;
        }
        
        return $value;
    }
    
    
    /**
     *
     *
     * @param string|int|NULL $key
     *
     * @return string
     */
    public function prepareKey($key)
    {
        // Само числа
        $key = (int) $key;
        
        return $key;
    }
    
    
    /**
     * Инициализиране на типа
     */
    protected function getSelectFld()
    {
        if ($this->params['selectBg'] && core_Lg::getCurrent() == 'bg') {
            
            return $this->params['selectBg'];
        }
        
        return $this->params['select'];
    }
    
    
    /**
     *
     *
     * @param int $value
     *
     * @return object
     */
    protected function fetchVal(&$value)
    {
        $mvc = &cls::get($this->params['mvc']);
        
        $rec = $mvc->fetch((int) $value);
        
        return $rec;
    }
    
    
    /**
     * Помощна функция, която може да извика fetchVal
     * 
     * @param int $value
     *
     * @return object
     */
    public function getRecForVal(&$value)
    {
        
        return $this->fetchVal($value);
    }
    
    
    /**
     * Връща възможните стойности за ключа
     *
     * @param string $value
     *
     * @return array
     */
    public function getAllowedKeyVal($id)
    {
        return array($id => $id);
    }
    
    
    /**
     * Подготвя масив с опциите
     */
    public function prepareOptions($value = null)
    {
        Mode::push('text', 'plain');
        
        Debug::startTimer('prepareOPT ' . $this->params['mvc']);
        
        // Ако опциите вече са генерирани - не ги подготвяме отново
        if (!is_array($this->options) || !countR($this->options)) {
            $mvc = cls::get($this->params['mvc']);
            
            if ($this->getSelectFld() == '*') {
                $field = null;
            } else {
                $field = $this->getSelectFld();
            }
            
            $where = '';
            
            if ($this->params['where']) {
                $where = $this->params['where'];
            }
            
            // Ако е зададено поле group='sysId'
            if ($this->params['group']) {
                $fWhere = $this->filterByGroup($mvc);
                
                if ($fWhere) {
                    $where = empty($where) ? $fWhere : "({$where}) AND ({$fWhere})" ;
                }
            }
            
            $options = array();
            
            $mvc->invoke('BeforePrepareKeyOptions', array(&$options, $this, $where));
            
            if (!countR($options)) {
                if (!is_array($this->options)) {
                    $keyIndex = $this->getKeyField();
                    
                    $arrForSelect = (array) $mvc->makeArray4select($field, $where, $keyIndex, $this->params['orderBy']);
                    
                    if ($value && !isset($arrForSelect[$value]) && get_class($this) == 'type_Key') {
                        $arrForSelect[$value] = $mvc->gettitleById($value, false);
                    }
                    
                    foreach ($arrForSelect as $id => $v) {
                        $options[$id] = $v;
                    }
                    $this->handler = md5($field . $where . $this->params['mvc'] . $keyIndex . '|' . core_Lg::getCurrent());
                } else {
                    foreach ($this->options as $id => $v) {
                        $options[$id] = $v;
                    }
                }
            }

            // Изкарваме най-отпред предпочитаните опции
            if(isset($this->params['preferred']) && is_array($this->params['preferred'])) {
                $prefArr = array();
                foreach($this->params['preferred'] as $idPref) {
                    if(isset($options[$idPref])) {
                        $optPref = $options[$idPref];
                        unset($options[$idPref]);
                        $prefArr[$idPref] = $optPref;
                    }
                }
                $options = $prefArr + $options;
            }
            
            // Правим титлите на опциите да са уникални и изчисляваме най-дългото заглавие
            if (is_array($options)) {
                $titles = array();
                
                $i = 1;
                
                foreach ($options as $id => $title) {
                    if (is_object($title)) {
                        continue;
                    }
                    
                    if ($titles[$title]) {
                        $title = self::getUniqTitle($title, $id);
                    }
                    $titles[$title] = true;
                    $options[$id] = $title;
                    
                    list($title1, ) = explode('||', $title);
                    $this->maxFieldSize = max($this->maxFieldSize, mb_strlen($title1));
                    
                    if ($i++ > 100) {
                        break;
                    }
                }
            }
            
            $this->options = &$options;
            
            $mvc->invoke('AfterPrepareKeyOptions', array(&$this->options, $this, $where));
        } else {
            $options = $this->options;
        }
        
        if (!$this->handler) {
            $this->handler = md5(implode(',', array_keys($this->options)) . '|' . core_Lg::getCurrent());
        }
        
        if ($optSz = core_Cache::get($this->selectOpt, $this->handler, 60, array($this->params['mvc']))) {
            $cacheOpt = unserialize($optSz);
            $options = array();
            foreach ($cacheOpt as $id => $obj) {
                $options[$id] = $obj['title'];
            }
        } else {
            $this->prepareSelectOpt($options);
        }
        
        Debug::stopTimer('prepareOPT ' . $this->params['mvc']);
        
        Mode::pop('text');
        
        if ($this->params['translate']) {
            $options = self::translateOptions($options);
        }
        
        $this->options = $options;
        
        return $options;
    }
    
    
    /**
     * Подготвя опциите за селект, ако условията са изпълнени
     *
     * @param array $options
     */
    protected function prepareSelectOpt(&$options)
    {
        if (!$this->prepareSelOpt) {
            
            return ;
        }
        
        $maxSuggestions = $this->getMaxSuggestions();
        
        // Ако трябва да показваме combo-box
        if (countR($options) <= $maxSuggestions) {
            
            return ;
        }
        
        if (is_object($options[''])) {
            $options['']->title = '';
        }
        
        $cacheOpt = array();
        
        $titles = array();
        
        $isInstalled = core_Packs::isInstalled('select2');
        
        foreach ($options as $key => $v) {
            $title = self::getOptionTitle($v);
            
            // Ако вече е добавено id-то след края на текста, да не се добавя повторвно
            if (!self::haveId($title, $key)) {
                if (!$isInstalled) {
                    $title = self::getUniqTitle($title, $key);
                }
                
                if (is_object($v)) {
                    $v->title = $title;
                    $options[$key] = $v;
                } else {
                    $options[$key] = $title;
                }
            }
            
            if ($titles[$title]) {
                $title = self::getUniqTitle($title, $key);
            }
            
            $titles[$title] = true;
            
            $vNorm = trim(preg_replace('/[^a-z0-9\*]+/', ' ', strtolower(str::utf2ascii($title))));
            
            if (is_object($v)) {
                $v->title = $title;
            } else {
                $v = $title;
            }
            
            $cacheOpt[$key]['title'] = $v;
            $cacheOpt[$key]['id'] = $vNorm;
        }
        
        core_Cache::set($this->selectOpt, $this->handler, serialize($cacheOpt), 60, array($this->params['mvc']));
    }
    
    
    /**
     *
     *
     * @return string
     */
    protected function getKeyField()
    {
        $keyField = 'id';
        
        if (!empty($this->params['key'])) {
            $keyField = $this->params['key'];
        }
        
        return $keyField;
    }
    
    
    /**
     * Връща броя на максимално допуситимите опции за показване
     *
     * @return int
     */
    public function getMaxSuggestions()
    {
        $conf = core_Packs::getConfig('core');
        
        $maxSuggestions = $this->params['maxSuggestions'] ? $this->params['maxSuggestions'] : $conf->TYPE_KEY_MAX_SUGGESTIONS;
        
        return $maxSuggestions;
    }
    
    
    /**
     *
     *
     * @param string $title
     * @param int    $id
     *
     * @return string
     */
    protected static function getUniqTitle($title, $id)
    {
        if ($id) {
            
            return $title . " ({$id})";
        }
        
        return $title;
    }
    
    
    /**
     * Проверява дали има 'id' в края на стринга
     *
     * @param string $title
     * @param int    $id
     *
     * @return NULL|bool
     */
    protected static function haveId($title, $id)
    {
        $nKey = " ({$id})";
        $pos = mb_strrpos($title, $nKey);
        
        if ($pos === false) {
            
            return false;
        }
        
        $len = mb_strlen($title);
        $keyLen = mb_strlen($nKey);
        
        if (($pos + $keyLen) == $len) {
            
            return true;
        }
    }
    
    
    /**
     * Опитва се да извлече ключа от текста
     *
     * @param string $title
     *
     * return integer|NULL
     */
    protected static function getKeyFromTitle($title)
    {
        if (is_numeric($title) || !isset($title)) {
            
            return $title;
        }
        
        $len = mb_strlen($title);
        
        $lastCloseBracketPos = mb_strrpos($title, ')');
        
        if (!$lastCloseBracketPos) {
            
            return $title;
        }
        
        if ($len != ($lastCloseBracketPos + 1)) {
            
            return $title;
        }
        
        $lastOpenBracketPos = mb_strrpos($title, ' (');
        
        if (!$lastOpenBracketPos) {
            
            return $title;
        }
        
        $lastOpenBracketPos += 2;
        
        $key = mb_substr($title, $lastOpenBracketPos, $lastCloseBracketPos - $lastOpenBracketPos);
        
        return $key;
    }
    
    
    /**
     *
     *
     * @param string $val
     *
     * @return string
     */
    protected static function normalizeKey($val)
    {
        $val = plg_Search::normalizeText($val);
        
        return $val;
    }
    
    
    /**
     * Рендира HTML поле за въвеждане на данни чрез форма
     */
    public function renderInput_($name, $value = '', &$attr = array())
    {
        expect($this->params['mvc']);
        $selOpt = array();
        $mvc = cls::get($this->params['mvc']);
        
        if (!$value) {
            $value = $attr['value'];
        }
        
        $options = $this->options;
        
        if (!is_array($options) || !countR($options)) {
            $options = $this->prepareOptions($value);
        }
        
        if (($div = $this->params['groupByDiv'])) {
            $options = ht::groupOptions($options, $div);
        }

        if ($this->getSelectFld() || countR($options)) {
            $optionsCnt = countR($options);
            
            if ($this->params['allowEmpty']) {
                $placeHolder = array('' => (object) array('title' => $attr['placeholder'] ? $attr['placeholder'] : ' ', 'attr' =>
                    array('style' => 'color:#777;')));
                $options = arr::combine($placeHolder, $options);
            } elseif ($attr['placeholder'] && $optionsCnt != 1) {
                $placeHolder = array('' => (object) array('title' => $attr['placeholder'], 'attr' =>
                    array('style' => 'color:#777;', 'disabled' => 'disabled')));
                $options = arr::combine($placeHolder, $options);
            }
            
            $maxSuggestions = $this->getMaxSuggestions();
            
            parent::setFieldWidth($attr);

            $maxRadio = $this->params['maxRadio'];
            if (!$attr['_isRefresh']) {
                if (!strlen($maxRadio) && $maxRadio !== 0 && $maxRadio !== '0' && !$this->params['isHorizontal']) {
                    if(arr::isOptionsTotalLenBellowAllowed($options)){
                        $maxRadio = 4;
                        $this->params['select2MinItems'] = 10000;
                        $this->params['columns'] =  ($optionsCnt > 3) ?  4 : 3;
                    }
                }
            }

            if (($optionsCnt > $maxSuggestions) && (!core_Packs::isInstalled('select2'))) {
                $options = $this->prepareOptions($value);
                
                if (Mode::is('keyStopAutocomplete')) {
                    $this->params['autocomplete'] = 'off';
                }
                
                if ($this->params['autocomplete']) {
                    $attr['autocomplete'] = $this->params['autocomplete'];
                }
                
                if (!$this->handler) {
                    $this->handler = md5(implode(',', array_keys($this->options)) . '|' . core_Lg::getCurrent());
                }
                
                $selOptCache = (array) unserialize(core_Cache::get($this->selectOpt, $this->handler));
                
                if ($this->suggestions) {
                    $suggestions = $this->suggestions;
                } else {
                    $suggestions = array_slice($options, 0, $maxSuggestions, true);
                }
                
                foreach ((array) $suggestions as $key => $v) {
                    $key = self::getOptionTitle($v);
                    
                    $selOpt[trim($key)] = $v;
                }
                
                $this->options = $selOpt;
                
                $attr['ajaxAutoRefreshOptions'] = '{Ctr:"type_Key"' .
                ", Act:\"ajax_GetOptions\", hnd:\"{$this->handler}\", maxSugg:\"{$maxSuggestions}\", ajax_mode:1}";
                
                // Ако е id определяме стойността която ще се показва, като вербализираме
                // Иначе - запазваме предходния вариянт. Работил ли е някога?
                $setVal = self::getOptionTitle($selOptCache[$value]['title']);
                
                if (!$setVal && is_numeric($value)) {
                    $setVal = $this->toVerbal($value);
                }
                
                // Най-отгоре да е стойността по подразбиране
                unset($selOpt[$setVal]);
                $selOpt = array($setVal => $setVal) + $selOpt;
                
                if ($selOpt['']) {
                    $selOpt = array('' => $selOpt['']) + $selOpt;
                }
                
                $tpl = ht::createCombo($name, $setVal, $attr, $selOpt);
            } else {
                $optionsCnt = countR($options);
                
                if (($optionsCnt == 0 || ($optionsCnt == 1 && isset($options['']) && $this->params['mandatory']))) {
                    $msg = tr('Липсва избор за');
                    
                    $title = tr($mvc->title);
                    
                    if ($mvc->haveRightFor('list')) {
                        $url = array($mvc, 'list');
                        $title = ht::createLink($title, $url, false, 'style=font-weight:bold;');
                    }
                    
                    $cssClass = $this->params['mandatory'] ? 'inputLackOfChoiceMandatory' : 'inputLackOfChoice';

                    $tpl = new ET("<span class='{$cssClass}'>[#1#] [#2#]</div>", $msg, $title);
                } else {
                    // ако ще се рендират опциите като радио-бутони маха се празната опция
                    if(isset($maxRadio) && $optionsCnt <= $maxRadio){
                        if($this->params['allowEmpty']){
                            if(isset($options['']) && (empty($options['']) || (is_object($options['']) && empty(trim($options['']->title)))) && countR($options) >= 2){
                                unset($options['']);
                            }
                        }
                    }

                    // Ако полето е задължително и имаме само една не-празна опция - тя да е по подразбиране
                    if ($this->params['mandatory'] && $optionsCnt == 2 && empty($value) && $options[key($options)] === '') {
                        list($o1, $o2) = array_keys($options);
                        if (!empty($o2)) {
                            $value = $o2;
                        } elseif (!empty($o1)) {
                            $value = $o1;
                        }
                    }

                    $tpl = ht::createSmartSelect($options, $name, $value, $attr, $maxRadio, $this->params['maxColumns'], $this->params['columns']);
                }
            }
        } else {
            error(null, $this);
        }
        
        return $tpl;
    }
    
    
    /**
     * Връща списък е елементи <option> при ajax заявка
     */
    public function act_ajax_GetOptions()
    {
        // Приключваме, ако няма заявка за търсене
        $hnd = Request::get('hnd');
        
        $q = Request::get('q');
        
        $q = plg_Search::normalizeText($q);
        
        $q = '/[ \"\'\(\[\-\s]' . str_replace(' ', '.* ', $q) . '/';
        
        if (!$hnd) {
            
            return array(
                'error' => 'Липсват допълнителни опции'
            );
        }
        
        if (!($maxSuggestions = Request::get('maxSugg', 'int'))) {
            $maxSuggestions = $this->getMaxSuggestions();
        }
        
        $options = unserialize(core_Cache::get($this->selectOpt, $hnd));
        
        $select = new ET('<option value="">&nbsp;</option>');
        
        $cnt = 0;
        
        if (is_array($options)) {
            $openGroup = false;
            
            foreach ($options as $key => $titleArr) {
                $title = $titleArr['title'];
                $id = $titleArr['id'];
                
                $attr = array();
                
                if ($key == '') {
                    continue;
                }
                
                if (!isset($title->group) && $q && (!preg_match($q, ' ' . $id))) {
                    continue;
                }
                
                $element = 'option';
                
                if (is_object($title)) {
                    if ($title->group) {
                        if ($openGroup) {
                            // затваряме групата
                            $select->append('</optgroup>');
                        }
                        $element = 'optgroup';
                        $attr = $title->attr;
                        $attr['label'] = $title->title;
                        $newGroup = ht::createElement($element, $attr);
                        continue;
                    }
                    if ($newGroup) {
                        $select->append($newGroup);
                        $newGroup = null;
                        $openGroup = true;
                    }
                    $attr = $title->attr;
                    $title = $title->title;
                } else {
                    if ($newGroup) {
                        $select->append($newGroup);
                        $newGroup = null;
                        $openGroup = true;
                    }
                }
                
                $attr['value'] = self::getOptionTitle($title);
                
                $option = ht::createElement($element, $attr, $title);
                $select->append($option);
                
                if (!is_object($title)) {
                    $cnt++;
                }
                
                if ($cnt >= $maxSuggestions) {
                    break;
                }
            }
        }
        
        $res = array(
            'content' => $select->getContent()
        );
        
        header('Content-Type: application/json');
        
        echo json_encode($res);
        
        die;
    }
    
    
    /**
     * Добавя филтриране на резултатите по група зададена с нейно sysId
     *
     * @param core_Mvc $mvc - мениджър на ключа
     *
     * @return string - 'where' клауза за филтриране по Ид на група
     */
    private function filterByGroup(core_Mvc $mvc)
    {
        // Ако не е посочено 'groupsField', приемаме че то се казва "groups"
        setIfNot($mvc->groupsField, 'groups');
        $fieldParams = $mvc->getField($mvc->groupsField)->type->params;
        $GroupManager = cls::get($fieldParams['mvc']);
        
        // Проверяваме дали мениджъра има поле sysId или systemId
        $groupQuery = $GroupManager->getQuery();
        
        if ($sysIdField = $GroupManager->fields['sysId']) {
            $sysIdField = 'sysId';
        } elseif ($GroupManager->fields['systemId']) {
            $sysIdField = 'systemId';
        }
        
        // Очакваме мениджъра да поддържа или sysId или systemId
        expect($sysIdField, 'Мениджъра не поддържа sysId-та');
        $groupQuery->where("#{$sysIdField} = '{$this->params['group']}'");
        
        // Очакваме да има запис зад това sysId
        expect($groupRec = $groupQuery->fetch(), 'Няма група с това sysId = ' . $this->params['group']);
        
        // Модифицираме заявката като добавяме филтриране по група, която
        // е зададена с нейно Id - отговарящо на посоченото systemId
        return "#{$mvc->groupsField} LIKE '%|{$groupRec->id}|%'";
    }
    
    
    /**
     * Връща заглавието на опцията, независимо от това дали тя е стринг или обект
     */
    public static function getOptionTitle($v)
    {
        if ($v == null || is_string($v)) {
            $title = $v;
        } else {
            $title = $v->title;
        }
        
        return $title;
    }
    
    
    /**
     * Транслитерира масив с опции, като запазва възможността някои от тях да са обекти
     */
    public static function transliterateOptions($options)
    {
        foreach ($options as &$opt) {
            if (is_object($opt)) {
                $opt->title = transliterate($opt->title);
            } else {
                $opt = transliterate($opt);
            }
        }
        
        return $options;
    }
    
    
    /**
     * Превежда масив с опции, като запазва възможността някои от тях да са обекти
     */
    public static function translateOptions($options)
    {
        foreach ($options as &$opt) {
            if (is_object($opt)) {
                $opt->title = tr($opt->title);
            } else {
                $opt = tr($opt);
            }
        }
        
        return $options;
    }
}
