<?php

class Application_Model_Show
{
    private $_showId;

    public function __construct($showId = null)
    {
        $this->_showId = $showId;
    }

    public function getName()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbName();
    }

    public function setName($name)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbName($name);
        Application_Model_RabbitMq::PushSchedule();
    }

    public function setAirtimeAuthFlag($flag)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbLiveStreamUsingAirtimeAuth($flag);
        $show->save();
    }

    public function setCustomAuthFlag($flag)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbLiveStreamUsingCustomAuth($flag);
        $show->save();
    }

    public function setCustomUsername($username)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbLiveStreamUser($username);
        $show->save();
    }

    public function setCustomPassword($password)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbLiveStreamPass($password);
        $show->save();
    }

    public function getDescription()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbDescription();
    }

    public function setDescription($description)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbDescription($description);
    }

    public function getColor()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbColor();
    }

    public function setColor($color)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbColor($color);
    }

    public function getUrl()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbUrl();
    }

    /*TODO : This method is not actually used anywhere as far as I can tell. We
        can safely remove it and probably many other superfluous methods.
        --RG*/

    public function setUrl($p_url)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbUrl($p_url);
    }

    public function getGenre()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbGenre();
    }

    public function setGenre($p_genre)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbGenre($p_genre);
    }

    public function getBackgroundColor()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        $color = $show->getDbBackgroundColor();
        if (empty($color)) {
            return DEFAULT_SHOW_COLOR;
        }

        return $color;
    }

    public function setBackgroundColor($backgroundColor)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbBackgroundColor($backgroundColor);
    }

    public function getId()
    {
        return $this->_showId;
    }

    public function getHasAutoPlaylist()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbHasAutoPlaylist();
    }

    public function getAutoPlaylistRepeat()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbAutoPlaylistRepeat();
    }

    public function setAutoPlaylistRepeat($value)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbAutoPlaylistRepeat($value);
    }

    public function setHasAutoPlaylist($value)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbHasAutoPlaylist($value);
    }

    public function getAutoPlaylistId()
    {
        $show = CcShowQuery::create()->findPK($this->_showId);

        return $show->getDbAutoPlaylistId();
    }

    public function setAutoPlaylistId($playlistid)
    {
        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->setDbAutoPlaylistId($playlistid);
    }

    public function getHosts()
    {
        $sql = <<<'SQL'
SELECT first_name,
       last_name
FROM cc_show_hosts
LEFT JOIN cc_subjs ON cc_show_hosts.subjs_id = cc_subjs.id
WHERE show_id = :show_id
SQL;

        $hosts = Application_Common_Database::prepareAndExecute(
            $sql,
            [':show_id' => $this->getId()],
            'all'
        );

        $res = [];
        foreach ($hosts as $host) {
            $res[] = $host['first_name'] . ' ' . $host['last_name'];
        }

        return $res;
    }

    public function getHostsIds()
    {
        $sql = <<<'SQL'
SELECT subjs_id
FROM cc_show_hosts
WHERE show_id = :show_id
SQL;

        return Application_Common_Database::prepareAndExecute(
            $sql,
            [':show_id' => $this->getId()],
            'all'
        );
    }

    /**
     * remove everything about this show.
     */
    public function delete()
    {
        //usually we hide the show-instance, but in this case we are deleting the show template
        //so delete all show-instances as well.
        CcShowInstancesQuery::create()->filterByDbOriginalShow($this->_showId)->delete();

        $show = CcShowQuery::create()->findPK($this->_showId);
        $show->delete();
    }

    public function resizeShow($deltaDay, $deltaMin, $instanceId)
    {
        $con = Propel::getConnection();

        if ($deltaDay > 0) {
            return _('Shows can have a max length of 24 hours.');
        }

        $utcTimezone = new DateTimeZone('UTC');
        $nowDateTime = new DateTime('now', $utcTimezone);

        //keep track of cc_show_day entries we need to update
        $showDayIds = [];

        /*
         * If the resized show is an edited instance of a repeating show we
         * need to treat it as a separate show and not resize the other instances
         *
         * Also, if the resized show has edited instances, we need to exclude
         * those from the resize
         */
        $ccShow = CcShowQuery::create()->findPk($this->_showId);
        if ($ccShow->isRepeating()) {
            //convert instance to local timezone
            $ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);
            $startsDT = $ccShowInstance->getDbStarts(null);
            $timezone = $ccShow->getFirstCcShowDay()->getDbTimezone();
            $startsDT->setTimezone(new DateTimeZone($timezone));

            /* Get cc_show_day for the current instance. If we don't find one
             * we know it is a repeat interval of one of cc_show_days first
             * show and we can assume we aren't resizing a modified instance
             */
            $ccShowDay = CcShowDaysQuery::create()
                ->filterByDbFirstShow($startsDT->format('Y-m-d'))
                ->filterByDbStartTime($startsDT->format('H:i:s'))
                ->filterByDbShowId($this->_showId)
                ->findOne();

            /* Check if this cc_show_day rule is non-repeating. If it is, then
             * we know this instance was edited out of the repeating sequence
             */
            if (!$ccShowDay || $ccShowDay->getDbRepeatType() != -1) {
                $ccShowDays = $ccShow->getRepeatingCcShowDays();
                foreach ($ccShowDays as $day) {
                    array_push($showDayIds, $day->getDbId());
                }

                $excludeIds = $ccShow->getEditedRepeatingInstanceIds();

                //exlcude edited instances from resize
                $showInstances = CcShowInstancesQuery::create()
                    ->filterByDbShowId($this->_showId)
                    ->filterByDbModifiedInstance(false)
                    ->filterByDbId($excludeIds, criteria::NOT_IN)
                    ->find();
            } elseif ($ccShowDay->getDbRepeatType() == -1) {
                array_push($showDayIds, $ccShowDay->getDbId());

                //treat edited instance as separate show for resize
                $showInstances = CcShowInstancesQuery::create()
                    ->filterByDbId($instanceId)
                    ->find();
            }
        } else {
            $ccShowDays = $ccShow->getCcShowDayss();
            foreach ($ccShowDays as $day) {
                array_push($showDayIds, $day->getDbId());
            }

            $showInstances = CcShowInstancesQuery::create()
                ->filterByDbShowId($this->_showId)
                ->find($con);
        }

        /* Check two things:
           1. If the show being resized and any of its repeats end in the past
           2. If the show being resized and any of its repeats overlap
              with other scheduled shows */

        //keep track of instance ids for update show instances start/end times
        $instanceIds = [];
        $displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());

        //check if new show time overlaps with any other shows
        foreach ($showInstances as $si) {
            array_push($instanceIds, $si->getDbId());

            $startsDateTime = $si->getDbStarts(null);
            $endsDateTime = $si->getDbEnds(null);

            /* The user is moving the show on the calendar from the perspective
                of local time.  * incase a show is moved across a time change
                border offsets should be added to the local * timestamp and
                then converted back to UTC to avoid show time changes */
            $startsDateTime->setTimezone($displayTimezone);
            $endsDateTime->setTimezone($displayTimezone);

            //$newStartsDateTime = Application_Model_ShowInstance::addDeltas($startsDateTime, $deltaDay, $deltaMin);
            $newEndsDateTime = Application_Model_ShowInstance::addDeltas($endsDateTime, $deltaDay, $deltaMin);

            if ($newEndsDateTime->getTimestamp() < $nowDateTime->getTimestamp()) {
                return _('End date/time cannot be in the past');
            }

            //convert our new starts/ends to UTC.
            //$newStartsDateTime->setTimezone($utc);
            $newEndsDateTime->setTimezone($utcTimezone);

            $overlapping = Application_Model_Schedule::checkOverlappingShows(
                $startsDateTime,
                $newEndsDateTime,
                true,
                $si->getDbId()
            );

            if ($overlapping) {
                return _("Cannot schedule overlapping shows.\nNote: Resizing a repeating show " .
                       'affects all of its repeats.');
            }
        }

        $hours = $deltaMin / 60;
        $hours = ($hours > 0) ? floor($hours) : ceil($hours);
        $mins = abs($deltaMin % 60);

        $sql_gen = 'UPDATE cc_show_instances ' .
            'SET ends = (ends + :deltaDay1::INTERVAL + :interval1::INTERVAL) ' .
            'WHERE (id IN (' . implode(',', $instanceIds) . ') ' .
            'AND ends > :current_timestamp1) ' .
            "AND ((ends + :deltaDay2::INTERVAL + :interval2::INTERVAL - starts) <= interval '24:00')";

        Application_Common_Database::prepareAndExecute(
            $sql_gen,
            [
                ':deltaDay1' => "{$deltaDay} days",
                ':interval1' => "{$hours}:{$mins}",
                ':current_timestamp1' => $nowDateTime->format(DEFAULT_TIMESTAMP_FORMAT),
                ':deltaDay2' => "{$deltaDay} days",
                ':interval2' => "{$hours}:{$mins}",
            ],
            'execute'
        );

        $sql_gen = 'UPDATE cc_show_days ' .
            'SET duration = (CAST(duration AS interval) + :deltaDay3::INTERVAL + :interval3::INTERVAL) ' .
            'WHERE id IN (' . implode(',', $showDayIds) . ') ' .
            "AND ((CAST(duration AS interval) + :deltaDay4::INTERVAL + :interval4::INTERVAL) <= interval '24:00')";

        Application_Common_Database::prepareAndExecute(
            $sql_gen,
            [
                ':deltaDay3' => "{$deltaDay} days",
                ':interval3' => "{$hours}:{$mins}",
                ':deltaDay4' => "{$deltaDay} days",
                ':interval4' => "{$hours}:{$mins}",
            ],
            'execute'
        );

        $con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
        $con->beginTransaction();

        try {
            //update the status flag in cc_schedule.

            /* Since we didn't use a propel object when updating
             * cc_show_instances table we need to clear the instances
             * so the correct information is retrieved from the db
             */
            CcShowInstancesPeer::clearInstancePool();

            $instances = CcShowInstancesQuery::create()
                ->filterByDbEnds($nowDateTime->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::GREATER_THAN)
                ->filterByDbId($instanceIds, Criteria::IN)
                ->find($con);

            foreach ($instances as $instance) {
                $instance->updateScheduleStatus($con);
            }

            $con->commit();
        } catch (Exception $e) {
            $con->rollback();
            Logging::info("Couldn't update schedule status.");
            Logging::info($e->getMessage());
        }

        Application_Model_RabbitMq::PushSchedule();
    }

    public function cancelShow($day_timestamp)
    {
        $timeinfo = explode(' ', $day_timestamp);

        CcShowDaysQuery::create()
            ->filterByDbShowId($this->_showId)
            ->update(['DbLastShow' => $timeinfo[0]]);

        $sql = <<<'SQL'
SELECT id from cc_show_instances
WHERE starts >= :dayTimestamp::TIMESTAMP
  AND show_id = :showId
SQL;

        $rows = Application_Common_Database::prepareAndExecute($sql, [
            ':dayTimestamp' => $day_timestamp,
            ':showId' => $this->getId(), ], 'all');

        foreach ($rows as $row) {
            try {
                $showInstance = new Application_Model_ShowInstance($row['id']);
                $showInstance->delete($rabbitmqPush = false);
            } catch (Exception $e) {
                Logging::info($e->getMessage());
            }
        }

        Application_Model_RabbitMq::PushSchedule();
    }

    /**
     * Check whether the current show originated
     * from a recording.
     *
     * @return bool
     *              true if originated from recording, otherwise false
     */
    public function isRecorded()
    {
        $showInstancesRow = CcShowInstancesQuery::create()
            ->filterByDbShowId($this->getId())
            ->filterByDbRecord(1)
            ->filterByDbModifiedInstance(false)
            ->findOne();

        return !is_null($showInstancesRow);
    }

    /**
     * Check whether the current show has rebroadcasts of a recorded
     * show. Should be used in conjunction with isRecorded().
     *
     * @return bool
     *              true if show has rebroadcasts, otherwise false
     */
    public function isRebroadcast()
    {
        $showInstancesRow = CcShowInstancesQuery::create()
            ->filterByDbShowId($this->_showId)
            ->filterByDbRebroadcast(1)
            ->filterByDbModifiedInstance(false)
            ->findOne();

        return !is_null($showInstancesRow);
    }

    /**
     * Get start time and relative start date for a recorded
     * shows rebroadcasts. For example start date format would be
     * "x days" and time would HH:MM:SS.
     *
     * @return array
     *               array of associate arrays containing "day_offset" and "start_time"
     */
    public function getRebroadcastsRelative()
    {
        $sql = <<<'SQL'
SELECT day_offset,
       start_time
FROM cc_show_rebroadcast
WHERE show_id = :showId
ORDER BY day_offset
SQL;

        return Application_Common_Database::prepareAndExecute(
            $sql,
            [':showId' => $this->getId()],
            'all'
        );
    }

    /**
     * Check whether the current show is set to repeat
     * repeating shows.
     *
     * @return bool
     *              true if repeating shows, otherwise false
     */
    public function isRepeating()
    {
        $showDaysRow = CcShowDaysQuery::create()
            ->filterByDbShowId($this->_showId)
            ->findOne();

        if (!is_null($showDaysRow)) {
            return $showDaysRow->getDbRepeatType() != -1;
        }

        return false;
    }

    /**
     * Get the repeat type of the show. Show can have repeat type of
     * "weekly", "every 2 weeks", "monthly", "monthly on the same weekday",
     * "every 3 weeks" and "every 4 weeks". These values are represented
     * by 0, 1, 2, 3, 4 and 5, respectively.
     *
     * @return int
     *             Return the integer corresponding to the repeat type
     */
    public function getRepeatType()
    {
        $showDaysRow = CcShowDaysQuery::create()
            ->filterByDbShowId($this->_showId)
            ->findOne();

        if (!is_null($showDaysRow)) {
            return $showDaysRow->getDbRepeatType();
        }

        return -1;
    }

    /**
     * Get the end date for a repeating show in the format yyyy-mm-dd.
     *
     * @return string
     *                Return the end date for the repeating show or the empty
     *                string if there is no end
     */
    public function getRepeatingEndDate()
    {
        $sql = <<<'SQL'
SELECT last_show
FROM cc_show_days
WHERE show_id = :showId
ORDER BY last_show DESC
SQL;

        $query = Application_Common_Database::prepareAndExecute(
            $sql,
            ['showId' => $this->getId()],
            'column'
        );

        // TODO: Why return empty string instead of false? very confusing --RG
        return ($query !== false) ? $query : '';
    }

    /**
     * Deletes all future instances of the current show object
     * from the show_instances table. This function is used when
     * a show is being edited - in some cases, when a show is edited
     * we just destroy all future show instances, and let another function
     * regenerate them later on. Note that this isn't always the most
     * desirable thing to do. Deleting a show instance and regenerating
     * it cause any scheduled playlists within those show instances to
     * be gone for good.
     */
    public function deleteAllInstances()
    {
        $sql = <<<'SQL'
DELETE
FROM cc_show_instances
WHERE starts > :timestamp::TIMESTAMP
  AND show_id = :showId
SQL;
        Application_Common_Database::prepareAndExecute(
            $sql,
            [':timestamp' => gmdate(DEFAULT_TIMESTAMP_FORMAT),
                ':showId' => $this->getId(), ],
            'execute'
        );
    }

    /**
     * Deletes all future rebroadcast instances of the current
     * show object from the show_instances table.
     */
    public function deleteAllRebroadcasts()
    {
        $sql = <<<'SQL'
DELETE
FROM cc_show_instances
WHERE starts > :timestamp::TIMESTAMP
  AND show_id :showId
  AND rebroadcast 1
SQL;
        Application_Common_Database::prepareAndExecute(
            $sql,
            [':showId' => $this->getId(),
                ':timestamp' => gmdate(DEFAULT_TIMESTAMP_FORMAT), ],
            'execute'
        );
    }

    /**
     * Get the start date of the current show in UTC timezone.
     *
     * @return string
     *                The start date in the format YYYY-MM-DD or empty string in case
     *                start date could not be found
     */
    public function getStartDateAndTime()
    {
        $con = Propel::getConnection();

        $showId = $this->getId();
        $stmt = $con->prepare(
            'SELECT first_show, start_time, timezone FROM cc_show_days'
            . ' WHERE show_id = :showId'
            . ' ORDER BY first_show'
            . ' LIMIT 1'
        );

        $stmt->bindParam(':showId', $showId);
        $stmt->execute();

        if (!$stmt) {
            return '';
        }

        $rows = $stmt->fetchAll();
        $row = $rows[0];

        $dt = new DateTime($row['first_show'] . ' ' . $row['start_time'], new DateTimeZone($row['timezone']));
        $dt->setTimezone(new DateTimeZone('UTC'));

        return $dt->format('Y-m-d H:i');
    }

    /**
     * Get the start date of the current show in UTC timezone.
     *
     * @return string
     *                The start date in the format YYYY-MM-DD
     */
    public function getStartDate()
    {
        [$date] = explode(' ', $this->getStartDateAndTime());

        return $date;
    }

    /**
     * Get the start time of the current show in UTC timezone.
     *
     * @return string
     *                The start time in the format HH:MM
     */
    public function getStartTime()
    {
        [, $time] = explode(' ', $this->getStartDateAndTime());

        return $time;
    }

    /**
     * Get the end date of the current show.
     * Note that this is not the end date of repeated show.
     *
     * @return string
     *                The end date in the format YYYY-MM-DD
     */
    public function getEndDate()
    {
        $startDate = $this->getStartDate();
        $startTime = $this->getStartTime();
        $duration = $this->getDuration();

        $startDateTime = new DateTime($startDate . ' ' . $startTime);
        $duration = explode(':', $duration);

        $endDate = $startDateTime->add(new DateInterval('PT' . $duration[0] . 'H' . $duration[1] . 'M'));

        return $endDate->format('Y-m-d');
    }

    /**
     * Get the end time of the current show.
     *
     * @return string
     *                The start time in the format HH:MM:SS
     */
    public function getEndTime()
    {
        $startDate = $this->getStartDate();
        $startTime = $this->getStartTime();
        $duration = $this->getDuration();

        $startDateTime = new DateTime($startDate . ' ' . $startTime);
        $duration = explode(':', $duration);

        $endDate = $startDateTime->add(new DateInterval('PT' . $duration[0] . 'H' . $duration[1] . 'M'));

        return $endDate->format('H:i:s');
    }

    /**
     * Indicate whether the starting point of the show is in the
     * past.
     *
     * @return bool
     *              true if the StartDate is in the past, false otherwise
     */
    public function isStartDateTimeInPast()
    {
        return gmdate(DEFAULT_TIMESTAMP_FORMAT) > ($this->getStartDate() . ' ' . $this->getStartTime());
    }

    /**
     * Get the ID's of future instance of the current show.
     *
     * @return array
     *               A simple array containing all ID's of show instance
     *               scheduled in the future
     */
    public function getAllFutureInstanceIds()
    {
        $sql = <<<'SQL'
SELECT id
FROM cc_show_instances
WHERE show_id = :showId
  AND starts > :timestamp::TIMESTAMP
  AND modified_instance != TRUE
SQL;
        $rows = Application_Common_Database::prepareAndExecute(
            $sql,
            [':showId' => $this->getId(),
                ':timestamp' => gmdate(DEFAULT_TIMESTAMP_FORMAT), ],
            'all'
        );

        $res = [];
        foreach ($rows as $r) {
            $res[] = $r['id'];
        }

        return $res;
    }

    /* Called when a show's duration is changed (edited).
     *
     * @param array $p_data
     *      array containing the POST data about the show from the
     *      browser.
     *
     */
    private function updateDurationTime($p_data)
    {
        //need to update cc_show_instances, cc_show_days
        $con = Propel::getConnection();
        $timestamp = gmdate(DEFAULT_TIMESTAMP_FORMAT);

        $stmt = $con->prepare('UPDATE cc_show_days '
                 . 'SET duration = :add_show_duration '
                 . 'WHERE show_id = :add_show_id');
        $stmt->execute([
            ':add_show_duration' => $p_data['add_show_duration'],
            ':add_show_id' => $p_data['add_show_id'],
        ]);

        $sql = <<<'SQL'
UPDATE cc_show_instances
SET ends = starts + :add_show_duration::INTERVAL
WHERE show_id = :show_id
  AND ends > :timestamp::TIMESTAMP
SQL;

        Application_Common_Database::prepareAndExecute($sql, [
            ':add_show_duration' => $p_data['add_show_duration'],
            ':show_id' => $p_data['add_show_id'],
            ':timestamp' => $timestamp, ], 'execute');
    }

    public function getDuration($format = false)
    {
        $showDay = CcShowDaysQuery::create()->filterByDbShowId($this->getId())->findOne();
        if (!$format) {
            return $showDay->getDbDuration();
        }
        $info = explode(':', $showDay->getDbDuration());

        return str_pad(intval($info[0]), 2, '0', STR_PAD_LEFT) . 'h ' . str_pad(intval($info[1]), 2, '0', STR_PAD_LEFT) . 'm';
    }

    public function getShowDays()
    {
        $showDays = CcShowDaysQuery::create()->filterByDbShowId(
            $this->getId()
        )->find();
        $res = [];
        foreach ($showDays as $showDay) {
            $res[] = $showDay->getDbDay();
        }

        return $res;
    }

    /* Only used for shows that aren't repeating.
     *
     * @return Boolean: true if show has an instance, otherwise false. */
    public function hasInstance()
    {
        return !is_null($this->getInstance());
    }

    /* Only used for shows that aren't repeating.
     *
     * @return CcShowInstancesQuery: An propel object representing a
     *      row in the cc_show_instances table. */
    public function getInstance()
    {
        return CcShowInstancesQuery::create()
            ->filterByDbShowId($this->getId())
            ->findOne();
    }

    /**
     *  returns info about live stream override info.
     */
    public function getLiveStreamInfo()
    {
        $info = [];
        if ($this->getId() == null) {
            return $info;
        }
        $ccShow = CcShowQuery::create()->findPK($this->_showId);
        $info['custom_username'] = $ccShow->getDbLiveStreamUser();
        $info['cb_airtime_auth'] = $ccShow->getDbLiveStreamUsingAirtimeAuth();
        $info['cb_custom_auth'] = $ccShow->getDbLiveStreamUsingCustomAuth();
        $info['custom_username'] = $ccShow->getDbLiveStreamUser();
        $info['custom_password'] = $ccShow->getDbLiveStreamPass();

        return $info;
    }

    /* Only used for shows that are repeating. Note that this will return
     * true even for dates that only have a "modified" show instance (does not
     * check if the "modified_instance" column is set to true). This is intended
     * behaviour.
     *
     * @param $p_dateTime: Date for which we are checking if instance
     * exists.
     *
     * @return Boolean: true if show has an instance on $p_dateTime,
     *      otherwise false. */
    public function hasInstanceOnDate($p_dateTime)
    {
        return !is_null($this->getInstanceOnDate($p_dateTime));
    }

    /* Only used for shows that are repeating. Note that this will return
     * shows that have been "modified" (does not check if the "modified_instance"
     * column is set to true). This is intended behaviour.
     *
     * @param $p_dateTime: Date for which we are getting an instance.
     *
     * @return CcShowInstancesQuery: An propel object representing a
     *      row in the cc_show_instances table. */
    public function getInstanceOnDate($p_dateTime)
    {
        $timestamp = $p_dateTime->format(DEFAULT_TIMESTAMP_FORMAT);
        $sql = <<<'SQL'
SELECT id
FROM cc_show_instances
WHERE date(starts) = date(:timestamp::TIMESTAMP)
  AND show_id = :showId
  AND rebroadcast = 0;
SQL;

        try {
            $row = Application_Common_Database::prepareAndExecute(
                $sql,
                [':showId' => $this->getId(),
                    ':timestamp' => $timestamp, ],
                'column'
            );

            return CcShowInstancesQuery::create()
                ->findPk($row);
        } catch (Exception $e) {
            return null;
        }
    }

    /**
     * Creates show instances that are scheduled after the shows_populated_until
     * value in cc_pref. If the show instances are linked their show content will
     * get filled.
     *
     * This can occur when a user is viewing the next day/week/month in the
     * calendar or when pypo is requesting the schedule after the shows_populated_until
     * date and time.
     *
     * @param mixed $needScheduleUntil
     */
    public static function createAndFillShowInstancesPastPopulatedUntilDate($needScheduleUntil)
    {
        $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME);

        try {
            $con->beginTransaction();
            //It is extremely important that we increase the transaction isolation level, so that if two
            //requests cause the show schedule to be generated at the same time, one will be rolled back.
            $con->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');

            //UTC DateTime object
            $showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil();
            //if application is requesting shows past our previous populated until date, generate shows up until this point.
            if (is_null($showsPopUntil) || $showsPopUntil->getTimestamp() < $needScheduleUntil->getTimestamp()) {
                $service_show = new Application_Service_ShowService();
                $ccShow = $service_show->delegateInstanceCreation(null, $needScheduleUntil, true);
                Application_Model_Preference::SetShowsPopulatedUntil($needScheduleUntil);
            }
            $con->commit();
        } catch (Exception $e) {
            $con->rollBack();
            //throw $e;
            Logging::warn('Did not create show instances due to transaction error. This is usually safe
                           and caused by two concurrent transactions. ' . $e->getMessage());
        }
    }

    /**
     * Get all the show instances in the given time range (inclusive).
     *
     * @param dateTime     $start_timestamp
     *                                      In UTC time
     * @param dateTime     $end_timestamp
     *                                      In UTC time
     * @param unknown_type $excludeInstance
     * @param bool         $onlyRecord
     * @param int          $showId
     *                                      limits the results to instances of a given showId only
     *
     * @return array
     */
    public static function getShows($start_timestamp, $end_timestamp, $onlyRecord = false, $showId = null)
    {
        self::createAndFillShowInstancesPastPopulatedUntilDate($end_timestamp);

        $sql = <<<'SQL'
SELECT si1.starts            AS starts,
       si1.ends              AS ends,
       si1.record            AS record,
       si1.rebroadcast       AS rebroadcast,
       si2.starts            AS parent_starts,
       si1.instance_id       AS record_id,
       si1.show_id           AS show_id,
       show.name             AS name,
       show.description      AS description,
       show.color            AS color,
       show.background_color AS background_color,
       show.image_path       AS image_path,
       show.linked           AS linked,
       si1.file_id           AS file_id,
       si1.id                AS instance_id,
       si1.description       AS instance_description,
       si1.created           AS created,
       si1.last_scheduled    AS last_scheduled,
       si1.time_filled       AS time_filled
FROM cc_show_instances      AS si1
LEFT JOIN cc_show_instances AS si2  ON si1.instance_id = si2.id
LEFT JOIN cc_show           AS show ON show.id         = si1.show_id
WHERE si1.modified_instance = FALSE
SQL;
        //only want shows that are starting at the time or later.
        $start_string = $start_timestamp->format(DEFAULT_TIMESTAMP_FORMAT);
        $end_string = $end_timestamp->format(DEFAULT_TIMESTAMP_FORMAT);

        $params = [];

        if ($showId) {
            $sql .= ' AND (si1.show_id = :show_id)';
            $params[':show_id'] = $showId;
        }

        if ($onlyRecord) {
            $sql .= ' AND (si1.starts >= :start::TIMESTAMP AND si1.starts < :end::TIMESTAMP)';
            $sql .= ' AND (si1.record = 1)';

            $params[':start'] = $start_string;
            $params[':end'] = $end_string;

            return Application_Common_Database::prepareAndExecute($sql, $params, 'all');
        }
        $sql .= ' ' . <<<'SQL'
AND ((si1.starts >= :start1::TIMESTAMP AND si1.starts < :end1::TIMESTAMP)
     OR (si1.ends > :start2::TIMESTAMP AND si1.ends <= :end2::TIMESTAMP)
     OR (si1.starts <= :start3::TIMESTAMP AND si1.ends >= :end3::TIMESTAMP))
ORDER BY si1.starts
SQL;
        $params = array_merge(
            $params,
            [
                'start1' => $start_string,
                'start2' => $start_string,
                'start3' => $start_string,
                'end1' => $end_string,
                'end2' => $end_string,
                'end3' => $end_string,
            ]
        );

        return Application_Common_Database::prepareAndExecute($sql, $params, 'all');
    }

    private static function setNextPop($next_date, $show_id, $day)
    {
        $nextInfo = explode(' ', $next_date);

        $repeatInfo = CcShowDaysQuery::create()
            ->filterByDbShowId($show_id)
            ->filterByDbDay($day)
            ->findOne();

        $repeatInfo->setDbNextPopDate($nextInfo[0])
            ->save();
    }

    /**
     * @param DateTime $start
     *                             -in UTC time
     * @param DateTime $end
     *                             -in UTC time
     * @param bool     $editable
     * @param mixed    $p_start
     * @param mixed    $p_end
     * @param mixed    $p_editable
     */
    public static function &getFullCalendarEvents($p_start, $p_end, $p_editable = false)
    {
        $events = [];
        $interval = $p_start->diff($p_end);
        $days = $interval->format('%a');
        $shows = Application_Model_Show::getShows($p_start, $p_end);
        $content_count = Application_Model_ShowInstance::getContentCount(
            $p_start,
            $p_end
        );
        $isFull = Application_Model_ShowInstance::getIsFull($p_start, $p_end);
        $hasAutoPlaylist = Application_Model_ShowInstance::getShowHasAutoplaylist($p_start, $p_end);
        $displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
        $utcTimezone = new DateTimeZone('UTC');
        $now = new DateTime('now', $utcTimezone);

        foreach ($shows as &$show) {
            $options = [];

            $options['percent'] = Application_Model_Show::getPercentScheduled($show['starts'], $show['ends'], $show['time_filled']);

            if (isset($show['parent_starts'])) {
                $parentStartsDT = new DateTime($show['parent_starts'], $utcTimezone);
            }

            $startsDT = DateTime::createFromFormat(
                'Y-m-d G:i:s',
                $show['starts'],
                $utcTimezone
            );
            $endsDT = DateTime::createFromFormat(
                'Y-m-d G:i:s',
                $show['ends'],
                $utcTimezone
            );

            if ($p_editable) {
                if ($show['record'] && $now > $startsDT) {
                    $options['editable'] = false;
                } elseif ($show['rebroadcast']
                    && $now > $parentStartsDT) {
                    $options['editable'] = false;
                } elseif ($now < $endsDT) {
                    $options['editable'] = true;
                }
            }

            $startsDT->setTimezone($displayTimezone);
            $endsDT->setTimezone($displayTimezone);

            $options['show_empty'] = (array_key_exists(
                $show['instance_id'],
                $content_count
            )) ? 0 : 1;

            if (array_key_exists($show['instance_id'], $isFull)) {
                $options['show_partial_filled'] = !$isFull[$show['instance_id']];
            } else {
                $options['show_partial_filled'] = true;
            }
            if (array_key_exists($show['instance_id'], $hasAutoPlaylist)) {
                $options['show_has_auto_playlist'] = true;
            } else {
                $options['show_has_auto_playlist'] = false;
            }

            $event = [];

            $event['id'] = intval($show['instance_id']);
            $event['title'] = $show['name'];
            $event['start'] = $startsDT->format(DEFAULT_TIMESTAMP_FORMAT);
            $event['end'] = $endsDT->format(DEFAULT_TIMESTAMP_FORMAT);
            $event['allDay'] = false;
            $event['showId'] = intval($show['show_id']);
            $event['linked'] = intval($show['linked']);
            $event['record'] = intval($show['record']);
            $event['rebroadcast'] = intval($show['rebroadcast']);

            //for putting the now playing icon on the show.
            if ($now > $startsDT && $now < $endsDT) {
                $event['nowPlaying'] = true;
            } else {
                $event['nowPlaying'] = false;
            }

            if (!empty($show['background_color'])) {
                $event['color'] = '#' . $show['background_color'];
            } else {
                $event['color'] = '#' . self::getDefaultBackgroundColor($startsDT); //DEFAULT_SHOW_COLOR;
            }

            //event colouring
            if ($show['color'] != '') {
                $event['textColor'] = '#' . $show['color'];
            } else {
                $bg = $event['color'];
                //Calculate the text colour (black or white) based on the brightness of the background.
                $r = intval(substr($bg, 1, 2), 16);
                $g = intval(substr($bg, 3, 2), 16);
                $b = intval(substr($bg, 5, 2), 16);
                $brightness = 0.299 * floatval($r) + 0.587 * floatval($g) + 0.114 * floatval($b);
                if ($brightness > 130) {
                    $event['textColor'] = '#000000';
                } else {
                    $event['textColor'] = '#fcfcfc';
                }
            }

            foreach ($options as $key => $value) {
                $event[$key] = $value;
            }

            $events[] = $event;
        }

        return $events;
    }

    /** Get a palettized colour for the show. */
    private static function getDefaultBackgroundColor($date)
    {
        $basePalette = ['A22BE8', '2FFF8D', 'FF743C', '2ED4FF', 'E8D82B'];
        // 'B23F11', 'FF7E4A', 'FF6C31'

        /*
        $palette = [['42d5a1', '56bd99', '65ab93', '7b938b'],
                    ['42a4d5', '569bbd', '6594ab', '7b8b93'],
                    ['4264d5', '566fbd', '6576ab', '7b8193']];
        */
        $palette = [];
        for ($baseColorIdx = 0; $baseColorIdx < count($basePalette); ++$baseColorIdx) {
            $dayPalette = [];
            for ($shade = 0.0; $shade < 0.8; $shade += 0.1) {
                $origColour = $basePalette[$baseColorIdx];
                $r = intval(substr($origColour, 0, 2), 16);
                $g = intval(substr($origColour, 2, 2), 16);
                $b = intval(substr($origColour, 4, 2), 16);
                $r = floatval($r) * (1.0 - $shade);
                $g = floatval($g) * (1.0 - $shade);
                $b = floatval($b) * (1.0 - $shade);
                $color = sprintf('%02x%02x%02x', $r, $g, $b);
                array_push($dayPalette, $color);
            }
            array_push($palette, $dayPalette);
        }

        //$hashValue = (md5($date->format('d'))[0] % $cols) + ((intval($date->format('h'))/24) % $rows);
        $row = intval($date->format('w')) % count($palette);
        $foo = $date->format('H');
        $col = intval(intval($date->format('H')) / 24.0 * count($palette[0]));
        //$color = $palette[$hashValue % sizeof($palette)];
        return $palette[$row][$col];
    }

    /**
     * Calculates the percentage of a show scheduled given the start and end times in date/time format
     * and the time_filled as the total time the schow is scheduled for in time format.
     *
     * TODO when using propel properly this should be a method on the propel show instance model.
     *
     * @param mixed $p_starts
     * @param mixed $p_ends
     * @param mixed $p_time_filled
     */
    private static function getPercentScheduled($p_starts, $p_ends, $p_time_filled)
    {
        $utcTimezone = new DateTimeZone('UTC');
        $startDt = new DateTime($p_starts, $utcTimezone);
        $endDt = new DateTime($p_ends, $utcTimezone);
        $durationSeconds = intval($endDt->format('U')) - intval($startDt->format('U'));
        $time_filled = Application_Common_DateHelper::playlistTimeToSeconds($p_time_filled);
        if ($durationSeconds != 0) { //Prevent division by zero if the show duration somehow becomes zero.
            $percent = ceil(($time_filled / $durationSeconds) * 100);
        } else {
            $percent = 0;
        }

        return $percent;
    }

    /**
     * Given time $timeNow, returns the show being played right now.
     * Times are all in UTC time.
     *
     * @param string $timeNow - current time (in UTC)
     *
     * @return array - show being played right now
     */
    public static function getCurrentShow($timeNow = null)
    {
        $con = Propel::getConnection();
        if ($timeNow == null) {
            $timeNow = gmdate(DEFAULT_TIMESTAMP_FORMAT);
        }
        //TODO, returning starts + ends twice (once with an alias). Unify this after the 2.0 release. --Martin
        $sql = <<<'SQL'
SELECT si.starts AS start_timestamp,
       si.ends AS end_timestamp,
       s.name,
       s.description,
       s.id,
       si.id AS instance_id,
       si.description AS instance_description,
       si.record,
       s.url,
       s.image_path,
       starts,
       ends
FROM cc_show_instances si
     LEFT JOIN cc_show s
     ON si.show_id = s.id
WHERE si.show_id = s.id
  AND si.starts <= :timeNow1::timestamp
  AND si.ends > :timeNow2::timestamp
  AND modified_instance != TRUE
SQL;

        $stmt = $con->prepare($sql);
        $stmt->bindParam(':timeNow1', $timeNow);
        $stmt->bindParam(':timeNow2', $timeNow);

        if ($stmt->execute()) {
            $rows = $stmt->fetchAll();
        } else {
            $msg = implode(',', $stmt->errorInfo());

            throw new Exception("Error: {$msg}");
        }

        return $rows;
    }

    /**
     * Gets the current show, previous and next with an n-day window from
     * the given timeNow, so timeNow-2days and timeNow+$daysToRetrieve days.
     *
     * @param $utcNow A DateTime object containing the current time in UTC
     * @param mixed $utcEndStr
     * @param mixed $showsToRetrieve
     *
     * @return An array containing the previous show,
     *            current show, and next show
     */
    public static function getPrevCurrentNext($utcNow, $utcEndStr, $showsToRetrieve)
    {
        $timeZone = new DateTimeZone('UTC'); //This function works entirely in UTC.
        assert(get_class($utcNow) === 'DateTime');
        assert($utcNow->getTimeZone() == $timeZone);

        $CC_CONFIG = Config::getConfig();
        $con = Propel::getConnection();

        // This will fetch the currently playing show first, then any
        // upcoming shows within our interval, and finally move on to
        // previous shows in the past 2 days.
        $sql = <<<'SQL'
SELECT s.name,
       s.description,
       s.genre,
       s.id,
       si.id AS instance_id,
       si.record,
       s.url,
       s.image_path,
       starts,
       ends
FROM cc_show_instances si
     LEFT JOIN cc_show s
     ON si.show_id = s.id
WHERE si.show_id = s.id
  AND si.starts >= :timeNow::timestamp - INTERVAL '2 days'
  AND si.starts < :timeEnd::timestamp
  AND modified_instance != TRUE
ORDER BY
  CASE
    WHEN si.ends > :timeNow::timestamp
      AND  si.starts < :timeNow::timestamp THEN 1
    WHEN si.starts > :timeNow::timestamp THEN 2
    ELSE 3
  END, si.starts
LIMIT :lim
SQL;

        $stmt = $con->prepare($sql);

        $utcNowStr = $utcNow->format(DEFAULT_TIMESTAMP_FORMAT);
        $stmt->bindValue(':timeNow', $utcNowStr);
        $stmt->bindValue(':timeEnd', $utcEndStr);
        $stmt->bindValue(':lim', $showsToRetrieve);

        if ($stmt->execute()) {
            // use PDO::FETCH_ASSOC to only get the associative values
            // note that fetchAll() defaults to PDO::FETCH_BOTH, which we don't want
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } else {
            $msg = implode(',', $stmt->errorInfo());

            throw new Exception("Error: {$msg}");
        }

        $numberOfRows = count($rows);

        $results['previousShow'] = [];
        $results['currentShow'] = null;
        $results['nextShow'] = [];

        for ($i = 0; $i < $numberOfRows; ++$i) {
            // all shows start/end times are stored in the database as UTC.
            $showStartTime = new DateTime($rows[$i]['starts'], $timeZone);
            $showEndTime = new DateTime($rows[$i]['ends'], $timeZone);

            // Find the show that is within the current time.
            if (($showStartTime <= $utcNow) && ($showEndTime > $utcNow)) {
                $results['currentShow'] = $rows[$i];
            } elseif ($showEndTime < $utcNow) {
                array_push($results['previousShow'], $rows[$i]);
            } elseif ($showStartTime > $utcNow) {
                array_push($results['nextShow'], $rows[$i]);
            }
        }

        return $results;
    }

    /**
     * Gets the current show, previous and next with an 2day window from
     * the given timeNow, so timeNow-2days and timeNow+2days.
     *
     * @param $utcNow A DateTime object containing the current time in UTC
     *
     * @return An array (with stupid sub-arrays) containing the previous show id,
     *            current show id, and next show id
     *
     * @deprecated
     */
    public static function getPrevCurrentNextOld($utcNow)
    {
        $timeZone = new DateTimeZone('UTC'); //This function works entirely in UTC.
        assert(get_class($utcNow) === 'DateTime');
        assert($utcNow->getTimeZone() == $timeZone);

        $CC_CONFIG = Config::getConfig();
        $con = Propel::getConnection();

        //TODO, returning starts + ends twice (once with an alias). Unify this after the 2.0 release. --Martin
        $sql = <<<'SQL'
SELECT si.starts AS start_timestamp,
       si.ends AS end_timestamp,
       s.name,
       s.description,
       s.id,
       si.id AS instance_id,
       si.record,
       s.url,
       s.image_path,
       starts,
       ends
FROM cc_show_instances si
     LEFT JOIN cc_show s
     ON si.show_id = s.id
WHERE si.show_id = s.id
  AND si.starts > :timeNow1::timestamp - INTERVAL '2 days'
  AND si.ends < :timeNow2::timestamp + INTERVAL '2 days'
  AND modified_instance != TRUE
ORDER BY si.starts
SQL;

        $stmt = $con->prepare($sql);

        $utcNowStr = $utcNow->format(DEFAULT_TIMESTAMP_FORMAT);
        $stmt->bindValue(':timeNow1', $utcNowStr);
        $stmt->bindValue(':timeNow2', $utcNowStr);

        if ($stmt->execute()) {
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        } else {
            $msg = implode(',', $stmt->errorInfo());

            throw new Exception("Error: {$msg}");
        }

        $numberOfRows = count($rows);

        $results['previousShow'] = [];
        $results['currentShow'] = [];
        $results['nextShow'] = [];

        for ($i = 0; $i < $numberOfRows; ++$i) {
            //All shows start/end times are stored in the database as UTC.
            $showStartTime = new DateTime($rows[$i]['starts'], $timeZone);
            $showEndTime = new DateTime($rows[$i]['ends'], $timeZone);

            //Find the show that is within the current time.
            if (($showStartTime <= $utcNow) && ($showEndTime > $utcNow)) {
                if ($i - 1 >= 0) {
                    $results['previousShow'][0] = [
                        'id' => $rows[$i - 1]['id'],
                        'instance_id' => $rows[$i - 1]['instance_id'],
                        'name' => $rows[$i - 1]['name'],
                        'description' => $rows[$i - 1]['description'],
                        'url' => $rows[$i - 1]['url'],
                        'start_timestamp' => $rows[$i - 1]['start_timestamp'],
                        'end_timestamp' => $rows[$i - 1]['end_timestamp'],
                        'starts' => $rows[$i - 1]['starts'],
                        'ends' => $rows[$i - 1]['ends'],
                        'record' => $rows[$i - 1]['record'],
                        'image_path' => $rows[$i - 1]['image_path'],
                        'type' => 'show', ];
                }

                $results['currentShow'][0] = $rows[$i];

                if (isset($rows[$i + 1])) {
                    $results['nextShow'][0] = [
                        'id' => $rows[$i + 1]['id'],
                        'instance_id' => $rows[$i + 1]['instance_id'],
                        'name' => $rows[$i + 1]['name'],
                        'description' => $rows[$i + 1]['description'],
                        'url' => $rows[$i + 1]['url'],
                        'start_timestamp' => $rows[$i + 1]['start_timestamp'],
                        'end_timestamp' => $rows[$i + 1]['end_timestamp'],
                        'starts' => $rows[$i + 1]['starts'],
                        'ends' => $rows[$i + 1]['ends'],
                        'record' => $rows[$i + 1]['record'],
                        'image_path' => $rows[$i + 1]['image_path'],
                        'type' => 'show', ];
                }

                break;
            }
            //Previous is any row that ends after time now capture it in case we need it later.
            if ($showEndTime < $utcNow) {
                $previousShowIndex = $i;
            }
            //if we hit this we know we've gone to far and can stop looping.
            if ($showStartTime > $utcNow) {
                $results['nextShow'][0] = [
                    'id' => $rows[$i]['id'],
                    'instance_id' => $rows[$i]['instance_id'],
                    'name' => $rows[$i]['name'],
                    'description' => $rows[$i]['description'],
                    'url' => $rows[$i]['url'],
                    'start_timestamp' => $rows[$i]['start_timestamp'],
                    'end_timestamp' => $rows[$i]['end_timestamp'],
                    'starts' => $rows[$i]['starts'],
                    'ends' => $rows[$i]['ends'],
                    'record' => $rows[$i]['record'],
                    'image_path' => $rows[$i]['image_path'],
                    'type' => 'show', ];

                break;
            }
        }
        //If we didn't find a a current show because the time didn't fit we may still have
        //found a previous show so use it.
        if (count($results['previousShow']) == 0 && isset($previousShowIndex)) {
            $results['previousShow'][0] = [
                'id' => $rows[$previousShowIndex]['id'],
                'instance_id' => $rows[$previousShowIndex]['instance_id'],
                'name' => $rows[$previousShowIndex]['name'],
                'description' => $rows[$previousShowIndex]['description'],
                'start_timestamp' => $rows[$previousShowIndex]['start_timestamp'],
                'end_timestamp' => $rows[$previousShowIndex]['end_timestamp'],
                'starts' => $rows[$previousShowIndex]['starts'],
                'ends' => $rows[$previousShowIndex]['ends'],
                'record' => $rows[$previousShowIndex]['record'],
                'type' => 'show', ];
        }

        return $results;
    }

    /**
     * Given a start time $timeStart and end time $timeEnd, returns the next $limit
     * number of shows within the time interval
     * If $timeEnd not given, shows within next 48 hours from $timeStart are returned
     * If $limit not given, all shows within the intervals are returned
     * Times are all in UTC time.
     *
     * @param string $timeStart - interval start time (in UTC)
     * @param int    $limit     - number of shows to return
     * @param string $timeEnd   - interval end time (in UTC)
     *
     * @return array - the next $limit number of shows within the time interval
     */
    public static function getNextShows($timeStart, $limit = 'ALL', $timeEnd = '')
    {
        // defaults to retrieving shows from next 2 days if no end time has
        // been specified
        if ($timeEnd == '') {
            $timeEnd = "'{$timeStart}' + INTERVAL '2 days'";
        }

        //TODO, returning starts + ends twice (once with an alias). Unify this after the 2.0 release. --Martin
        $sql = <<<'SQL'
SELECT si.starts AS start_timestamp,
       si.ends AS end_timestamp,
       s.name,
       s.description,
       s.id,
       si.id AS instance_id,
       si.description AS instance_description,
       si.record,
       s.url,
	   s.image_path,
	   starts,
       ends
FROM cc_show_instances si
     LEFT JOIN cc_show s
     ON si.show_id = s.id
WHERE si.show_id = s.id
  AND si.starts >= :timeStart::timestamp
  AND si.starts < :timeEnd::timestamp
  AND modified_instance != TRUE
ORDER BY si.starts
SQL;

        //PDO won't accept "ALL" as a limit value (complains it is not an
        //integer, and so we must completely remove the limit clause if we
        //want to show all results - MK
        if ($limit != 'ALL') {
            $sql .= PHP_EOL . 'LIMIT :lim';
            $params = [
                ':timeStart' => $timeStart,
                ':timeEnd' => $timeEnd,
                ':lim' => $limit, ];
        } else {
            $params = [
                ':timeStart' => $timeStart,
                ':timeEnd' => $timeEnd, ];
        }

        return Application_Common_Database::prepareAndExecute($sql, $params, 'all');
    }

    public static function getMaxLengths()
    {
        $sql = <<<'SQL'
SELECT column_name, character_maximum_length FROM information_schema.columns
WHERE table_name = 'cc_show' AND character_maximum_length > 0
SQL;
        $result = Application_Common_Database::prepareAndExecute($sql);
        $assocArray = [];
        foreach ($result as $row) {
            $assocArray[$row['column_name']] = $row['character_maximum_length'];
        }

        return $assocArray;
    }

    public static function getStartEndCurrentMonthView()
    {
        $utcTimeZone = new DateTimeZone('UTC');

        //We have to get the start of the day in the user's timezone, and then convert that to UTC.
        $start = new DateTime('first day of this month', new DateTimeZone(Application_Model_Preference::GetUserTimezone()));

        $start->setTimezone($utcTimeZone); //Covert it to UTC.
        $monthInterval = new DateInterval('P1M');
        $end = clone $start;
        $end->add($monthInterval);

        return [$start, $end];
    }

    /** Returns the start and end date that FullCalendar will display for today's month.
     *
     *  FullCalendar displays 6 weeks, starting on a Sunday, for a total of 42 days. This function returns 42 days worth
     *  of data (a few days before, and a few days after.)
     */
    public static function getStartEndCurrentMonthPlusView()
    {
        $utcTimeZone = new DateTimeZone('UTC');

        //We have to get the start of the day in the user's timezone, and then convert that to UTC.
        $start = new DateTime('first day of this month', new DateTimeZone(Application_Model_Preference::GetUserTimezone()));

        $dayOfWeekNumeric = $start->format('w');
        $start->sub(new DateInterval("P{$dayOfWeekNumeric}D")); //Subtract the index of the day of the week the month starts on. (adds this many days from the previous month)
        $start->setTimezone($utcTimeZone); //Covert it to UTC.

        $fullCalendarMonthInterval = new DateInterval('P42D'); //42 days
        $end = clone $start;
        $end->add($fullCalendarMonthInterval);

        return [$start, $end];
    }

    public static function getStartEndCurrentWeekView()
    {
        $weekStartDayNum = Application_Model_Preference::GetWeekStartDay();
        $utcTimeZone = new DateTimeZone('UTC');

        //We have to get the start of the week in the user's timezone, and then convert that to UTC.
        $start = new DateTime('Sunday last week', new DateTimeZone(Application_Model_Preference::GetUserTimezone()));
        $start->add(new DateInterval("P{$weekStartDayNum}D")); //Shift the start date to the station's "Week Starts on Day"

        $start->setTimezone($utcTimeZone); //Covert it to UTC.
        $weekInterval = new DateInterval('P1W');
        $end = clone $start;
        $end->add($weekInterval);

        return [$start, $end];
    }

    public static function getStartEndCurrentDayView()
    {
        $utcTimeZone = new DateTimeZone('UTC');

        //We have to get the start of the day in the user's timezone, and then convert that to UTC.
        $start = new DateTime('today', new DateTimeZone(Application_Model_Preference::GetUserTimezone()));

        $start->setTimezone($utcTimeZone); //Covert it to UTC.
        $dayInterval = new DateInterval('P1D');
        $end = clone $start;
        $end->add($dayInterval);

        return [$start, $end];
    }
}