<?php

/**
 * RestNormalizer - The main class
 */
class RestNormalizer
{
    public $clear = true;
    private $validation = array();
    private $originalValidation = array();
    private $server;

    /**
     * Class constructor
     * @return void
     * @access public
     * @final
     */
    final public function __construct()
    {
        if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
            date_default_timezone_set(@date_default_timezone_get());
        }
        $this->server = \Bitrix\Main\Context::getCurrent()->getServer()->getDocumentRoot();
    }

    /**
     * Parsing the file validation
     * @param string $file The path to the file validation
     * @return boolean
     * @access private
     * @final
     */
    final private function parseConfig($file)
    {
        if (json_decode(file_get_contents($file)) !== null) {
            $this->originalValidation = json_decode(file_get_contents($file), true);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Starting the process of normalization of the data
     * @param array $data The key is to sort the data validation
     * @param string $key Data normalization
     * @return array
     * @access public
     * @final
     */
    final public function normalize($data, $key = false, $file = '/bitrix/modules/intaro.retailcrm/classes/general/config/retailcrm.json')
    {
        $server = \Bitrix\Main\Context::getCurrent()->getServer()->getDocumentRoot();
        $file = $server . $file;
        if (is_null($file) || is_file($file) === false
            || json_decode(file_get_contents($file)) === null
            || $this->parseConfig($file) === false) {
                RCrmActions::eventLog('RestNormalizer', 'intaro.retailcrm', 'Incorrect file normalize.');
                return false;
        }

        if (is_string($data)) {
            $data = json_decode($data, true);
        }

        if (is_string($key) && isset($this->originalValidation[ $key ])) {
            $this->validation = $this->originalValidation[ $key ];
        } else {
            $this->validation = $this->originalValidation;
        }

        if (!is_array($data) || count($data) < 1) {
            RCrmActions::eventLog('RestNormalizer', 'intaro.retailcrm', 'Incorrect data array.');
            return false;
        }

        return $this->formatting($data);
    }

    /**
     * Data formatting
     * @param array $data The key is to sort the data validation
     * @param boolean $skip Skip perform methods intended for the first run
     * @return array
     * @access private
     * @final
     */
    final private function formatting($data, $skip = false)
    {
        $formatted = array();

        foreach ($data as $code => $value) {
            if (isset($this->validation[ $code ]) && $this->validation[ $code ]['type'] == 'skip') {
                $formatted[ $code ] = $value;
            } elseif (isset($this->validation[ $code ]) && is_array($value) === false) {
                $formatted[ $code ] = $this->setFormat($value, $this->validation[ $code ]);
            } elseif (is_array($value)) {
                $formatted[ $code ] = $this->formatting($value, true);
            }

            if ($formatted[ $code ] === null || $formatted[ $code ] === '' || count($formatted[ $code ]) < 1) {
                if ($this->clear === true) {
                    unset($formatted[ $code ]);
                }

                if (isset($this->validation[ $code ]['required']) && $this->validation[ $code ]['required'] === true) {
                    $formatted = array();
                    break;
                }
            }

        }

        if ($skip === false) {
            foreach ($this->validation as $code => $valid) {
                if (isset($valid['required']) && $valid['required'] === true && isset($formatted[ $code ]) === false) {
                    RCrmActions::eventLog('RestNormalizer', 'intaro.retailcrm', "NOT VALID: $code");
                }
            }

            $formatted = $this->multiConvert($formatted);
        }

        return count($formatted) < 1 ? false : $formatted;
    }

    /**
     * Formatting data depending on the type
     * @param mixed $data The value to be formatted
     * @param array $validation The data for the current data type validation
     * @return mixed
     * @access private
     * @final
     */
    final private function setFormat($data, $validation)
    {
        $format = null;

        switch ($validation['type']) {
            case 'string':
                $format = $this->setString($data, $validation);
                break;
            case 'int':
                $format = $this->setInt($data, $validation);
                break;
            case 'double':
                $format = $this->setDouble($data, $validation);
                break;
            case 'bool':
                $format = $this->setBool($data, $validation);
                break;
            case 'datetime':
                $format = $this->setDateTime($data, $validation);
                break;
            case 'enum':
                $format = $this->setEnum($data, $validation);
                break;
        }

        return $format;
    }

    /**
     * Formatting data for strings
     * @param string $data String to formatting
     * @param array $validation The data for the current data type validation
     * @return string
     * @access private
     * @final
     */
    final private function setString($data, $validation)
    {
        $data = trim((string) $data);

        if (isset($validation['default']) && is_string($validation['default']) && trim($validation['default']) != ''
            && ($data == '' || is_string($data) === false)) {
            $data = trim($validation['default']);
        } elseif ($data == '' || is_string($data) === false) {
            return null;
        } elseif (isset($validation['min']) && mb_strlen($data) < $validation['min']) {
            $pad = isset($validation['pad']) && mb_strlen($validation['pad']) == 1 ? $validation['pad'] : ' ';
            $data .= str_repeat($pad, $validation['min'] - mb_strlen($data));
        } elseif (isset($validation['max']) && mb_strlen($data) > $validation['max']) {
            $data = mb_substr($data, 0, $validation['max']);
        }

        return (string) $data;
    }

    /**
     * Formatting data for integers
     * @param integer $data Integer to formatting
     * @param array $validation The data for the current data type validation
     * @return integer
     * @access private
     * @final
     */
    final private function setInt($data, $validation)
    {
        if (isset($validation['default']) && is_numeric($validation['default']) && is_numeric($data) === false) {
            $data = $validation['default'];
        } elseif (is_numeric($data) === false) {
            return null;
        } elseif (isset($validation['min']) && $data < $validation['min']) {
            $data += $validation['min'] - $data;
        } elseif (isset($validation['max']) && $data > $validation['max']) {
            $data -= $data - $validation['max'];
        }

        return (int) $data;
    }

    /**
     * Formatting data for floating-point numbers
     * @param float $data Floating-point number to formatting
     * @param array $validation The data for the current data type validation
     * @return float
     * @access private
     * @final
     */
    final private function setDouble($data, $validation)
    {
        if (isset($validation['default']) && is_numeric($validation['default']) && is_numeric($data) === false) {
            $data = $validation['default'];
        } elseif (is_numeric($data) === false) {
            return null;
        } elseif (isset($validation['min']) && $data < $validation['min']) {
            $data += $validation['min'] - $data;
        } elseif (isset($validation['max']) && $data > $validation['max']) {
            $data -= $data - $validation['max'];
        }

        if (isset($validation['decimals'])) {
            $data = number_format($data, $validation['decimals'], '.', '');
        }

        return (double) $data;
    }

    /**
     * Formatting data for logical values
     * @param boolean $data Boolean value to formatting
     * @param array $validation The data for the current data type validation
     * @return boolean
     * @access private
     * @final
     */
    final private function setBool($data, $validation)
    {
        if (isset($validation['default']) && is_bool($validation['default']) && is_bool($data) === false) {
            $data = $validation['default'];
        } elseif (is_bool($data) === false) {
            return null;
        }

        return (bool) $data;
    }

    /**
     * Formatting data for date and time
     * @param mixed $data Date and time of to formatting
     * @param array $validation The data for the current data type validation
     * @param boolean $skip Skip perform methods intended for the first run
     * @return mixed
     * @access private
     * @final
     */
    final private function setDateTime($data, $validation, $skip = false)
    {
        if (is_a($data, 'DateTime') && isset($validation['format'])) {
            $data = (string) $data->format($validation['format']);
        } elseif (is_string($data) && isset($validation['format']) && strtotime($data) !== false) {
            $data = (string) date($validation['format'], strtotime($data));
        } elseif (is_numeric($data) && isset($validation['format'])) {
            $data = (string) date($validation['format'], (int) $data);
        } elseif (is_numeric($data)) {
            $data = (int) $data;
        } elseif (isset($validation['format'])) {
            $data = (string) date($validation['format']);
        } elseif (isset($validation['default']) && $skip === false) {
            $data = $this->setDateTime(time(), $validation, true);
        } else {
            return null;
        }

        return $data;
    }

    /**
     * Formatting data for enum
     * @param string $data Enum to formatting
     * @param array $validation The data for the current data type validation
     * @return string
     * @access private
     * @final
     */
    final private function setEnum($data, $validation)
    {
        if (isset($validation['values']) === false || count($validation['values']) < 1) {
            return null;
        } elseif (isset($validation['default']) && in_array($validation['default'], $validation['values']) === false) {
            return null;
        } elseif (in_array($data, $validation['values']) === false
                  && isset($validation['default']) && in_array($validation['default'], $validation['values'])) {
            $data = $validation['default'];
        } elseif (in_array($data, $validation['values']) === false) {
            return null;
        }

        return $data;
    }

    /**
     * Installing the specified encoding
     * @param array $data The original dataset
     * @return array
     * @access private
     * @final
     */
    final private function multiConvert($data)
    {
        global $APPLICATION;

        if (is_array($data)) {
            foreach ($data as $code => $value) {
                if (is_array($value)) {
                    $value = array_diff($value, array('', NULL));
                }
                $data[$APPLICATION->ConvertCharset($code, SITE_CHARSET, 'utf-8')] = is_array($value)
                                                                        ? $this->multiConvert($value)
                                                                        : $APPLICATION->ConvertCharset($value, SITE_CHARSET, 'utf-8');
            }
            return $data;
        } else {
            return $APPLICATION->ConvertCharset($data, SITE_CHARSET, 'utf-8');
        }

        return $data;
    }
}
?>