<?php

/**
 * EOS相关操作
 * User: ben
 * Date: 2018/9/19
 * Time: 下午2:59
 */
class EosBase extends Singleton {

    /**
     * 解锁钱包
     * @param string $name 钱包名 [0-9a-f]{12}
     * @param string $password 钱包名对应的密码
     */
    function unlockWallet($name = null, $password = null) {
        $name = $name ?: $GLOBALS['walletName'];
        $password = $password ?: $GLOBALS['walletPwd'];
        // 解锁钱包
        $cmd = "cleos wallet unlock -n {$name} --password {$password}";
        exec($cmd);
    }

    /**
     * 获取链的概要信息
     * @return mixed
     */
    static function getInfo() {
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get info";
        $json = self::execCmd($cmd);

        return json_decode($json, true);
    }

    /**
     * eos或gt币转账给用户
     * @param $from
     * @param $to
     * @param $amount
     * @param $memo
     * @param $transaction_type
     * @param $player_transaction_id
     * @return array
     */
    public function transfer($from, $to, $amount, $memo, $transaction_type, $player_transaction_id = '') {
        $type = strpos(strtolower($amount), 'eos') === false ? 'gt' : 'eos';
        if ($type == 'gt') {
            //            $from = $GLOBALS['codeGtAdmin'];
            $cmd = "cleos -u {$GLOBALS['eosUrl']} push action eosgetgtoken transfer '[ \"{$from}\", \"{$to}\", \"{$amount}\", \"{$memo}\" ]' -p {$from} -j";
        } elseif ($type == 'eos') {
            //            $from = $GLOBALS['codeAdmin'];
            $cmd = "cleos -u {$GLOBALS['eosUrl']} transfer {$from} {$to} '{$amount}' '{$memo}' -j";
        } else {
            return null;
        }

        $json = self::execCmd($cmd);
        $log = [
            'player_transaction_id' => $player_transaction_id,
            'transaction_type' => $transaction_type,
            'from' => $from,
            'to' => $to,
            'cmd' => $cmd,
            'json' => $json,
        ];

        // 记录交易，失败重试
        return self::logTrans($log);
    }

    /**
     * 当前交易的块id
     * @var int
     */
    static $last_json = '';
    static $last_trx_id = 0;

    /**
     * 记录交易，并定时检查是否失效了
     * @param $data [`transaction_id`, `player_transaction_id`, `transaction_type`, `from`, `to`, `cmd`, `transaction_state`, `json`, `retry_times`]
     * @return array
     */
    static function logTrans($data) {
        self::$last_json = $data['json'];
        $objTrans = new TableHelper('transaction_log', 'dw_eos');

        $ret = [];
        $flag = false;
        if (!$data['transaction_id']) {
            if (!$data['json']) {
                self::_fillId($data);
                $data['transaction_state'] = -1;
            } else {
                $ret = json_decode($data['json'], true);
                $data['transaction_id'] = $ret['transaction_id'];

                if ($data['transaction_id']) {
                    $flag = true;
                    $data['block_num'] = $ret['processed']['block_num'];
                } else {
                    // 没有id，就需要填充
                    self::_fillId($data);
                    $data['transaction_state'] = -1;
                }
            }
        }

        self::$last_trx_id = $data['transaction_id'];
        $data['console'] = $data['console'] ?: $ret['processed']['action_traces'][0]['console'];
        $data['create_time'] = date('Y-m-d H:i:s');

//        $objTrans->addObject($data);

        if ($flag) {
            //            return $data['transaction_id'];
            return $data;
        } else {
            return null;
        }
    }

    private static function _fillId(&$data) {
        $objTrans = new TableHelper('transaction_log', 'dw_eos');
        $count = $objTrans->getCount();
        $rand = rand(1000, 9999);
        $data['transaction_id'] = "unknown:{$count}:{$rand}";
    }

    /**
     * 获取真正的块id
     * @param $trxId
     * @param int $block_num
     *
     * @return mixed
     */
    static function getRealBlockNum($trxId, $block_num = 0) {
        if ($block_num || ENV == ENV_DEV) {
            $ret = self::getTransaction2($trxId, $block_num);
        } else {
            $ret = self::getTrxBySpark($trxId);
        }

        return $ret['block_num'];
    }

    static function getTrxBySpark($transaction_id) {
        $api_key = '37400d93f3c7eb64428585f1cd355fe8';
        $url = "https://api.eospark.com/api?module=transaction&action=get_transaction_detail_info&apikey={$api_key}&trx_id={$transaction_id}";
        $objHttp = new dwHttp();

        $data = null;
        for ($i = 1; $i <= 3; $i++) {
            $json = $objHttp->get2($url);
            $data = json_decode($json, true);
            if ($data['errno'] == 0) {
                return $data['data'];
            } else {
                sleep($i / 2);
            }
        }

        return $data;
    }

    /**
     * 获取交易详情
     * @param string $transaction_id
     * @param int $block_num_hint
     * @return string
     */
    static function getTransaction($transaction_id, $block_num_hint) {
        $api = $GLOBALS['eosUrl'] . 'v1/history/get_transaction';
        $data = ["id" => $transaction_id];

        $block_num_hint = (int)$block_num_hint;
        if ($block_num_hint) {
            $data['block_num_hint'] = $block_num_hint;
        }

        $json = null;
        for ($i = 0; $i < 4; $i++) {
            $post_json = json_encode($data);
            $json = (new dwHttp)->post2($api, $post_json, 3, 3);
            $trx = json_decode($json, true);

            if ($trx['id']) {
                return $json;
            } else {
                $data['block_num_hint']++;
            }
        }


        return $json;
    }

    /**
     * 获取账号信息
     * @param $userName
     *
     * @return string
     */
    static function getAccount($userName) {
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get account {$userName} -j";
        return self::execCmd($cmd, false);
    }

    /**
     * 获取余额
     * @param $userName
     * @param $code
     *
     * @return float
     */
    public static function getBalance($userName, $code = 'eosio.token') {
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get currency balance {$code} {$userName}";
        $result = self::execCmd($cmd);

        $balance = (float) $result;
        return $balance * 10000;
    }

    /**
     * 获取块信息
     * @param $block_num
     *
     * @return array
     */
    public static function getBlock($block_num) {
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get block {$block_num}";
        $json = self::execCmd($cmd);
        $json = str_replace("\n", '', $json);

        $info = json_decode($json, true);
        return $info;
    }

    /**
     * 获取块id
     * @param $block_num
     *
     * @return array
     */
    public static function getBlockId($block_num) {
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get block {$block_num}";
        $json = self::execCmd($cmd);

        $json = str_replace("\n", '', $json);
        $info = json_decode($json, true);

        if ($info && $info['id']) {
            $block_id = $info['id'];
        } else {
            $hex = dechex($block_num);
            $hex = sprintf("%08s", $hex);
            preg_match("/\"({$hex}[0-9a-fA-F]{56})\"/", $json, $matches);
            $block_id = $matches[1];
        }

        return $block_id;
    }

    static function execCmd($cmd, $writeLog = true) {
        self::$last_json = null;
        $startTime = microtime(true);
        if (strpos($cmd, ' 2>&1') === false) {
            $cmd = $cmd .' 2>&1';
        }
        // 同时输出错误信息
        $json = shell_exec($cmd);

        $msg = "{$cmd}, ret:" . substr($json, 0, 100);

        if ($writeLog) {
            CallLog::logModuleCall("Shell:", $msg, null, $json, $startTime);
            CallLog::flushLogs();
        }
        self::$last_json = $json;

        return $json;
    }

    static function log($msg) {
        $date = date('Y-m-d H:i:s', time());
        print_r("[{$date}], {$msg}\n");
    }

    /**
     * 重试多次获取交易id
     * @param $transaction_id
     * @param int $block_num_hint
     * @param int $maxTimes
     * @param int $times
     *
     * @return mixed
     */
    static function getTransaction2($transaction_id, $block_num_hint, $maxTimes = 3, $times = 0) {
        // 检验交易id是否是当前用户，当前gameid
        $json = self::getTransaction($transaction_id, $block_num_hint);
        $ret = json_decode($json, true);

        if ($ret['error']) {
            // 3040011 The transaction can not be found
            if ($ret['error']['code'] == 3040011) {
                if ($times < $maxTimes) {
                    Tool::sleep($times);
                    return self::getTransaction2($transaction_id, $block_num_hint, $maxTimes, $times + 1);
                } else {
                    Response::error(CODE_PARAM_ERROR, "transaction id:{$transaction_id} is not found.");
                }
            }
        }

        return $ret;
    }


    static function getActions($account, $limit = 50, $p = 1, $ascendOrder = false){
        $p = (int)$p ?: 1;
        $pos = $ascendOrder ? $limit * ($p - 1) : -1;//升序支持分页，降序不支持分页，仅能从最尾部开始
        $offset = $limit - 1;
        $cmd = "cleos -u {$GLOBALS['eosUrl']} get actions -j {$account} {$pos} {$offset}";
        $actions = [];
        for ($i = 0; $i < 5; $i ++) {
            $json = self::execCmd($cmd);
            @$data = json_decode($json, 1) ?: [];
            if (isset($data['actions']) && is_array($data['actions'])) {
                $actions = $data['actions'];
                if (is_array($actions) && count($actions) == $limit) {
                    break;//由于节点的不稳定性，所以要求一定要拿到actions，并争取非尾页的条数等于期望条数
                }
            }
        }
        return $actions;
    }

}