<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Entity\Webservice\Type;

use Sso\WebserviceBundle\Entity\Webservice\Base as ModelBase;
use Sso\WebserviceBundle\Api\Error\Type\External as ExternalError;
use Symfony\Component\Validator\Validator\ValidatorInterface as Validator;

/**
 * Class Base
 *
 * @copyright  2017 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 * @package Sso\WebserviceBundle\Entity\Webservice\Type
 */
abstract class Base extends ModelBase
{
    /**
     * Validator
     *
     * @var Validator
     */
    protected $_validator;

    /**
     * List of fields, that has been changed via set
     *
     * @var array
     */
    private $_changedFields = array();

    /**
     * Constructor
     *
     * @param Validator $validator
     */
    public function __construct(Validator $validator)
    {
        $this->setValidator($validator);
        parent::__construct();
    }

    /**
     * Set validator
     *
     * @param Validator $validator
     */
    public function setValidator(Validator $validator)
    {
        $this->_validator = $validator;
    }

    /**
     * Set values
     *
     * @param array|object $values
     * @param array|null $validationGroups Null for full validation, array of group-name(s) for group specific validation
     */
    public function init($values, $validationGroups = null)
    {
        // Throw exception, if input format is not valid
        if (!is_array($values) && !($values instanceof \Traversable)) {
            $this->_exception(new ExternalError('b1', 'b1', 'Invalid data format',
                'Data should be an array or an traversable object, but is ' . get_class($values) . '.'));
        }

        // Fill up object with values
        $this->_setValues($values);

        // Validate
        $this->validate($validationGroups);

        // Throw exception, if any error has occured
        if ($this->errors()->hasErrors()) {
            $this->_exception();
        }
    }

    /**
     * Set properties
     *
     * @param array|object $values
     */
    private function _setValues($values)
    {
        $castValues = !is_array($values);

        // Get methods
        $methods = $this->_getSetters();

        // Set values
        foreach ($values as $key => $value) {
            if (!isset($methods[strtolower($key)])) {
                continue;
            }
            if ($castValues) {
                $value = (string)$value;
            }
            $method = $methods[strtolower($key)];
            $this->$method($value);
        }
    }

    /**
     * Returns an assoc array of setter methods
     *
     * @return array
     */
    private function _getSetters()
    {
        $methods = array();
        foreach (array_filter(get_class_methods($this), array($this, 'filterSetters')) as $method) {
            $methods[strtolower(substr($method, 3))] = $method;
        }
        return $methods;
    }

    /**
     * Validate object
     *
     * @param array|null $validationGroups Null for full validation, array of group-name(s) for group specific validation
     * @return bool True on successful validation
     */
    public function validate($validationGroups = null)
    {
        $errors = $this->_validator->validate($this, $validationGroups);
        foreach ($errors as $error) {
            $parts = explode('##', $error->getMessage());
            $longMessage = array_pop($parts) ?: 'Unknown error';
            $shortMessage = array_pop($parts) ?: 'Unknown error';
            $errorCode = array_pop($parts)? : 'e500';
            $this->errors()->addError(new ExternalError('v001', $errorCode, $shortMessage, $longMessage));
        }
        return 0 === count($errors);
    }

    /**
     * Filter list of methods
     *
     * @param string $method
     * @return bool
     */
    public function filterSetters($method)
    {
        return preg_match('/^set[A-Z]/', $method);
    }

    /**
     * Returns true, if data is valid, no error found
     *
     * @return bool
     */
    public function isValid()
    {
        return $this->validate() && !$this->errors()->hasErrors();
    }

    /**
     * Merge another model into this model
     *
     * @param \Sso\WebserviceBundle\Entity\Webservice\Type\Base $model
     */
    public function merge(Base $model)
    {
        foreach ($model->getChangedFields() as $field) {
            $getMethod = 'get' . $field;
            $setMethod = 'set' . $field;
            if (method_exists($this, $setMethod)) {
                $this->$setMethod($model->$getMethod());
            }
        }
    }

    /**
     * Get list of changed fields
     *
     * @return array
     */
    public function getChangedFields()
    {
        return array_keys($this->_changedFields);
    }

    /**
     * Mark value as changed
     *
     * @param string $field
     */
    protected function _setChanged($field)
    {
        $this->_changedFields[$field] = true;
    }

    /**
     * Returns true, if value has been changed
     *
     * @param string $field
     * @return bool
     */
    protected function _hasChanged($field)
    {
        return isset($this->_changedFields[$field]);
    }
}
