<?php


namespace ZM\Store;


use Exception;
use Swoole\Table;
use ZM\Annotation\Swoole\OnSave;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Exception\ZMException;

class LightCache
{
    /** @var Table|null */
    private static $kv_table = null;
    /** @var Table|null */
    private static $kv_lock = null;

    private static $config = [];

    public static $last_error = '';

    /**
     * @param $config
     * @return bool|mixed
     * @throws Exception
     */
    public static function init($config) {
        self::$config = $config;
        self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]);
        self::$kv_table->column("value", Table::TYPE_STRING, $config["max_strlen"]);
        self::$kv_table->column("expire", Table::TYPE_INT);
        self::$kv_table->column("data_type", Table::TYPE_STRING, 8);
        $result = self::$kv_table->create();
        self::$kv_lock = new Table($config["size"], $config["hash_conflict_proportion"]);
        $result = $result && self::$kv_lock->create();
        if ($result === true && isset($config["persistence_path"])) {
            if (file_exists($config["persistence_path"])) {
                $r = json_decode(file_get_contents($config["persistence_path"]), true);
                if ($r === null) $r = [];
                foreach ($r as $k => $v) {
                    $write = self::set($k, $v, -2);
                    Console::verbose("Writing LightCache: " . $k);
                    if ($write === false) {
                        self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
                        return false;
                    }
                }
            }
        }
        if ($result === false) {
            self::$last_error = '系统内存不足，申请失败';
        }
        return $result;
    }

    /**
     * @param string $key
     * @return null|mixed
     * @throws ZMException
     */
    public static function get(string $key) {
        if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
        self::checkExpire($key);
        $r = self::$kv_table->get($key);
        return $r === false ? null : self::parseGet($r);
    }

    /**
     * @param string $key
     * @return mixed|null
     * @throws ZMException
     */
    public static function getExpire(string $key) {
        if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
        self::checkExpire($key);
        $r = self::$kv_table->get($key, "expire");
        return $r === false ? null : $r - time();
    }

    /**
     * @param string $key
     * @param string|array|int $value
     * @param int $expire
     * @return mixed
     * @throws ZMException
     */
    public static function set(string $key, $value, int $expire = -1) {
        if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
        if (is_array($value)) {
            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
            if (strlen($value) >= self::$config["max_strlen"]) return false;
            $data_type = "json";
        } elseif (is_string($value)) {
            $data_type = "";
        } elseif (is_int($value)) {
            $data_type = "int";
        } elseif (is_bool($value)) {
            $data_type = "bool";
            $value = json_encode($value);
        } else {
            throw new ZMException("Only can set string, array and int");
        }
        try {
            return self::$kv_table->set($key, [
                "value" => $value,
                "expire" => $expire >= 0 ? $expire + time() : $expire,
                "data_type" => $data_type
            ]);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * @param string $key
     * @param $value
     * @return bool|mixed
     * @throws ZMException
     */
    public static function update(string $key, $value) {
        if (self::$kv_table === null) throw new ZMException("not initialized LightCache.");
        if (is_array($value)) {
            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
            if (strlen($value) >= self::$config["max_strlen"]) return false;
            $data_type = "json";
        } elseif (is_string($value)) {
            $data_type = "";
        } elseif (is_int($value)) {
            $data_type = "int";
        } else {
            throw new ZMException("Only can set string, array and int");
        }
        try {
            if (self::$kv_table->get($key) === false) return false;
            return self::$kv_table->set($key, [
                "value" => $value,
                "data_type" => $data_type
            ]);
        } catch (Exception $e) {
            return false;
        }
    }

    public static function getMemoryUsage() {
        return self::$kv_table->getMemorySize();
    }

    /**
     * @param string $key
     * @return bool
     * @throws Exception
     */
    public static function isset(string $key) {
        return self::get($key) !== null;
    }

    public static function unset(string $key) {
        return self::$kv_table->del($key);
    }

    public static function getAll() {
        $r = [];
        $del = [];
        foreach (self::$kv_table as $k => $v) {
            if ($v["expire"] <= time() && $v["expire"] >= 0) {
                $del[] = $k;
                continue;
            }
            $r[$k] = self::parseGet($v);
        }
        foreach ($del as $v) {
            self::unset($v);
        }
        return $r;
    }

    public static function savePersistence($only_worker = false) {

        // 下面将OnSave激活一下
        if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) {
            $dispatcher = new EventDispatcher(OnSave::class);
            $dispatcher->dispatchEvents();
        }

        if($only_worker) return;

        if (self::$kv_table === null) return;
        $r = [];
        foreach (self::$kv_table as $k => $v) {
            if ($v["expire"] === -2) {
                Console::verbose("Saving " . $k);
                $r[$k] = self::parseGet($v);
            }
        }
        if (self::$config["persistence_path"] == "") return;
        if (file_exists(self::$config["persistence_path"])) {
            $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256));
            if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!");
        }


    }

    private static function checkExpire($key) {
        if (($expire = self::$kv_table->get($key, "expire")) >= 0) {
            if ($expire <= time()) {
                self::$kv_table->del($key);
            }
        }
    }

    private static function parseGet($r) {
        switch ($r["data_type"]) {
            case "json":
            case "int":
            case "bool":
                return json_decode($r["value"], true);
            case "":
            default:
                return $r["value"];
        }
    }
}
