<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Theme\Model\ResourceModel;

use Magento\Framework\Stdlib\DateTime;

/**
 * Design Change Resource Model
 */
class Design extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * @var DateTime
     */
    protected $dateTime;

    /**
     * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
     * @param DateTime $dateTime
     * @param string $connectionName
     */
    public function __construct(
        \Magento\Framework\Model\ResourceModel\Db\Context $context,
        DateTime $dateTime,
        $connectionName = null
    ) {
        $this->dateTime = $dateTime;
        parent::__construct($context, $connectionName);
    }

    /**
     * Define main table and primary key
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('design_change', 'design_change_id');
    }

    /**
     * Perform actions before object save
     *
     * @param \Magento\Framework\Model\AbstractModel $object
     * @return void
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
    {
        if ($date = $object->getDateFrom()) {
            $object->setDateFrom($this->dateTime->formatDate($date));
        } else {
            $object->setDateFrom(null);
        }

        if ($date = $object->getDateTo()) {
            $object->setDateTo($this->dateTime->formatDate($date));
        } else {
            $object->setDateTo(null);
        }

        if ($object->getDateFrom() !== null
            && $object->getDateTo() !== null
            && (new \DateTime($object->getDateFrom()))->getTimestamp()
            > (new \DateTime($object->getDateTo()))->getTimestamp()
        ) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('The start date can\'t follow the end date.')
            );
        }

        $check = $this->_checkIntersection(
            $object->getStoreId(),
            $object->getDateFrom(),
            $object->getDateTo(),
            $object->getId()
        );

        if ($check) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('The date range for this design change overlaps another design change for the specified store.')
            );
        }

        if ($object->getDateFrom() === null) {
            $object->setDateFrom(new \Zend_Db_Expr('null'));
        }
        if ($object->getDateTo() === null) {
            $object->setDateTo(new \Zend_Db_Expr('null'));
        }

        parent::_beforeSave($object);
    }

    /**
     * Check intersections
     *
     * @param int $storeId
     * @param string $dateFrom
     * @param string $dateTo
     * @param int $currentId
     * @return string
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    protected function _checkIntersection($storeId, $dateFrom, $dateTo, $currentId)
    {
        $connection = $this->getConnection();
        $select = $connection->select()->from(
            ['main_table' => $this->getTable('design_change')]
        )->where(
            'main_table.store_id = :store_id'
        )->where(
            'main_table.design_change_id <> :current_id'
        );

        $dateConditions = ['date_to IS NULL AND date_from IS NULL'];

        if ($dateFrom !== null) {
            $dateConditions[] = ':date_from BETWEEN date_from AND date_to';
            $dateConditions[] = ':date_from >= date_from and date_to IS NULL';
            $dateConditions[] = ':date_from <= date_to and date_from IS NULL';
        } else {
            $dateConditions[] = 'date_from IS NULL';
        }

        if ($dateTo !== null) {
            $dateConditions[] = ':date_to BETWEEN date_from AND date_to';
            $dateConditions[] = ':date_to >= date_from AND date_to IS NULL';
            $dateConditions[] = ':date_to <= date_to AND date_from IS NULL';
        } else {
            $dateConditions[] = 'date_to IS NULL';
        }

        if ($dateFrom === null && $dateTo !== null) {
            $dateConditions[] = 'date_to <= :date_to OR date_from <= :date_to';
        }

        if ($dateFrom !== null && $dateTo === null) {
            $dateConditions[] = 'date_to >= :date_from OR date_from >= :date_from';
        }

        if ($dateFrom !== null && $dateTo !== null) {
            $dateConditions[] = 'date_from BETWEEN :date_from AND :date_to';
            $dateConditions[] = 'date_to BETWEEN :date_from AND :date_to';
        } elseif ($dateFrom === null && $dateTo === null) {
            $dateConditions = [];
        }

        if (!empty($dateConditions)) {
            $condition = '(' . implode(') OR (', $dateConditions) . ')';
            $select->where($condition);
        }

        $bind = ['store_id' => (int)$storeId, 'current_id' => (int)$currentId];

        if ($dateTo !== null) {
            $bind['date_to'] = $dateTo;
        }
        if ($dateFrom !== null) {
            $bind['date_from'] = $dateFrom;
        }

        $result = $connection->fetchOne($select, $bind);
        return $result;
    }

    /**
     * Load changes for specific store and date
     *
     * @param int $storeId
     * @param string $date
     * @return array
     */
    public function loadChange($storeId, $date)
    {
        $select = $this->getConnection()->select()->from(
            ['main_table' => $this->getTable('design_change')]
        )->where(
            'store_id = :store_id'
        )->where(
            'date_from <= :required_date or date_from IS NULL'
        )->where(
            'date_to >= :required_date or date_to IS NULL'
        );

        $bind = ['store_id' => (int)$storeId, 'required_date' => $date];

        return $this->getConnection()->fetchRow($select, $bind);
    }
}
