<?php


namespace Mainto\RpcServer\Util\ObjectMapper;


use Mainto\RpcServer\Util\Language;
use Mainto\RpcServer\Util\Types\Map;
use ReflectionException;

/**
 * Class JsonMapper
 * @package Mainto\RpcServer\Util\ObjectMapper
 */
class JsonMapper implements MapperInterface {
    /**
     * @var array ['class_key' => ClassRef]
     */
    private array $classRefMap = [];

    /**
     * @param $data
     * @param object $object
     */
    public function map ($data, object $object) {
        if (is_string($data)) {
            $data = json_decode($data, true);
        }

        $this->mapJsonArray($data, $object);
    }

    public function mapJsonArray (array $data, object $object) {
        $classRef = $this->classRef($object);

        foreach ($classRef->properties as $propertyRef) {
            if (!array_key_exists($propertyRef->name, $data)) {
                continue;
            }

            $type = $propertyRef->type;
            if (in_array($type, Language::$simpleType) || $type == 'mixed') {
                $value = $data[$propertyRef->name];
            } elseif (ends_with($type, '[]')) {
                $subType = substr($type, 0, -2);
                if (in_array($subType, Language::$simpleType)) {
                    $value = $data[$propertyRef->name];
                } else {
                    $value = [];
                    foreach ($data[$propertyRef->name] as $key => $datum) {
                        $subObject = new $subType;
                        $this->mapJsonArray($datum, $subObject);
                        $value[$key] = $subObject;
                    }
                }
            } elseif (starts_with($type, 'Map::')) {
                $value = new Map();
                $subType = substr($type, 5);
                if (in_array($subType, Language::$simpleType)) {
                    $value->setItems($data[$propertyRef->name]);
                } else {
                    foreach ($data[$propertyRef->name] as $key => $datum) {
                        $subObject = new $subType;
                        $this->mapJsonArray($datum, $subObject);
                        $value->set($key, $subObject);
                    }
                }
            } else {
                if (is_null($data[$propertyRef->name])) {
                    $value = null;
                } else {
                    $propertyObject = new $type;
                    $this->mapJsonArray($data[$propertyRef->name], $propertyObject);

                    $value = $propertyObject;
                }
            }

            if ($propertyRef->setterMethod) {
                $object->{$propertyRef->setterMethod}($value);
            } else {
                $object->{$propertyRef->name} = $value;
            }
        }
    }

    /**
     * @param object $object
     * @return ClassRef
     * @throws ReflectionException
     */
    public function classRef (object $object): ClassRef {
        $propertiesKey = get_class($object);
        if (isset($this->classRefMap[$propertiesKey])) {
            return $this->classRefMap[$propertiesKey];
        } else {
            return $this->classRefMap[$propertiesKey] = new ClassRef($object);
        }
    }
}