<?php


namespace Mainto\RpcServer\RpcServer;


use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use Mainto\RpcServer\RpcServer\Definition\Controller;
use Mainto\RpcServer\RpcServer\Definition\Enum;
use Mainto\RpcServer\RpcServer\Definition\Exception;
use Mainto\RpcServer\RpcServer\Definition\NotDefinitionStruct;
use Mainto\RpcServer\RpcServer\Definition\RpcObject\JsonMapper;
use Mainto\RpcServer\RpcServer\Definition\RpcObject\ObjectRef;
use Mainto\RpcServer\RpcServer\Definition\Struct;
use Mainto\RpcServer\Util\ArrayHelper;
use Mainto\RpcServer\Util\ObjectMapper\MapperInterface;
use Mainto\RpcServer\Util\Types\Map;
use RuntimeException;

class RpcDefinition implements Arrayable, \Serializable {
    use ArrayHelper;

    public const EmptyType = '#empty';
    public const SimpleTypes = [
        "string",
        "bool",
        "int",
        "uint",
        "double",
        "unsignedDouble",
        "array",
        "array<int>",
        "array<uint>",
        "array<string>",
    ];
    private static ?RpcDefinition $instance = null;
    /**
     * @var Map<Controller>|Controller[]
     */
    public Map $controllers;
    /**
     * @var Map<Struct>|Struct[]
     */
    public Map $structs;

    /**
     * @var Enum[]
     */
    public array $enums = [];

    /**
     * @var Exception[]
     */
    public array $exceptions = [];

    /**
     * @var string
     */
    public string $serviceName;

    /**
     * @var string
     */
    public string $version;

    private MapperInterface $mapper;

    private function __construct () {
        $this->init();
    }

    public static function getInstance (): RpcDefinition {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public static function newInstance (): RpcDefinition {
        return new self();
    }

    public static function clear () {
        self::$instance = null;
    }

    public function inStructCache (string $type): bool {
        return isset($this->structs[$type]);
    }

    public function getStruct (string $type): Struct {
        if (!isset($this->structs[$type])) {
            throw new RuntimeException("the struct not exists");
        }

        return $this->structs[$type];
    }

    public function objectStruct (string $type): Struct {
        if (!isset($this->structs[$type])) {
            $this->structs[$type] = $struct = new Struct();

            $objectRef = ObjectRef::getRef($type);
            $struct->name = $objectRef->name;
            $struct->namespace = $objectRef->namespace;
            $struct->properties = $objectRef->properties;
        }

        return $this->structs[$type];
    }

    public function notDefinitionStruct (string $type): NotDefinitionStruct {
        if (!isset($this->structs[$type])) {
            $this->structs[$type] = $struct = new NotDefinitionStruct();

            $shortName = class_basename($type);
            $struct->name = Str::studly($shortName).Str::studly(str_replace('.', ' ', $struct->originName));
            $struct->namespace = substr($type, 0, -strlen($shortName) - 1);
        }

        return $this->structs[$type];
    }

    public function parse (string $path) {
        $this->mapper->map(json_decode(file_get_contents($path), true), $this);
    }

    public function getMapper (): MapperInterface {
        return $this->mapper;
    }

    public function serialize () {
        return json_encode($this);
    }

    public function unserialize ($serialized) {
        $this->init();
        $this->mapper->map($serialized, $this);
    }

    private function init (): void {
        $this->structs = new Map();
        $this->controllers = new Map();

        $empty = new Struct();
        $empty->name = "#empty";
        $empty->namespace = "#empty";

        $this->structs[self::EmptyType] = $empty;
        $this->mapper = new JsonMapper();
        $this->version = get_git_branch_version(env('BASE_DIR'));
        $this->serviceName = Str::studly(config('rpc-server.service_name'));
    }
}