<?php

namespace App\Services\PwApi\Core;

class Gamed
{
    public $ip;

    public $version;

    public $cycle = false;

    protected array $pwConfig;

    public function __construct()
    {
        $this->cycle = false;
        $this->pwConfig = require '/var/www/html/cp/config/pw-api.php';
    }

    public function deleteHeader($data)
    {
        if ($data === false || $data === null || !is_string($data) || strlen($data) < 2) {
            return '';
        }

        $length = 0;
        $this->unpackCuint($data, $length);
        $this->unpackCuint($data, $length);
        $length += 8;
        $data = substr($data, $length);

        return is_string($data) ? $data : '';
    }

    public function createHeader($opcode, $data)
    {
        return $this->cuint($opcode).$this->cuint(strlen($data)).$data;
    }

    public function packString($data)
    {
        $data = iconv('UTF-8', 'UTF-16LE', $data);

        return $this->cuint(strlen($data)).$data;
    }

    public function packString2($data)
    {
        $packetLength = strlen($data);
        if ($packetLength < 128) {
            $packetLength = pack('C*', $packetLength);
        } else {
            $packetLength = pack('n*', $packetLength + 32768);
        }

        return $packetLength.$data;
    }

    public function packLongOctet($data)
    {
        return pack('n', strlen($data) + 32768).$data;
    }

    public function packOctet($data)
    {
        $data = pack('H*', (string) $data);

        return $this->cuint(strlen($data)).$data;
    }

    public function packInt($data)
    {
        return pack('N', $data);
    }

    public function packByte($data)
    {
        return pack('C', $data);
    }

    public function packFloat($data)
    {
        return strrev(pack('f', $data));
    }

    public function packShort($data)
    {
        return pack('n', $data);
    }

    public function packLong($data)
    {
        $left = 0xffffffff00000000;
        $right = 0x00000000FFFFFFFF;
        $l = ($data & $left) >> 32;
        $r = $data & $right;

        return pack('NN', $l, $r);
    }

    public function hex2octet($tmp)
    {
        $t = 8 - strlen($tmp);
        for ($i = 0; $i < $t; $i++) {
            $tmp = '0'.$tmp;
        }

        return $tmp;
    }

    public function reverseOctet($str)
    {
        $octet = '';
        $length = strlen($str) / 2;
        for ($i = 0; $i < $length; $i++) {
            $tmp = substr($str, -2);
            $octet .= $tmp;
            $str = substr($str, 0, -2);
        }

        return $octet;
    }

    public function hex2int($value)
    {
        $value = str_split($value, 2);
        $value = $value[3].$value[2].$value[1].$value[0];
        $value = hexdec($value);

        return $value;
    }

    public function getTime($str)
    {
        return hexdec($str);
    }

    public function getIp($str)
    {
        return long2ip(hexdec($str));
    }

    public function putIp($str)
    {
        $ip = ip2long($str);
        $ip = dechex($ip);
        $ip = hexdec($this->reverseOctet($ip));

        return $ip;
    }

    public function cuint($data)
    {
        if ($data < 64) {
            return strrev(pack('C', $data));
        } elseif ($data < 16384) {
            return strrev(pack('S', ($data | 0x8000)));
        } elseif ($data < 536870912) {
            return strrev(pack('I', ($data | 0xC0000000)));
        }

        return strrev(pack('c', -32).pack('i', $data));
    }

    public function unpackLong($data)
    {
        if (strlen($data) < 8) {
            return 0;
        }
        $set = unpack('N2', $data);

        return $set[1] << 32 | $set[2];
    }

    public function unpackOctet($data, &$tmp)
    {
        $p = 0;
        $size = $this->unpackCuint($data, $p);
        $octet = bin2hex(substr($data, $p, $size));
        $tmp = $tmp + $p + $size;

        return $octet;
    }

    public function unpackString($data, &$tmp)
    {
        $size = (hexdec(bin2hex(substr($data, $tmp, 1))) >= 128) ? 2 : 1;
        $octetlen = (hexdec(bin2hex(substr($data, $tmp, $size))) >= 128) ? hexdec(bin2hex(substr($data, $tmp, $size))) - 32768 : hexdec(bin2hex(substr($data, $tmp, $size)));
        $pp = $tmp;
        $tmp += $size + $octetlen;

        return mb_convert_encoding(substr($data, $pp + $size, $octetlen), 'UTF-8', 'UTF-16LE');
    }

    public function unpackCuint($data, &$p)
    {
        $remaining = strlen($data) - $p;
        if ($remaining < 1) {
            return 0;
        }

        if ($this->pwConfig['game_version'] != '07') {
            $hex = hexdec(bin2hex(substr($data, $p, 1)));
            $min = 0;
            if ($hex < 0x80) {
                $size = 1;
            } elseif ($hex < 0xC0) {
                $size = 2;
                $min = 0x8000;
            } elseif ($hex < 0xE0) {
                $size = 4;
                $min = 0xC0000000;
            } else {
                $p++;
                $size = 4;
            }

            if ($remaining < $size) {
                return 0;
            }

            $data = (hexdec(bin2hex(substr($data, $p, $size))));
            $unpackCuint = $data - $min;
            $p += $size;

            return $unpackCuint;
        } else {
            $byte = unpack('Carray', substr($data, $p, 1));
            if ($byte['array'] < 0x80) {
                $p++;
            } elseif ($byte['array'] < 0xC0) {
                if ($remaining < 2) {
                    return 0;
                }
                $byte = unpack('Sarray', strrev(substr($data, $p, 2)));
                $byte['array'] -= 0x8000;
                $p += 2;
            } elseif ($byte['array'] < 0xE0) {
                if ($remaining < 4) {
                    return 0;
                }
                $byte = unpack('Iarray', strrev(substr($data, $p, 4)));
                $byte['array'] -= 0xC0000000;
                $p += 4;
            } else {
                if ($remaining < 5) {
                    return 0;
                }
                $prom = strrev(substr($data, $p, 5));
                $byte = unpack('Iarray', strrev($prom));
                $p += 4;
            }

            return $byte['array'];
        }
    }

    public function SendToGamedBD($data)
    {
        return $this->SendToSocket($data, $this->pwConfig['ports']['gamedbd']);
    }

    public function SendToDelivery($data)
    {
        return $this->SendToSocket($data, $this->pwConfig['ports']['gdeliveryd'], true);
    }

    public function SendToProvider($data)
    {
        return $this->SendToSocket($data, $this->pwConfig['ports']['gacd']);
    }

    public function SendToSocket($data, $port, $RecvAfterSend = false, $buf = null)
    {
        if (!@fsockopen($this->pwConfig['local'], $port, $errCode, $errStr, 2)) {
            return false;
        }

        $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (!$sock) {
            return false;
        }

        if (!@socket_connect($sock, $this->pwConfig['local'], $port)) {
            socket_close($sock);
            return false;
        }

        if ($this->pwConfig['s_block']) {
            socket_set_block($sock);
        }
        if ($RecvAfterSend) {
            socket_recv($sock, $tmp, 8192, 0);
        }
        socket_send($sock, $data, strlen($data), 0);
        $buf = '';
        switch ($this->pwConfig['s_readtype']) {
            case 1:
                socket_recv($sock, $buf, $this->pwConfig['maxbuffer'], 0);
                break;
            case 2:
                $buffer = socket_read($sock, 1024, PHP_BINARY_READ);
                while ($buffer !== false && strlen($buffer) == 1024) {
                    $buf .= $buffer;
                    $buffer = socket_read($sock, 1024, PHP_BINARY_READ);
                }
                if ($buffer !== false) {
                    $buf .= $buffer;
                }
                break;
            case 3:
                $tmp = 0;
                $buf .= socket_read($sock, 1024, PHP_BINARY_READ);
                if (strlen($buf) >= 8) {
                    $this->unpackCuint($buf, $tmp);
                    $length = $this->unpackCuint($buf, $tmp);
                    while (strlen($buf) < $length) {
                        $chunk = socket_read($sock, 1024, PHP_BINARY_READ);
                        if ($chunk === false || $chunk === '') {
                            break;
                        }
                        $buf .= $chunk;
                    }
                }
                break;
        }
        if ($this->pwConfig['s_block']) {
            socket_set_nonblock($sock);
        }
        socket_close($sock);

        return $buf;
    }

    public function unmarshal(&$rb, $struct)
    {
        $data = [];
        foreach ($struct as $key => $val) {
            if (is_array($val)) {
                if ($this->cycle) {
                    if ($this->cycle > 0) {
                        for ($i = 0; $i < $this->cycle; $i++) {
                            $data[$key][$i] = $this->unmarshal($rb, $val);
                            if (! $data[$key][$i]) {
                                return false;
                            }
                        }
                    }
                    $this->cycle = false;
                } else {
                    $data[$key] = $this->unmarshal($rb, $val);
                    if (! $data[$key]) {
                        return false;
                    }
                }
            } else {
                $tmp = 0;
                switch ($val) {
                    case 'int':
                        if (strlen($rb) < 4) {
                            return false;
                        }
                        $un = unpack('N', substr($rb, 0, 4));
                        $rb = substr($rb, 4);
                        $data[$key] = $un[1];
                        break;
                    case 'int64':
                        if (strlen($rb) < 8) {
                            return false;
                        }
                        $un = unpack('N', substr($rb, 0, 8));
                        $rb = substr($rb, 8);
                        $data[$key] = $un[1];
                        break;
                    case 'long':
                        if (strlen($rb) < 8) {
                            return false;
                        }
                        $data[$key] = $this->unpackLong(substr($rb, 0, 8));
                        $rb = substr($rb, 8);
                        break;
                    case 'lint':
                        if (strlen($rb) < 4) {
                            return false;
                        }
                        $un = unpack('V', substr($rb, 0, 4));
                        $rb = substr($rb, 4);
                        $data[$key] = $un[1];
                        break;
                    case 'byte':
                        if (strlen($rb) < 1) {
                            return false;
                        }
                        $un = unpack('C', substr($rb, 0, 1));
                        $rb = substr($rb, 1);
                        $data[$key] = $un[1];
                        break;
                    case 'cuint':
                        if (strlen($rb) < 1) {
                            return false;
                        }
                        $cui = $this->unpackCuint($rb, $tmp);
                        $rb = substr($rb, $tmp);
                        if ($cui > 0) {
                            $this->cycle = $cui;
                        } else {
                            $this->cycle = -1;
                        }
                        break;
                    case 'octets':
                        if (strlen($rb) < 1) {
                            return false;
                        }
                        $data[$key] = $this->unpackOctet($rb, $tmp);
                        $rb = substr($rb, $tmp);
                        break;
                    case 'name':
                        if (strlen($rb) < 1) {
                            return false;
                        }
                        $data[$key] = $this->unpackString($rb, $tmp);
                        $rb = substr($rb, $tmp);
                        break;
                    case 'short':
                        if (strlen($rb) < 2) {
                            return false;
                        }
                        $un = unpack('n', substr($rb, 0, 2));
                        $rb = substr($rb, 2);
                        $data[$key] = $un[1];
                        break;
                    case 'lshort':
                        if (strlen($rb) < 2) {
                            return false;
                        }
                        $un = unpack('v', substr($rb, 0, 2));
                        $rb = substr($rb, 2);
                        $data[$key] = $un[1];
                        break;
                    case 'float2':
                        if (strlen($rb) < 4) {
                            return false;
                        }
                        $un = unpack('f', substr($rb, 0, 4));
                        $rb = substr($rb, 4);
                        $data[$key] = $un[1];
                        break;
                    case 'float':
                        if (strlen($rb) < 4) {
                            return false;
                        }
                        $un = unpack('f', strrev(substr($rb, 0, 4)));
                        $rb = substr($rb, 4);
                        $data[$key] = $un[1];
                        break;
                }
                if ($val != 'cuint' and is_null($data[$key])) {
                    return false;
                }
            }
        }

        return $data;
    }

    public function marshal($pack, $struct)
    {
        $this->cycle = false;
        $data = '';
        foreach ($struct as $key => $val) {
            if (substr($key, 0, 1) == '@') {
                continue;
            }
            if (is_array($val)) {
                if ($this->cycle) {
                    if ($this->cycle > 0) {
                        $count = $this->cycle;
                        for ($i = 0; $i < $count; $i++) {
                            $data .= $this->marshal($pack[$key][$i], $val);
                        }
                    }
                    $this->cycle = false;
                } else {
                    $data .= $this->marshal($pack[$key], $val);
                }
            } else {
                switch ($val) {
                    case 'int':
                        $data .= $this->packInt((int) $pack[$key]);
                        break;
                    case 'byte':
                        $data .= $this->packByte($pack[$key]);
                        break;
                    case 'cuint':
                        $arrkey = substr($key, 0, -5);
                        $cui = isset($pack[$arrkey]) ? count($pack[$arrkey]) : 0;
                        $this->cycle = ($cui > 0) ? $cui : -1;
                        $data .= $this->cuint($cui);
                        break;
                    case 'octets':
                        if ($pack[$key] === []) {
                            $pack[$key] = '';
                        }
                        $data .= $this->packOctet($pack[$key]);
                        break;
                    case 'name':
                        if ($pack[$key] === []) {
                            $pack[$key] = '';
                        }
                        $data .= $this->packString($pack[$key]);
                        break;
                    case 'short':
                        $data .= $this->packShort($pack[$key]);
                        break;
                    case 'float':
                        $data .= $this->packFloat($pack[$key]);
                        break;
                    case 'cat1':
                    case 'cat2':
                    case 'cat4':
                        $data .= $pack[$key];
                        break;
                }
            }
        }

        return $data;
    }

    public function MaxOnlineUserID($arr)
    {
        $max = $arr[0]['userid'];
        for ($i = 1; $i < count($arr); $i++) {
            if ($arr[$i]['userid'] > $max) {
                $max = $arr[$i]['userid'];
            }
        }

        return $max + 1;
    }

    public function getArrayValue($array = [], $index = null)
    {
        return $array[$index];
    }
}
