<?php

namespace goodboyalex\php_components_pack\classes;

use Exception;
use goodboyalex\php_components_pack\interfaces\ISerializable;

/**
 * Класс, описывающий двумерный размер.
 *
 * @author Александр Бабаев
 * @package php_components_pack
 * @version 1.0
 * @since 1.0.5
 */
final class TwoDimSize implements ISerializable
{
    /**
     * Разделитель частей по умолчанию.
     */
    public const string DEFAULT_DELIMITER = ':';

    /**
     * @var bool $NoNegativeValues Значения не должны быть отрицательными.
     */
    public bool $NoNegativeValues = true;

    /**
     * @var int $Width Длина (публичная)
     */
    public int $Width {
        get {
            return $this->pWidth;
        }
        set {
            $this->pWidth = $this->NoNegativeValues ? max($value, 0) : $value;
        }

    }

    /**
     * @var int $Height Ширина (публичная)
     */
    public int $Height {
        get {
            return $this->pHeight;
        }
        set {
            $this->pHeight = $this->NoNegativeValues ? max($value, 0) : $value;
        }

    }

    /**
     * @var int $pWidth Длина (приватное)
     */
    private int $pWidth = 0;

    /**
     * @var int $pHeight Ширина (приватное)
     */
    private int $pHeight = 0;

    /**
     * Конструктор.
     *
     * @param int $width Длина.
     * @param int $height Ширина.
     * @param bool $noNegativeValues Значения не должны быть отрицательными.
     */
    public function __construct (int $width = 0, int $height = 0, bool $noNegativeValues = true)
    {
        $this->Width = $width;
        $this->Height = $height;
        $this->NoNegativeValues = $noNegativeValues;
    }

    /**
     * Конвертация в строку (магический метод).
     *
     * @return string Строка.
     */
    public function __toString (): string
    {
        return $this->AsString();
    }

    /**
     * Конвертация в строку (расширенный метод).
     *
     * @param string $delimiter Делитель размера.
     *
     * @return string Строка.
     */
    public function AsString (string $delimiter = self::DEFAULT_DELIMITER): string
    {
        return $this->Width . $delimiter . $this->Height;
    }

    /**
     * @inheritDoc
     */
    public function Serialize (): string
    {
        // Получаю строковое значение
        $str = $this->AsString("x");

        // Добавляю допустимы ли отрицательные значения
        $str .= $this->NoNegativeValues ? "x1" : "x0";

        // Возвращаю строку
        return $str;
    }

    /**
     * @inheritDoc
     */
    public function UnSerialize (string $serialized): void
    {
        // Десериализую строку
        $result = explode("x", $serialized);

        // Присваиваю параметры
        $this->NoNegativeValues = $result[2] === "1";

        // Объединяю длину и ширину
        $tdSize = $result[0] . self::DEFAULT_DELIMITER . $result[1];

        // Пытаюсь получить размер
        try {
            $result = self::Parse($tdSize, noNegativeValues: $this->NoNegativeValues);
        }
        catch (Exception $e) {
            $result = new TwoDimSize(noNegativeValues: $this->NoNegativeValues);
        }

        // Присваиваю длину
        $this->Width = $result->Width;

        // Присваиваю ширину
        $this->Height = $result->Height;
    }

    /**
     * Получение размера из строки.
     *
     * @param string $str Строка.
     * @param string $delimiter Разделитель размеров.
     * @param bool $noNegativeValues Значения не должны быть отрицательными.
     *
     * @return TwoDimSize Модель размеров.
     * @throws Exception Если в строке <code>str</code> не содержится символа <code>delimiter</code> или таких
     *     разделителей слишком много.
     */
    public static function Parse (string $str, string $delimiter = self::DEFAULT_DELIMITER,
        bool $noNegativeValues = true): TwoDimSize
    {
        // Разделяю значения
        $splitSizes = explode($delimiter, $str);

        // Проверяю, что массив имеет ровно два элемента
        if (count($splitSizes) != 2)
            throw new Exception(sprintf("Похоже, что в строке %s не содержится символа «%s» или таких разделителей слишком много!",
                $str, $delimiter));

        // Пытаюсь получить длину
        $width = filter_var($splitSizes[0], FILTER_VALIDATE_INT);

        // Если не удалось получить длину
        if ($width === false)
            $width = 0;

        // Пытаюсь получить ширину
        $height = filter_var($splitSizes[1], FILTER_VALIDATE_INT);

        // Если не удалось получить ширину
        if ($height === false)
            $height = 0;

        // Вывожу значение
        return new TwoDimSize($width, $height, $noNegativeValues);
    }
}