<?php

namespace Velis\Model\DataObject;

use Velis\Lang;
use Velis\Output;

/**
 * Object comparison functionality
 * @author Olek Procki <olo@velis.pl>
 */
trait CompareTrait
{
    /**
     * Diff after save
     * @var array
     */
    private $_diff;


    /**
     * Override this method to get comparison functionality
     * @return array<string, string|int>
     */
    protected function _getDiffFields()
    {
        return [];
    }


    /**
     * Compares changes between two objects
     *
     * @param self $object
     * @param array $diffFields (fields to check diff)
     * @return array
     */
    public function diff(self $object, array $diffFields = [])
    {
        $changes = [];

        foreach ($this->_getDiffFields() as $field => $fieldDescription) {
            $objectField = strtolower(Output::toSnakeCase($field));
            $sep = '"';

            if ($diffFields && !in_array($field, $diffFields)) {
                continue;
            }

            if ($object->offsetExists($objectField) && $this->offsetExists($objectField)) {
                $oldValue = $object[$objectField];
                $newValue = $this[$objectField];
            } elseif (method_exists($object, 'get' . $field)) {
                $oldValue = (string) $object->{'get' . $field}();
                $newValue = (string) $this->{'get' . $field}();
            } else {
                continue;
            }

            if (
                strtotime($oldValue) && !is_numeric($oldValue)
                && !(is_numeric(trim(substr($oldValue, 0, -1))) && ctype_alpha(substr($oldValue, -1)))
            ) {
                $oldValue = date('Y-m-d H:i', strtotime($oldValue));
                $sep = '';
            }

            if (
                strtotime($newValue) && !is_numeric($newValue)
                && !(is_numeric(trim(substr($newValue, 0, -1))) && ctype_alpha(substr($newValue, -1)))
            ) {
                $newValue = date('Y-m-d H:i', strtotime($newValue));
                $sep = '';
            }

            if (is_numeric($oldValue) && is_numeric($newValue) && (float) $oldValue === (float) $newValue) {
                continue;
            }

            if (trim($oldValue) != trim($newValue)) {
                if (is_array($fieldDescription)) {
                    $fieldDescription = print_r($fieldDescription, true);
                }

                $change = '{GENERAL_HISTORY_CHANGE} ' . $fieldDescription . ' {GENERAL_HISTORY_FROM} ' . $sep;
                $change .= (mb_strlen($oldValue) ? mb_substr($oldValue, 0, count(Lang::getLanguages()) * 100) : '{GENERAL_HISTORY_LACK}') . $sep . ' {GENERAL_HISTORY_TO} ' . $sep;
                $change .= (mb_strlen($newValue) ? mb_substr($newValue, 0, count(Lang::getLanguages()) * 100) : '{GENERAL_HISTORY_LACK}') . $sep;
                $changes[] = $change;
            }
        }

        return $changes;
    }


    /**
     * Returns changes made by last update
     */
    public function getDiff(): array
    {
        return $this->_diff ?? [];
    }


    /**
     * @param self $other
     * @return bool
     */
    public function equals(self $other)
    {
        if (static::$_sortMode == self::SORT_STRING) {
            return (string)$this == (string)$other;
        }
        return $this->identifier() == $other->identifier();
    }


    /**
     * @param self $other
     * @return bool
     */
    public function less(self $other)
    {
        if (static::$_sortMode == self::SORT_STRING) {
            return (string)$this < (string)$other;
        }

        return $this->id() < $other->id();
    }


    /**
     * @param self $other
     * @return bool
     */
    public function greater(self $other)
    {
        if (static::$_sortMode == self::SORT_STRING) {
            return (string)$this > (string)$other;
        }

        return $this->id() > $other->id();
    }


    /**
     * Compares 2 objects
     *
     * @param self $other
     * @return int
     */
    public function compareTo(self $other)
    {
        if ($this->equals($other)) {
            return 0;
        }

        if ($this->greater($other)) {
            return 1;
        }

        if ($this->less($other)) {
            return -1;
        }
    }


    /**
     * @param self $elem1
     * @param self $elem2
     * @return int
     */
    public static function compareTwo(self $elem1, self $elem2)
    {
        return $elem1->compareTo($elem2);
    }
}
