<?php
/*
 * Spring Signage Ltd - http://www.springsignage.com
 * Copyright (C) 2015 Spring Signage Ltd
 * (ScheduleFactory.php)
 */


namespace Xibo\Factory;


use Jenssegers\Date\Date;
use Stash\Interfaces\PoolInterface;
use Xibo\Entity\Schedule;
use Xibo\Exception\NotFoundException;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Service\DateServiceInterface;
use Xibo\Service\LogServiceInterface;
use Xibo\Service\SanitizerServiceInterface;
use Xibo\Storage\StorageServiceInterface;

/**
 * Class ScheduleFactory
 * @package Xibo\Factory
 */
class ScheduleFactory extends BaseFactory
{
    /**
     * @var ConfigServiceInterface
     */
    private $config;

    /** @var PoolInterface  */
    private $pool;

    /** @var  DateServiceInterface */
    private $dateService;

    /**
     * @var DisplayGroupFactory
     */
    private $displayGroupFactory;

    /** @var DayPartFactory */
    private $dayPartFactory;

    /**
     * Construct a factory
     * @param StorageServiceInterface $store
     * @param LogServiceInterface $log
     * @param SanitizerServiceInterface $sanitizerService
     * @param ConfigServiceInterface $config
     * @param PoolInterface $pool
     * @param DateServiceInterface $date
     * @param DisplayGroupFactory $displayGroupFactory
     * @param DayPartFactory $dayPartFactory
     */
    public function __construct($store, $log, $sanitizerService, $config, $pool, $date, $displayGroupFactory, $dayPartFactory)
    {
        $this->setCommonDependencies($store, $log, $sanitizerService);
        $this->config = $config;
        $this->pool = $pool;
        $this->dateService = $date;
        $this->displayGroupFactory = $displayGroupFactory;
        $this->dayPartFactory = $dayPartFactory;
    }

    /**
     * Create Empty
     * @return Schedule
     */
    public function createEmpty()
    {
        return new Schedule(
            $this->getStore(),
            $this->getLog(),
            $this->config,
            $this->pool,
            $this->dateService,
            $this->displayGroupFactory,
            $this->dayPartFactory
        );
    }

    /**
     * @param int $eventId
     * @return Schedule
     * @throws NotFoundException
     */
    public function getById($eventId)
    {
        $events = $this->query(null, ['disableUserCheck' => 1, 'eventId' => $eventId]);

        if (count($events) <= 0)
            throw new NotFoundException();

        return $events[0];
    }

    /**
     * @param int $displayGroupId
     * @return array[Schedule]
     * @throws NotFoundException
     */
    public function getByDisplayGroupId($displayGroupId)
    {
        return $this->query(null, ['disableUserCheck' => 1, 'displayGroupIds' => [$displayGroupId]]);
    }

    /**
     * Get by Campaign ID
     * @param int $campaignId
     * @return array[Schedule]
     * @throws NotFoundException
     */
    public function getByCampaignId($campaignId)
    {
        return $this->query(null, ['disableUserCheck' => 1, 'campaignId' => $campaignId]);
    }

    /**
     * Get by OwnerId
     * @param int $ownerId
     * @return array[Schedule]
     * @throws NotFoundException
     */
    public function getByOwnerId($ownerId)
    {
        return $this->query(null, ['disableUserCheck' => 1, 'ownerId' => $ownerId]);
    }

    /**
     * Get by DayPartId
     * @param int $dayPartId
     * @return Schedule[]
     * @throws NotFoundException
     */
    public function getByDayPartId($dayPartId)
    {
        return $this->query(null, ['disableUserCheck' => 1, 'dayPartId' => $dayPartId]);
    }

    /**
     * @param int $displayId
     * @param Date $fromDt
     * @param Date $toDt
     * @param array $options
     * @return array
     */
    public function getForXmds($displayId, $fromDt, $toDt, $options = [])
    {
        $options = array_merge(['useGroupId' => false], $options);

        // We dial the fromDt back to the top of the day, so that we include dayPart events that start on this
        // day
        $adjustedFromDt = clone $fromDt;

        $params = array(
            'fromDt' => $adjustedFromDt->startOfDay()->format('U'),
            'toDt' => $toDt->format('U')
        );

        $this->getLog()->debug('Get events for XMDS - with options: ' . json_encode($options));

        // Add file nodes to the $fileElements
        // Firstly get all the scheduled layouts
        $SQL = '
            SELECT `schedule`.eventTypeId, 
                layout.layoutId, 
                `layout`.status, 
                `command`.code, 
                schedule.fromDt, 
                schedule.toDt,
                schedule.recurrence_type AS recurrenceType,
                schedule.recurrence_detail AS recurrenceDetail,
                schedule.recurrence_range AS recurrenceRange,
                schedule.recurrenceRepeatsOn,
                schedule.lastRecurrenceWatermark,
                schedule.eventId, 
                schedule.is_priority AS isPriority,
                `schedule`.displayOrder,
                schedule.dayPartId,
                `schedule`.campaignId,
                `schedule`.commandId,
                schedule.syncTimezone,
                `campaign`.campaign,
                `command`.command,
                `lkscheduledisplaygroup`.displayGroupId,
                `daypart`.isAlways,
                `daypart`.isCustom
               FROM `schedule`
                INNER JOIN `daypart`
                ON `daypart`.dayPartId = `schedule`.dayPartId
                INNER JOIN `lkscheduledisplaygroup`
                ON `lkscheduledisplaygroup`.eventId = `schedule`.eventId
                INNER JOIN `lkdgdg`
                ON `lkdgdg`.parentId = `lkscheduledisplaygroup`.displayGroupId
        ';

        if (!$options['useGroupId']) {
            // Only join in the display/display group link table if we are requesting this data for a display
            // otherwise the group we are looking for might not have any displays, and this join would therefore
            // remove any records.
            $SQL .= '
                INNER JOIN `lkdisplaydg`
                ON lkdisplaydg.DisplayGroupID = `lkdgdg`.childId
            ';
        }

        $SQL .= '    
                LEFT OUTER JOIN `campaign`
                ON `schedule`.CampaignID = campaign.CampaignID
                LEFT OUTER JOIN `lkcampaignlayout`
                ON lkcampaignlayout.CampaignID = campaign.CampaignID
                LEFT OUTER JOIN `layout`
                ON lkcampaignlayout.LayoutID = layout.LayoutID
                  AND layout.retired = 0
                LEFT OUTER JOIN `command`
                ON `command`.commandId = `schedule`.commandId
        ';

        if ($options['useGroupId']) {
            $SQL .= ' WHERE `lkdgdg`.childId = :displayGroupId ';
            $params['displayGroupId'] = $options['displayGroupId'];
        } else {
            $SQL .= ' WHERE `lkdisplaydg`.DisplayID = :displayId ';
            $params['displayId'] = $displayId;
        }

        // Are we requesting a range or a single date/time?
        // only the inclusive range changes, but it is clearer to have the whole statement reprinted.
        // Ranged request
        $SQL .= ' 
            AND (
                  (schedule.FromDT <= :toDt AND IFNULL(`schedule`.toDt, `schedule`.fromDt) >= :fromDt) 
                  OR `schedule`.recurrence_range >= :fromDt 
                  OR (
                    IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\' 
                  )
            )
            
            ORDER BY schedule.DisplayOrder, IFNULL(lkcampaignlayout.DisplayOrder, 0), schedule.FromDT, schedule.eventId
        ';

        return $this->getStore()->select($SQL, $params);
    }

    /**
     * @param array $sortOrder
     * @param array $filterBy
     * @return array[Schedule]
     */
    public function query($sortOrder = null, $filterBy = [])
    {
        $entries = [];
        $params = [];

        $sql = '
        SELECT `schedule`.eventId, 
            `schedule`.eventTypeId,
            `schedule`.fromDt,
            `schedule`.toDt,
            `schedule`.userId,
            `schedule`.displayOrder,
            `schedule`.is_priority AS isPriority,
            `schedule`.recurrence_type AS recurrenceType,
            `schedule`.recurrence_detail AS recurrenceDetail,
            `schedule`.recurrence_range AS recurrenceRange,
            `schedule`.recurrenceRepeatsOn,
            `schedule`.lastRecurrenceWatermark,
            campaign.campaignId,
            campaign.campaign,
            `command`.commandId,
            `command`.command,
            `schedule`.dayPartId,
            `schedule`.syncTimezone,
            `daypart`.isAlways,
            `daypart`.isCustom
          FROM `schedule`
            INNER JOIN `daypart`
            ON `daypart`.dayPartId = `schedule`.dayPartId
            LEFT OUTER JOIN `campaign`
            ON campaign.CampaignID = `schedule`.CampaignID
            LEFT OUTER JOIN `command`
            ON `command`.commandId = `schedule`.commandId
          WHERE 1 = 1
        ';

        if ($this->getSanitizer()->getInt('eventId', $filterBy) !== null) {
            $sql .= ' AND `schedule`.eventId = :eventId ';
            $params['eventId'] = $this->getSanitizer()->getInt('eventId', $filterBy);
        }

        if ($this->getSanitizer()->getInt('campaignId', $filterBy) !== null) {
            $sql .= ' AND `schedule`.campaignId = :campaignId ';
            $params['campaignId'] = $this->getSanitizer()->getInt('campaignId', $filterBy);
        }

        if ($this->getSanitizer()->getInt('ownerId', $filterBy) !== null) {
            $sql .= ' AND `schedule`.userId = :ownerId ';
            $params['ownerId'] = $this->getSanitizer()->getInt('ownerId', $filterBy);
        }

        if ($this->getSanitizer()->getInt('dayPartId', $filterBy) !== null) {
            $sql .= ' AND `schedule`.dayPartId = :dayPartId ';
            $params['dayPartId'] = $this->getSanitizer()->getInt('dayPartId', $filterBy);
        }

        // Only 1 date
        if ($this->getSanitizer()->getInt('fromDt', $filterBy) !== null && $this->getSanitizer()->getInt('toDt', $filterBy) === null) {
            $sql .= ' AND schedule.fromDt > :fromDt ';
            $params['fromDt'] = $this->getSanitizer()->getInt('fromDt', $filterBy);
        }

        if ($this->getSanitizer()->getInt('toDt', $filterBy) !== null && $this->getSanitizer()->getInt('fromDt', $filterBy) === null) {
            $sql .= ' AND IFNULL(schedule.toDt, schedule.fromDt) <= :toDt ';
            $params['toDt'] = $this->getSanitizer()->getInt('toDt', $filterBy);
        }
        // End only 1 date

        // Both dates
        if ($this->getSanitizer()->getInt('fromDt', $filterBy) !== null && $this->getSanitizer()->getInt('toDt', $filterBy) !== null) {
            $sql .= ' AND schedule.fromDt < :toDt ';
            $sql .= ' AND IFNULL(schedule.toDt, schedule.fromDt) >= :fromDt ';
            $params['fromDt'] = $this->getSanitizer()->getInt('fromDt', $filterBy);
            $params['toDt'] = $this->getSanitizer()->getInt('toDt', $filterBy);
        }
        // End both dates

        if ($this->getSanitizer()->getIntArray('displayGroupIds', $filterBy) != null) {
            $sql .= ' AND `schedule`.eventId IN (SELECT `lkscheduledisplaygroup`.eventId FROM `lkscheduledisplaygroup` WHERE displayGroupId IN (' . implode(',', $this->getSanitizer()->getIntArray('displayGroupIds', $filterBy)) . ')) ';
        }

        // Future schedules?
        if ($this->getSanitizer()->getInt('futureSchedulesFrom', $filterBy) !== null && $this->getSanitizer()->getInt('futureSchedulesTo', $filterBy) === null) {
            // Get schedules that end after this date, or that recur after this date
            $sql .= ' AND (IFNULL(`schedule`.toDt, `schedule`.fromDt) >= :futureSchedulesFrom OR `schedule`.recurrence_range >= :futureSchedulesFrom OR (IFNULL(`schedule`.recurrence_range, 0) = 0) AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\') ';
            $params['futureSchedulesFrom'] = $this->getSanitizer()->getInt('futureSchedulesFrom', $filterBy);
        }

        if ($this->getSanitizer()->getInt('futureSchedulesFrom', $filterBy) !== null && $this->getSanitizer()->getInt('futureSchedulesTo', $filterBy) !== null) {
            // Get schedules that end after this date, or that recur after this date
            $sql .= ' AND ((schedule.fromDt < :futureSchedulesTo AND IFNULL(`schedule`.toDt, `schedule`.fromDt) >= :futureSchedulesFrom) OR `schedule`.recurrence_range >= :futureSchedulesFrom OR (IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\') ) ';
            $params['futureSchedulesFrom'] = $this->getSanitizer()->getInt('futureSchedulesFrom', $filterBy);
            $params['futureSchedulesTo'] = $this->getSanitizer()->getInt('futureSchedulesTo', $filterBy);
        }

        // Restrict to mediaId - meaning layout schedules of which the layouts contain the selected mediaId
        if ($this->getSanitizer()->getInt('mediaId', $filterBy) !== null) {
            $sql .= '
                AND schedule.campaignId IN (
                    SELECT `lkcampaignlayout`.campaignId
                      FROM `lkwidgetmedia`
                       INNER JOIN `widget`
                       ON `widget`.widgetId = `lkwidgetmedia`.widgetId
                       INNER JOIN `lkregionplaylist`
                       ON `lkregionplaylist`.playlistId = `widget`.playlistId
                       INNER JOIN `region`
                       ON `region`.regionId = `lkregionplaylist`.regionId
                       INNER JOIN layout
                       ON layout.LayoutID = region.layoutId
                       INNER JOIN `lkcampaignlayout`
                       ON lkcampaignlayout.layoutId = layout.layoutId
                     WHERE lkwidgetmedia.mediaId = :mediaId
                    UNION
                    SELECT `lkcampaignlayout`.campaignId
                      FROM `layout`
                       INNER JOIN `lkcampaignlayout`
                       ON lkcampaignlayout.layoutId = layout.layoutId
                     WHERE `layout`.backgroundImageId = :mediaId
                )
            ';
            $params['mediaId'] = $this->getSanitizer()->getInt('mediaId', $filterBy);
        }

        // Sorting?
        if (is_array($sortOrder))
            $sql .= 'ORDER BY ' . implode(',', $sortOrder);

        foreach ($this->getStore()->select($sql, $params) as $row) {
            $entries[] = $this->createEmpty()->hydrate($row, ['intProperties' => ['isPriority', 'syncTimezone', 'isAlways', 'isCustom']]);
        }

        return $entries;
    }
}