<?php

namespace CouponsPlus\Original\Collections;

use CouponsPlus\Original\Characters\StringManager;
use CouponsPlus\Original\Collections\Collection;
use CouponsPlus\Original\Utilities\ObjectSetter;
use CouponsPlus\Original\Collections\Abilities\ArrayRepresentation;
use ReflectionObject;
use ReflectionProperty;
use StdClass;

Class MappedObject extends StdClass implements ArrayRepresentation
{
    protected $mapFieldsFoundInSource;
    protected $mapFieldsNotFoundInSource;
    protected $allFieldsFoundInSource;
    protected $dataFound;
    protected $rawDataFound; // the raw data as it was received, in a plain object format (first level only)

    public function __construct(array $values = [], bool $noMetaDataInitialization = false)
    {
        ObjectSetter::setPublicValues(['object' => $this, 'values' => $values]);

        if ($noMetaDataInitialization) {
            return;
        }
        
        $this->mapFieldsFoundInSource = new Collection([]);   
        $this->mapFieldsNotFoundInSource = new Collection([]);  
        $this->allFieldsFoundInSource = new Collection([]); 
    }

    public function setMappedFieldsFound(MappedObject $mappedObjectOnlyWithFieldsFound)
    {
        $this->dataFound = $mappedObjectOnlyWithFieldsFound;
    }

    public function setRawDataFound(Collection $rawDataFound)
    {
        $this->rawDataFound = $rawDataFound;   
    }
    
    public function asRawArray() : array
    {
        (object) $reflection = new ReflectionObject($this);

        return $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
    }
    

    public function asArrayWithNativeValues() : array
    {
        return json_decode($this->asCollection()->asJson(), $asArray = true);
    }

    public function asArray()
    {
        (array) $publicProperties = [];

        foreach($this->asRawArray() as $publicProperty) {

            $value = $this->{$publicProperty->name};
            
            if ($value instanceof MappedObject) {
                $value = $value->asArray();
            }

            $publicProperties[$publicProperty->name] = $value;
        }

        return $publicProperties;
    }

    public function asCollection()
    {
        return new Collection($this->asArray());   
    }    

    public function __get($property)
    {
        return $this->{$property};
    }

    public function __clone()
    {
        (object) $properties = (new Collection(get_object_vars($this)))->getKeys();

        $properties->forEvery(function($property){
            if (is_object($this->{$property})) {
                $this->{$property} = clone $this->{$property};
            }
        });
    }
 
    public function __sleep(): array
    {
        (object) $excludedProperties = new Collection([
            'mapFieldsFoundInSource',
            'mapFieldsNotFoundInSource',
            'allFieldsFoundInSource',
            'dataFound',
            'rawDataFound',
        ]);

        return Collection::create(get_object_vars($this))->filter(function($value, string $key) use ($excludedProperties) : bool {
            return (new StringManager($key))->isNotEither($excludedProperties);
        })->getKeys()->map(function($key) : string {
            return (string) $key;
        })->asArray();
    } 
}