<?php

namespace dominion\cache;

use Yii;
use Redis;
use yii\console\Exception;

class RedisCache
{
    protected static $redis;
    /**
     * используется для отключения кеширования
     * например при реиндексе или дебаге
     * @var type
     */
    protected static $active = true;
    /**
     * время жизни кеша в секундах
     * @var int
     */
    protected static $livetime = 300;
    /**
     * начальная директория для кеша в редисе
     * @var type
     */
    protected static $prefix = '';

    /**
     * Время чкрез ктр удалить кешь
     * @var int
     */
    protected static $keysDeleteTime = 30;

    protected static $setDeleteKey = false;

    public static function setActive($active)
    {
        self::$active = $active;
    }

    public static function getActive()
    {
        return self::$active;
    }

    public static function setDeleteKeyValue($setDeleteKey)
    {
        self::$setDeleteKey = $setDeleteKey;
    }

    public static function connection($reConnect = false)
    {
        if(!self::$redis || $reConnect)
        {
            if(empty(Yii::$app->params['redis'])) {
                throw new Exception('Отсутствует конфигурация для redis');
            }
            if(empty(Yii::$app->params['redis']['host'])) {
                throw new Exception('Отсутствует конфигурация для redis host');
            }
            if(empty(Yii::$app->params['redis']['port'])) {
                throw new Exception('Отсутствует конфигурация для redis port');
            }
            if(empty(Yii::$app->params['redis']['prefix'])) {
                throw new Exception('Отсутствует конфигурация для redis prefix');
            }

            $redis = new Redis;
            $redis->connect(Yii::$app->params['redis']['host'], Yii::$app->params['redis']['port']);
            $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
            $redis->setOption(Redis::OPT_PREFIX, Yii::$app->params['redis']['prefix']);
            $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);

            self::$redis = $redis;
            self::$livetime = isset(Yii::$app->params['redis']['livetime']) ? (int)Yii::$app->params['redis']['livetime'] : self::$livetime;
            self::$prefix = Yii::$app->params['redis']['prefix'];
        }
        return self::$redis;
    }

    public static function hdel($key, $value, $connect)
    {
        if(self::$setDeleteKey)
        {
            self::addDeleteKey($key, $value);
        }
        else
        {
            $prefix = self::$prefix;
            $raw = "HDEL {$prefix}{$key} {$value}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            $connect->hdel($key, $value);
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
    }
    public static function del($key, $realDelete = false)
    {
        if(self::$setDeleteKey && !$realDelete)
        {
            self::addDeleteKey($key);
        }
        else
        {
            $prefix = self::$prefix;
            $raw = "DEL {$prefix}{$key}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                self::connection()->del($key);
            }
            catch (\Exception $exc)
            {
                self::connection(true)->del($key);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
    }

    public static function hset($key, $field, $value)
    {
        $result = false;
        if(self::getActive())
        {
            $prefix = self::$prefix;
            $raw = "HSET {$prefix}{$key} {$field}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                $result = self::connection()->hset($key, $field, $value);
            }
            catch (\Exception $exc)
            {
                $result = self::connection(true)->hset($key, $field, $value);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
        return $result;
    }

    public static function hsetModel($key, $field, $model)
    {
        return self::hset($key, $field, is_object($model) ? $model->attributes : null);
    }

    /**
     *
     * @param type $key
     * @param type $field
     * @param type $modelArray
     * @param type $page родительская модель (используется для пагинации)
     * @return type
     */
    public static function hsetModelArray($key, $field, $modelArray, $page = null)
    {
        $values = [];
        foreach ($modelArray as $model)
        {
            $values[] = $model->attributes;
        }
        if($page)
        {
            $values['page'] = [
                'totalCount' => $page->totalCount,
                'perPage' => $page->perPage,
                'sort' => $page->sort,
            ];
            $field .= ":page:{$page->page}:perPage:{$page->perPage}:sort:{$page->sort}";
        }
        return self::hset($key, $field, $values);
    }

    public static function hget($key, $field)
    {
        $result = false;
        if(self::getActive())
        {
            $prefix = self::$prefix;
            $raw = "HGET {$prefix}{$key} {$field}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                $result = self::connection()->hget($key, $field);
            }
            catch (\Exception $exc)
            {
                $result = self::connection(true)->hget($key, $field);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
        return $result;
    }

    public static function hgetModel($key, $field, $className)
    {
        $value = self::hget($key, $field);
        return empty($value) ? $value : (new $className($value));
    }

    public static function hgetModelArray($key, $field, $className, $page = null)
    {
        if($page)
        {
            $field .= ":page:{$page->page}:perPage:{$page->perPage}:sort:{$page->sort}";
        }
        $values = self::hget($key, $field);
        if($values === false)
        {
            return false;
        }
        $ids = [];
        foreach ($values as $keyVal => $value)
        {
            if($keyVal !== 'page' && isset($value['id']))
            {
                $ids[] = $value['id'];
            }
        }
        $output = [];
        foreach ($values as $keyVal => $value)
        {
            if($keyVal === 'page')
            {
                if($page)
                {
                    $page->setMeta($value['totalCount'], $value['perPage']);
                    $page->sort = $value['sort'];
                }
            }
            else
            {
                $model = new $className($value);
                if(isset($model->ids))
                {
                    $model->ids = $ids;
                }
                $output[] = $model;
            }
        }
        return $output;
    }

    public static function delete($key, array $patterns)
    {
        if(self::getActive())
        {
            $prefix = self::$prefix;
            $connect = self::connection();
            foreach($patterns as $pattern)
            {
                $cursor = NULL;
                foreach ($patterns as $pattern)
                {
                    $arKeys = [];
                    $raw = "HSCAN {$prefix}{$key} {$cursor} MATCH {$pattern}";
                    Yii::beginProfile($raw, 'yii\db\Command::query');
                    while($values = $connect->hscan($key, $cursor, $pattern))
                    {
                        foreach($values as $vkey => $value)
                        {
                            $arKeys[] = $vkey;
                        }
                    }
                    Yii::endProfile($raw, 'yii\db\Command::query');
                    foreach($arKeys as $value)
                    {
                        self::hdel($key, $value, $connect);
                    }
                }
            }
        }
    }

    /**
     * удаление одного элемента
     * @param type $key
     * @return type
     */
    public static function deleteAll($key, $realDelete = false)
    {
        if(self::getActive())
        {
            self::del($key, $realDelete);
        }
    }

    public static function getKeyTypes()
    {
        $prefix = self::$prefix;
        $raw = "KEYS {$prefix}*";
        Yii::beginProfile($raw, 'yii\db\Command::query');
        try
        {
            $result = self::connection()->keys('*');
        }
        catch (\Exception $exc)
        {
            $result = self::connection(true)->keys('*');
        }
        Yii::endProfile($raw, 'yii\db\Command::query');
        $output = ['*'];
        foreach ($result as $val)
        {
            $arVal = explode(':', $val);
            if(isset($arVal[1]) && !in_array($arVal[1], $output))
            {
                $output[] = $arVal[1];
            }
        }
        sort($output);
        return $output;
    }

    /**
     * Удаление всех элементов по шаблоку
     * @param type $type
     * @return boolean
     */
    public static function deleteType($type)
    {
        if(self::getActive())
        {
            self::deleteAll($type);
            $type = $type == "*" ? "*" : "{$type}:*";
            $prefix = self::$prefix;
            $raw = "KEYS {$prefix}{$type}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                $result = self::connection()->keys($type);
            }
            catch (\Exception $exc)
            {
                $result = self::connection(true)->keys($type);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
            foreach ($result as $val)
            {
                self::deleteAll(str_replace($prefix, '', $val));
            }
        }
        return true;
    }

    public static function set($key, $value, $options = null)
    {
        $result = false;
        if(self::getActive())
        {
            if($options === true)
            {
                $options = ['EX' => self::$livetime];
            }
            elseif(is_int($options))
            {
                $options = ['EX' => $options];
            }
            $addStr = '';
            foreach((array)$options as $okey => $oval)
            {
                $addStr .= "{$okey} {$oval}";
            }
            $prefix = self::$prefix;
            $json = json_encode($value);
            $raw = "SET {$prefix}{$key} {$json} {$addStr}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                $result = self::connection()->set($key, $json, $options);
            }
            catch (\Exception $exc)
            {
                $result = self::connection(true)->set($key, $json, $options);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
        return $result;
    }

    public static function setModel($key, $model, $options = true)
    {
        return self::set($key, is_object($model) ? $model->attributes : null, $options);
    }

    public static function setModelArray($key, $modelArray, $page = null, $options = true)
    {
        $values = [];
        foreach ($modelArray as $model)
        {
            $values[] = $model->attributes;
        }
        if($page)
        {
            $values['page'] = [
                'totalCount' => $page->totalCount,
                'perPage' => $page->perPage,
                'sort' => $page->sort,
            ];
        }
        return self::set($key, $values, $options);
    }

    public static function get(string $key)
    {
        $result = false;
        if(self::getActive())
        {
            $prefix = self::$prefix;
            $raw = "GET {$prefix}{$key}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            try
            {
                $result = self::connection()->get($key);
            }
            catch (\Exception $exc)
            {
                $result = self::connection(true)->get($key);
            }
            Yii::endProfile($raw, 'yii\db\Command::query');
        }
        return $result ? json_decode($result, true) : $result;
    }

    public static function getModel($key, $className)
    {
        $value = self::get($key);
        return empty($value) ? $value : (new $className($value));
    }

    public static function getModelArray($key, $className, $page = null)
    {
        $values = self::get($key);
        if($values === false)
        {
            return false;
        }
        $ids = [];
        foreach ($values as $keyVal => $value)
        {
            if($keyVal !== 'page' && isset($value['id']))
            {
                $ids[] = $value['id'];
            }
        }
        $output = [];
        foreach ($values as $keyVal => $value)
        {
            if($keyVal === 'page')
            {
                if($page)
                {
                    $page->setMeta($value['totalCount'], $value['perPage']);
                    $page->sort = $value['sort'];
                }
            }
            else
            {
                $model = new $className($value);
                if(isset($model->ids))
                {
                    $model->ids = $ids;
                }
                $output[] = $model;
            }
        }
        return $output;
    }

    /**
     * Доюавляем в лист удаляемый индекс
     * @param type $key
     * @param type $field
     * @return type
     */
    public static function addDeleteKey($key, $field = '-')
    {
        return self::hset('del:'.$key, $field, time() + self::$keysDeleteTime);
    }
    /**
     * Удаляет устаревший кеш
     * @return type
     */
    public static function oldCacheDelete()
    {
        $connect = self::connection();
        $prefix = self::$prefix;
        $raw = "KEYS {$prefix}del:*";
        Yii::beginProfile($raw, 'yii\db\Command::query');
        $result = $connect->keys('del:*');
        Yii::endProfile($raw, 'yii\db\Command::query');
        foreach ($result as $val)
        {
            $cursor = null;
            //$key = str_replace("{$prefix}del:", '', $val);
            $key = str_replace($prefix, '', $val);
            $pattern = '*';
            $arKeys = [];
            $raw = "HSCAN {$prefix}{$key} {$cursor} MATCH {$pattern}";
            Yii::beginProfile($raw, 'yii\db\Command::query');
            while($values = $connect->hscan($key, $cursor, $pattern))
            {
                foreach($values as $vkey => $value)
                {
                    if($value < time())
                    {
                        $arKeys[] = $vkey;
                    }
                }
            }
            $keyCache = str_replace("{$prefix}del:", '', $val);
            Yii::endProfile($raw, 'yii\db\Command::query');
            foreach($arKeys as $value)
            {
                self::$setDeleteKey = false;
                if($value == '-')
                {
                    self::del($keyCache, $connect);
                }
                else
                {
                    self::hdel($keyCache, $value, $connect);
                }
                self::hdel($key, $value, $connect);
            }
        }
    }

}