<?php


namespace Mainto\RpcServer\Protocol\Response;


use Mainto\RpcServer\Protocol\Common\BaseType;
use Mainto\RpcServer\Protocol\Common\Types\ResponseHeaderType;
use Mainto\RpcServer\Protocol\Common\Version;
use Mainto\RpcServer\Protocol\Common\Body;
use Mainto\RpcServer\Util\Binary;
use Mainto\RpcServer\Util\Bytes\Bytes;
use Mainto\RpcServer\Util\IO\IO;
use Mainto\RpcServer\Util\IO\Writer;
use Mainto\RpcServer\Util\IO\WriterTo;

class Response implements WriterTo {
    private string $type;
    private bool $isStream = false;
    private int $runtime = 0;

    private ?Extend $ext = null;
    private ?Body $body = null;

    public function setType (int $type) {
        $this->type = $type;
    }

    public function setTypeWithExtend (int $type, ?Extend $extent) {
        $this->type = $type;
        $this->ext = $extent;
    }

    public function setExtend (?Extend $extent) {
        $this->ext = $extent;
    }

    public function setBody (?Body $body) {
        $this->body = $body;
    }

    public function useReturnOKType () {
        $this->type = ResponseHeaderType::ReturnOKType;
        return $this;
    }

    public function useReturnAuthInvokeType () {
        $this->type = ResponseHeaderType::ReturnAuthInvokeType;
        return $this;
    }

    public function useReturnAuthRevokeType () {
        $this->type = ResponseHeaderType::ReturnAuthRevokeType;
        return $this;
    }

    public function useReturnErrType () {
        $this->type = ResponseHeaderType::ReturnErrType;
        return $this;
    }

    public function useReturnSystemErrType () {
        $this->type = ResponseHeaderType::ReturnSystemErrType;
        return $this;
    }

    private function encodeHeaderLen () {
        $size = 0;
        $size += 2;  // head size
        $size += 2;  // version
        $size += 2;  // extend magic number
        $size += 1;  // has body
        $size += 8;  // 预留
        $size += 2;  // stream | empty  | main type | second type
        $size += 4;  // runtime

        // uint16
        return $size;
    }

    private function encodeHeader () {
        $size = $this->encodeHeaderLen();
        $buf = Binary::makeBytes($size);
        Binary::$littleEndian::putUint16($buf, 0, $size);

        $buf[2] = Version::ProtocolVersion[0];
        $buf[3] = Version::ProtocolVersion[1];
        if ($this->ext !== null) {
            Binary::$littleEndian::putUint16($buf, 4, $this->ext->magicNum());
        }

        if ($this->body !== null) {
            $buf[6] = "\x01";
        }

        // type
        if ($this->isStream) {
            $this->type = $this->type | BaseType::StreamOpen | $this->type;
        }

        Binary::$littleEndian::putUint16($buf, 15, $this->type);

        //runtime
        Binary::$littleEndian::putUint32($buf, 17, $this->runtime);

        return $buf;
    }

    public function encode() {
        return IO::IOWriterToBytes($this);
    }

    public function resetBodyAndExt() {
        optional($this->getBody())->reset();

        $this->ext = null;
    }


    public function writeTo (Writer $w): int {
        $b = $this->encodeHeader();
        $totalWrite = $w->write($b);

        if ($this->ext !== null) {
            if ($this->ext instanceof HeaderExtend) {
                $totalWrite += $this->ext->writeHeaderTo($w);
            }
        }

        if ($this->body !== null) {
            $totalWrite += $this->body->writeTo($w);
        }

        if ($this->ext !== null) {
            if ($this->ext instanceof BodyExtend) {
                $totalWrite += $this->ext->writeBodyTo($w);
            }
        }

        return $totalWrite;
    }

    public function getBody (): ?Body {
        return $this->body;
    }

    /**
     * @return bool
     */
    public function hasBody (): bool {
        return $this->body !== null;
    }

    /**
     * @return bool
     */
    public function hasExt (): bool {
        return $this->ext !== null;
    }

    public function toArray (): array {
        $ret = [
            'header' => [
                "type"         => $this->type,
                "type string"  => BaseType::Map[$this->type],
                "version"      => bin2hex(Version::ProtocolVersion),
                "magic number" => optional($this->ext)->magicNum(),
                "is stream"    => var_export($this->isStream, true),
            ],
        ];

        if ($this->hasBody()) $ret['body'] = $this->getBody()->toArray();
        if ($this->hasExt()) $ret['ext'] = $this->getExt()->toArray();

        return $ret;
    }

    public function dump (): string {
        return json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    }

    /**
     * @param int $runtime
     */
    public function setRuntime (int $runtime): void {
        $this->runtime = $runtime;
    }

    /**
     * @return Extend|null
     */
    public function getExt (): ?Extend {
        return $this->ext;
    }

    /**
     * @return string
     */
    public function getType (): string {
        return $this->type;
    }
}