<?php
namespace Incoviba\Common\Implement\Repository;

use Error;
use Closure;
use Incoviba\Common\Define;
use Incoviba\Common\Implement\Exception\EmptyResult;

class Mapper implements Define\Repository\Mapper
{
    public string $property;
    public Closure $function;
    public Define\Repository\Factory $factory;
    public mixed $default;

    public function setProperty(string $property): Define\Repository\Mapper
    {
        $this->property = $property;
        return $this;
    }
    public function setFunction(callable $function): Define\Repository\Mapper
    {
        $this->function = $function(...);
        return $this;
    }
    public function setFactory(Define\Repository\Factory $factory): Define\Repository\Mapper
    {
        $this->factory = $factory;
        return $this;
    }
    public function setDefault(mixed $value): Define\Repository\Mapper
    {
        $this->default = $value;
        return $this;
    }

    public function hasProperty(): bool
    {
        return isset($this->property);
    }
    public function hasFunction(): bool
    {
        return isset($this->function);
    }
    public function hasFactory(): bool
    {
        return isset($this->factory);
    }
    public function hasDefault(): bool
    {
        try {
            return isset($this->default) or $this->default === null;
        } catch (Error) {
            return false;
        }
    }

    public function parse(Define\Model &$model, string $column, ?array $data): bool
    {
        $property = $column;
        if ($this->hasProperty()) {
            $property = $this->property;
        }
        if (in_array($column, array_keys($data))) {
            if ($this->hasFactory()) {
                $model->addFactory($property, $this->factory);
                return true;
            }
            $value = $data[$column];
            if ($this->hasFunction()) {
                try {
                    $value = ($this->function)($data);
                } catch (EmptyResult $exception) {
                    if ($this->hasDefault()) {
                        $value = $this->default;
                    } else {
                        throw $exception;
                    }
                }
            }
            $model->{$property} = $value;
            return true;
        }
        if ($this->hasDefault()) {
            $model->{$property} = $this->default;
            return true;
        }
        return false;
    }
}
