<?php
namespace App\Library\Xml;

use App\Models\Dataset;
use DOMDocument;

/**
 * Strategy short summary.
 *
 * Strategy description.
 *
 * @version 1.0
 * @author kaiarn
 */
class Strategy
{
    /**
     * Holds current configuration.
     *
     * @var Conf
     */
    private $_config;

    /**
     * Holds current representation version.
     *
     * @var double
     */
    protected $_version = null;


    /**
     * Initiate class with a valid config object.
     */
    public function __construct()
    {
        $this->_version = 1.0;
        $this->_config = new Conf();
    }

    /**
     * (non-PHPdoc)
     * see library/Opus/Model/Xml/Opus_Model_Xml_Strategy#setDomDocument()
     */
    public function setup(Conf $conf)
    {
        $this->_config = $conf;
    }

    /**
     * If a model has been set this method generates and returnes
     * DOM representation of it.
     *
     * @throws \Exception Thrown if no Model is given.
     * @return \DOMDocument DOM representation of the current Model.
     */
    public function getDomDocument()
    {
        if (null === $this->_config->model) {
            throw new \Exception('No Model given for serialization.');
        }
        $this->_config->dom = new DOMDocument('1.0', 'UTF-8');
        $root = $this->_config->dom->createElement('Opus');
        $root->setAttribute('version', $this->getVersion());
        $this->_config->dom->appendChild($root);
        $root->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');

        $this->_mapModel($this->_config->model, $this->_config->dom, $root);

        return $this->_config->dom;
    }

    public function getModelFieldValues(Dataset $model)
    {
        $fields = $model->describe();
        $excludeFields = $this->getConfig()->excludeFields;
        if (count($excludeFields) > 0) {
            $fieldsDiff = array_diff($fields, $excludeFields);
        } else {
            $fieldsDiff = $fields;
        }

        foreach ($fieldsDiff as $fieldname) {
            $field = $model->getField($fieldname);
            $fieldValue = $this->getFieldValue($field);
        }
    }

    protected function getFieldValue(Field $field): string
    {
        $modelClass = $field->getValueModelClass();
        $fieldValues = $field->getValue();
      
        if (null === $modelClass) {
            $fieldName = $field->getName();
            $fieldValues = $this->getFieldValues($field);
    
            // Replace invalid XML-1.0-Characters by UTF-8 replacement character.
            $fieldValues = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', "\xEF\xBF\xBD ", $fieldValues);
        }
        return fieldValues;
    }

    protected function _mapModel(Dataset $model, \DOMDocument $dom, \DOMNode $rootNode)
    {
        $fields = $model->describe();
        $excludeFields = $this->getConfig()->excludeFields;
        if (count($excludeFields) > 0) {
            $fieldsDiff = array_diff($fields, $excludeFields);
        } else {
            $fieldsDiff = $fields;
        }

        $childNode = $this->createModelNode($dom, $model);
        $rootNode->appendChild($childNode);

        foreach ($fieldsDiff as $fieldname) {
            $field = $model->getField($fieldname);
            $this->_mapField($field, $dom, $childNode);
        }
    }

    protected function _mapField(Field $field, DOMDocument $dom, \DOMNode $rootNode)
    {
        $modelClass = $field->getValueModelClass();
        $fieldValues = $field->getValue();

        if (true === $this->getConfig()->excludeEmpty) {
            if (true === is_null($fieldValues)
                or (is_string($fieldValues) && trim($fieldValues) == '')
                or (is_array($fieldValues) && empty($fieldValues))) {
                return;
            }
        }

        if (null === $modelClass) {
            $this->mapSimpleField($dom, $rootNode, $field);
        } else {
            $fieldName = $field->getName();

            if (!is_array($fieldValues)) {
                $fieldValues = array($fieldValues);
            }

            foreach ($fieldValues as $value) {
                $childNode = $this->createFieldElement($dom, $fieldName);
                //$childNode->setAttribute("Value",  $value);
                $rootNode->appendChild($childNode);

                
                // if a field has no value then is nothing more to do
                // TODO maybe must be there an other solution
                // FIXME remove code duplication (duplicates Opus_Model_Xml_Version*)
                if (is_null($value)) {
                    continue;
                }

                if ($value instanceof \Illuminate\Database\Eloquent\Model) {
                    $this->_mapModelAttributes($value, $dom, $childNode);
                } elseif ($value instanceof \Carbon\Carbon) {
                    $this->_mapDateAttributes($value, $dom, $childNode);
                } elseif (is_array($value)) {
                    $this->_mapArrayAttributes($value, $dom, $childNode);
                }
            }
        }
    }

    public function mapSimpleField(DOMDocument $dom, \DOMNode $rootNode, Field $field)
    {
        $fieldName = $field->getName();
        $fieldValues = $this->getFieldValues($field);

        // Replace invalid XML-1.0-Characters by UTF-8 replacement character.
        $fieldValues = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', "\xEF\xBF\xBD ", $fieldValues);
        $rootNode->setAttribute($fieldName, $fieldValues);
    }

    protected function createFieldElement(DOMDocument $dom, $fieldName)
    {
        return $dom->createElement($fieldName);
    }

    protected function _mapDateAttributes(\Carbon\Carbon $model, DOMDocument $dom, \DOMNode $rootNode)
    {
        $rootNode->setAttribute("Year", $model->year);
        $rootNode->setAttribute("Month", $model->month);
        $rootNode->setAttribute("Day", $model->day);
        $rootNode->setAttribute("Hour", $model->hour);
        $rootNode->setAttribute("Minute", $model->minute);
        $rootNode->setAttribute("Second", $model->second);
        $rootNode->setAttribute("UnixTimestamp", $model->timestamp);
        $rootNode->setAttribute("Timezone", $model->tzName);
    }

    protected function _mapArrayAttributes(array $attributes, DOMDocument $dom, \DOMNode $rootNode)
    {
        //$attributes = array_keys($model->getAttributes());
        foreach ($attributes as $property_name => $value) {
            $fieldName = $property_name;
            $field = new Field($fieldName);
            $fieldval = $value;
            $field->setValue($fieldval);
            $this->_mapField($field, $dom, $rootNode);
        }
    }

    protected function _mapModelAttributes(\Illuminate\Database\Eloquent\Model $model, DOMDocument $dom, \DOMNode $rootNode)
    {
        $attributes = array_keys($model->getAttributes());
        foreach ($attributes as $property_name) {
            $fieldName = self::convertColumnToFieldname($property_name);
            $field = new Field($fieldName);
            $fieldval = $model->{$property_name};
            $field->setValue($fieldval);
            $this->_mapField($field, $dom, $rootNode);
        }
    }

    //snakeToCamel
    public static function convertColumnToFieldname($columnname)
    {
        //return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $columnname))));
        return str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $columnname)));
    }

    public function getFieldValues($field)
    {
        $fieldValues = $field->getValue();

        // workaround for simple fields with multiple values
        if (true === $field->hasMultipleValues()) {
            $fieldValues = implode(',', $fieldValues);
        }
        //if ($fieldValues instanceOf DateTimeZone) {
        //    $fieldValues = $fieldValues->getName();
        //}

        return trim($fieldValues);
    }


    protected function createModelNode(DOMDocument $dom, Dataset $model)
    {
        $classname = "Rdr_" . substr(strrchr(get_class($model), '\\'), 1);
        return $dom->createElement($classname);
    }

    /**
     * Return version value of current xml representation.
     *
     * @see library/Opus/Model/Xml/Opus_Model_Xml_Strategy#getVersion()
     */
    public function getVersion()
    {
        return floor($this->_version);
    }

    public function getConfig()
    {
        return $this->_config;
    }
}