<?php


namespace Library\DB;

use Exception;
use Game\Constant\TestParams;
use RedisCluster;
use RedisClusterException;


class RedisColony implements IRedis
{
    private RedisCluster $db_cluster;

    private bool $is_pipe = false;

    private ?string $last_error = "";

    private array $cluster_nodes;

    public function createRedis(array $info): bool
    {
        try {
            $this->db_cluster = new RedisCluster(null, $info);
        } catch (RedisClusterException $e) {
            $this->last_error = $e->getMessage();
            return false;
        }
        $this->cluster_nodes = $info;
        return true;
    }

    public function startPipe():bool
    {
        $this->is_pipe = true;
       // $this->db_cluster->multi();
        return $this->checkRedisError();
    }

    public function finishPipe():bool
    {
        $this->is_pipe = false;
     //   $this->db_cluster->exec();
        return $this->checkRedisError();
    }

    public function getLastError(): string
    {
        return $this->last_error;
    }

    public function isPipe(): bool
    {
        return $this->is_pipe;
    }

    private function checkIsPipe(): bool {
        if($this->is_pipe) {
            $this->last_error = "pipe is true, operation not allowed!";
            return false;
        }
        return true;
    }

    public function evalSha(&$ret, string $sha, array $param, int $key_num): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        $ret = $this->db_cluster->evalSha($sha, $param, $key_num);
        return $this->checkRedisError();
    }

    public function pipeEvalSha(&$ret, string $sha, array $param, int $key_num): bool
    {
        $ret = $this->db_cluster->evalSha($sha, $param, $key_num);
        return $this->checkRedisError();
    }

    public function dbReadNum() {
        ++ TestParams::$dbRNum;
    }

    public function dbWriteNum() {
        ++ TestParams::$dbWNum;
    }

    public function hIncrBy(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 3) {
            $this->last_error = "hIncrBy param num error";
            return false;
        }
        $key = $param[0];
        $field = $param[1];
        $increment = $param[2];
        $ret = $this->db_cluster->hIncrBy($key, $field, $increment);
        $this->dbWriteNum();
        return $this->checkRedisError();
    }

    public function exists(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "exists param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->exists($key);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function script(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "script param num error";
            return false;
        }
        $command = $param[0];
        $scriptInfo = $param[1];
        foreach ($this->cluster_nodes as $node) {
            $nodeParams = explode(":", $node);
            $nodeParams[1] = intval($nodeParams[1]);
            try {
                $ret = $this->db_cluster->script($nodeParams, $command, $scriptInfo);
            } catch (Exception $e) {
                $this->last_error = $e->getMessage();
                return false;
            }
            $this->last_error = $this->db_cluster->getLastError();
            if ($this->last_error != null) {
                return false;
            }
        }
        return $this->checkRedisError();
    }

    public function del(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "del param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->del($key);
        $this->dbWriteNum();
        return $this->checkRedisError();
    }

    public function keys(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        $this->last_error = "keys do not use " . json_encode($param);
        $ret = $this->last_error;
        return false;
    }

    public function set(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "set param num error";
            return false;
        }
        $key = $param[0];
        $val = $param[1];
        $ret = $this->db_cluster->set($key, $val);
        $this->dbWriteNum();
        return $this->checkRedisError();
    }

    public function get(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "get param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->get($key);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function hExists(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "hExists param num error";
            return false;
        }
        $key = $param[0];
        $hashKey = $param[1];
        $ret = $this->db_cluster->hExists($key, $hashKey);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function hGet(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "hGet param num error";
            return false;
        }
        $key = $param[0];
        $hashKey = $param[1];
        $ret = $this->db_cluster->hGet($key, $hashKey);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function hMSet(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "hGet param num error";
            return false;
        }
        $key = $param[0];
        $hashData = $param[1];
        $ret = $this->db_cluster->hMSet($key, $hashData);
        $this->dbWriteNum();
        return $this->checkRedisError();
    }

    public function hMGet(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "hMGet param num error";
            return false;
        }
        $key = $param[0];
        $hashData = $param[1];
        $result = $this->db_cluster->hMGet($key, $hashData);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function expire(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "expire param num error";
            return false;
        }
        $key = $param[0];
        $time = $param[1];
        $result = $this->db_cluster->expire($key, $time);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function hDel(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "hDel param num error";
            return false;
        }
        $key = $param[0];
        $hashData = $param[1];
        $ret = $this->db_cluster->hDel($key, ...$hashData);
        $this->dbWriteNum();
        return $this->checkRedisError();
    }

    public function hGetAll(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "hGetAll param num error";
            return false;
        }
        $key = $param[0];
        $result = $this->db_cluster->hGetAll($key);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function hLen(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "hLen param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->hLen($key);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function zScore(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "zScore param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $ret = $this->db_cluster->zScore($key, $term);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function BRPop(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "BRPop param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->brPop($key, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRange(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 4) {
            $this->last_error = "zRange param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $type = $param[3];
        $result = $this->db_cluster->zRange($key, $start, $stop, $type);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRevRange(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 4) {
            $this->last_error = "zRevRange param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $type = $param[3];
        $result = $this->db_cluster->zRevRange($key, $start, $stop, $type);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRangeByScore(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 4) {
            $this->last_error = "zRangeByScore param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $type = $param[3];
        $result = $this->db_cluster->zRangeByScore($key, $start, $stop, array('withscores' => $type));
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRevRangeByScore(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 4) {
            $this->last_error = "zRevRangeByScore param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $type = $param[3];
        $result = $this->db_cluster->zRevRangeByScore($key, $start, $stop, array('withscores' => $type));
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRemRangeByScore(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 3) {
            $this->last_error = "zRemRangeByScore param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $result = $this->db_cluster->zRemRangeByScore($key, $start, $stop);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zAdd(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 3) {
            $this->last_error = "zAdd param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $score = $param[2];
        $result = $this->db_cluster->zAdd($key, $score, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function sAdd(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "sAdd param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->sAdd($key, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function sRem(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "sRem param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->sRem($key, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zRem(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "zRem param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->zRem($key, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function rPush(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "rPush param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->rPush($key, $term);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function lRange(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 3) {
            $this->last_error = "lRange param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $result = $this->db_cluster->lRange($key, $start, $stop);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function lTrim(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 3) {
            $this->last_error = "lTrim param num error";
            return false;
        }
        $key = $param[0];
        $start = $param[1];
        $stop = $param[2];
        $result = $this->db_cluster->lTrim($key, $start, $stop);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function lPop(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "lPop param num error";
            return false;
        }
        $key = $param[0];
        $result = $this->db_cluster->lPop($key);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function BLPop(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "BLPop param num error";
            return false;
        }
        $key = $param[0];
        $time = $param[1];
        $result = $this->db_cluster->blPop($key, $time);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function lRem(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "lRem param num error";
            return false;
        }
        $key = $param[0];
        $term = $param[1];
        $result = $this->db_cluster->lRem($key, $term, 0);
        $this->dbWriteNum();
        return $this->dealRedisResult($ret, $result);
    }

    public function zCard(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "zCard param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->zCard($key);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function sRandMember(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 2) {
            $this->last_error = "sRandMember param num error";
            return false;
        }
        $key = $param[0];
        $count = $param[1];
        $ret = $this->db_cluster->sRandMember($key, $count);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function sCard(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "sCard param num error";
            return false;
        }
        $key = $param[0];
        $ret = $this->db_cluster->sCard($key);
        $this->dbReadNum();
        return $this->checkRedisError();
    }

    public function sMembers(&$ret, array $param): bool
    {
        if(!$this->checkIsPipe()) {
            return false;
        }
        if (count($param) != 1) {
            $this->last_error = "sMembers param num error";
            return false;
        }
        $key = $param[0];
        $result = $this->db_cluster->sMembers($key);
        $this->dbReadNum();
        return $this->dealRedisResult($ret, $result);
    }


    public function ExistError(): bool
    {
        if ($this->db_cluster == null) {
            $this->last_error = "redis cluster null";
            return true;
        }
        return false;
    }

    private function checkRedisError(): bool
    {
        $this->last_error = $this->db_cluster->getLastError();
        if ($this->last_error == null) {
            return true;
        }
        return false;
    }

    private function dealRedisResult(&$ret, $result): bool
    {
        $this->last_error = $this->db_cluster->getLastError();
        if ($this->last_error != null) {
            return false;
        }
        if (is_array($result)) {
            $ret = json_encode($result);
        } else {
            $ret = $result;
        }
        return true;
    }
}