<?php
class Application_Service_SchedulerService
{
    private $con;
    private $fileInfo = array(
            "id" => "",
            "cliplength" => "",
            "cuein" => "00:00:00",
            "cueout" => "00:00:00",
            "fadein" => "00:00:00",
            "fadeout" => "00:00:00",
            "sched_id" => null,
            "type" => 0 //default type of '0' to represent files. type '1' represents a webstream
        );

    private $epochNow;
    private $nowDT;
    private $currentUser;
    private $checkUserPermissions = true;

    public function __construct()
    {
        $this->con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);

        //subtracting one because sometimes when we cancel a track, we set its end time
        //to epochNow and then send the new schedule to pypo. Sometimes the currently cancelled
        //track can still be included in the new schedule because it may have a few ms left to play.
        //subtracting 1 second from epochNow resolves this issue.
        $this->epochNow = microtime(true)-1;
        $this->nowDT = DateTime::createFromFormat("U.u", $this->epochNow, new DateTimeZone("UTC"));

        if ($this->nowDT === false) {
            // DateTime::createFromFormat does not support millisecond string formatting in PHP 5.3.2 (Ubuntu 10.04).
            // In PHP 5.3.3 (Ubuntu 10.10), this has been fixed.
            $this->nowDT = DateTime::createFromFormat("U", time(), new DateTimeZone("UTC"));
        }

        $user_service = new Application_Service_UserService();
        $this->currentUser = $user_service->getCurrentUser();
    }

    /**
     * 
     * Applies the show start difference to any scheduled items
     * 
     * @param $instanceIds
     * @param $diff (integer, difference between unix epoch in seconds)
     */
    public static function updateScheduleStartTime($instanceIds, $diff)
    {
        $con = Propel::getConnection();
        if (count($instanceIds) > 0) {
            $showIdList = implode(",", $instanceIds);

            $ccSchedules = CcScheduleQuery::create()
                ->filterByDbInstanceId($instanceIds, Criteria::IN)
                ->find($con);

            $interval = new DateInterval("PT".abs($diff)."S");
            if ($diff < 0) {
                $interval->invert = 1;
            }
            foreach ($ccSchedules as $ccSchedule) {
                $start = $ccSchedule->getDbStarts(null);
                $newStart = $start->add($interval);
                $end = $ccSchedule->getDbEnds(null);
                $newEnd = $end->add($interval);
                $ccSchedule
                    ->setDbStarts($newStart)
                    ->setDbEnds($newEnd)
                    ->save($con);
            }
        }
    }

    /**
     * 
     * Removes any time gaps in shows
     * 
     * @param array $schedIds schedule ids to exclude
     */
    public function removeGaps($showId, $schedIds=null)
    {
        $ccShowInstances = CcShowInstancesQuery::create()->filterByDbShowId($showId)->find();

        foreach ($ccShowInstances as $instance) {
            Logging::info("Removing gaps from show instance #".$instance->getDbId());
            //DateTime object
            $itemStart = $instance->getDbStarts(null);

            $ccScheduleItems = CcScheduleQuery::create()
                ->filterByDbInstanceId($instance->getDbId())
                ->filterByDbId($schedIds, Criteria::NOT_IN)
                ->orderByDbStarts()
                ->find();

            foreach ($ccScheduleItems as $ccSchedule) {
                //DateTime object
                $itemEnd = $this->findEndTime($itemStart, $ccSchedule->getDbClipLength());

                $ccSchedule->setDbStarts($itemStart)
                    ->setDbEnds($itemEnd);

                $itemStart = $itemEnd;
            }
            $ccScheduleItems->save();
        }
    }

    /**
     * 
     * Enter description here ...
     * @param DateTime $instanceStart
     * @param string $clipLength
     */
    private static function findEndTime($instanceStart, $clipLength)
    {
        $startEpoch = $instanceStart->format("U.u");
        $durationSeconds = Application_Common_DateHelper::playlistTimeToSeconds($clipLength);

        //add two float numbers to 6 subsecond precision
        //DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
        $endEpoch = bcadd($startEpoch , (string) $durationSeconds, 6);

        $dt = DateTime::createFromFormat("U.u", $endEpoch, new DateTimeZone("UTC"));

        if ($dt === false) {
            //PHP 5.3.2 problem
            $dt = DateTime::createFromFormat("U", intval($endEpoch), new DateTimeZone("UTC"));
        }

        return $dt;
    }

    private static function findTimeDifference($p_startDT, $p_seconds)
    {
    	$startEpoch = $p_startDT->format("U.u");
    	
    	//add two float numbers to 6 subsecond precision
    	//DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
    	$newEpoch = bcsub($startEpoch , (string) $p_seconds, 6);
    
    	$dt = DateTime::createFromFormat("U.u", $newEpoch, new DateTimeZone("UTC"));
    
    	if ($dt === false) {
    		//PHP 5.3.2 problem
    		$dt = DateTime::createFromFormat("U", intval($newEpoch), new DateTimeZone("UTC"));
    	}
    
    	return $dt;
    }

    /**
     * 
     * Gets a copy of the linked show's schedule from cc_schedule table
     * 
     * If $instanceId is not null, we use that variable to grab the linked
     * show's schedule from cc_schedule table. (This is likely to be the case
     * if a user has edited a show and changed it from un-linked to linked, in
     * which case we copy the show content from the show instance that was clicked
     * on to edit the show in the calendar.) Otherwise the schedule is taken
     * from the most recent show instance that existed before new show
     * instances were created. (This is likely to be the case when a user edits a 
     * show and a new repeat show day is added (i.e. mondays), or moves forward in the
     * calendar triggering show creation)
     * 
     * @param integer $showId
     * @param array $instancsIdsToFill
     * @param integer $instanceId
     */
    private static function getLinkedShowSchedule($showId, $instancsIdsToFill, $instanceId)
    {
        $con = Propel::getConnection();
        
        if (is_null($instanceId)) {
            $showsPopulatedUntil = Application_Model_Preference::GetShowsPopulatedUntil();

            $showInstanceWithMostRecentSchedule = CcShowInstancesQuery::create()
                ->filterByDbShowId($showId)
                ->filterByDbStarts($showsPopulatedUntil->format("Y-m-d H:i:s"), Criteria::LESS_THAN)
                ->filterByDbId($instancsIdsToFill, Criteria::NOT_IN)
                ->orderByDbStarts(Criteria::DESC)
                ->limit(1)
                ->findOne();

            if (is_null($showInstanceWithMostRecentSchedule)) {
                return null;
            }
            $instanceId = $showInstanceWithMostRecentSchedule->getDbId();
        }

        $linkedShowSchedule_sql = $con->prepare(
            "select * from cc_schedule where instance_id = :instance_id ".
            "order by starts");
        $linkedShowSchedule_sql->bindParam(':instance_id', $instanceId);
        $linkedShowSchedule_sql->execute();

        return $linkedShowSchedule_sql->fetchAll();
    }
    
    /**
     * 
     * This function gets called after new linked show_instances are created, or after
     * a show has been edited and went from being un-linked to linked.
     * It fills the new show instances' schedules.
     * 
     * @param CcShow_type $ccShow
     * @param array $instanceIdsToFill ids of the new linked cc_show_instances that
     * need their schedules filled
     */
    public static function fillLinkedInstances($ccShow, $instanceIdsToFill, $instanceId=null)
    {
        //Get the "template" schedule for the linked show (contents of the linked show) that will be 
        //copied into to all the new show instances.
        $linkedShowSchedule = self::getLinkedShowSchedule($ccShow->getDbId(), $instanceIdsToFill, $instanceId);

        //get time_filled so we can update cc_show_instances
        if (!empty($linkedShowSchedule)) {
            $timeFilled_sql = "SELECT time_filled FROM cc_show_instances ".
               "WHERE id = {$linkedShowSchedule[0]["instance_id"]}";
            $timeFilled = Application_Common_Database::prepareAndExecute(
               $timeFilled_sql, array(), Application_Common_Database::COLUMN);
        } else {
            //We probably shouldn't return here because the code below will
            //set this on each empty show instance...
            $timeFilled = "00:00:00";
        }
    
        $values = array();

        $con = Propel::getConnection();
        
        //Here we begin to fill the new show instances (as specified by $instanceIdsToFill)
        //with content from $linkedShowSchedule.
        try {
            $con->beginTransaction();
            foreach ($instanceIdsToFill as $id) 
            {
                //Start by clearing the show instance that needs to be filling. This ensure
                //we're not going to get in trouble in case there's an programming error somewhere else.
                self::clearShowInstanceContents($id);
                    
                // Now fill the show instance with the same content that $linkedShowSchedule has.
                $instanceStart_sql = "SELECT starts FROM cc_show_instances " .
                         "WHERE id = {$id} " . "ORDER BY starts";
                
                //What's tricky here is that when we copy the content, we have to adjust
                //the start and end times of each track so they're inside the new show instance's time slot.
                $nextStartDT = new DateTime(
                        Application_Common_Database::prepareAndExecute(
                                $instanceStart_sql, array(), 
                                Application_Common_Database::COLUMN), 
                        new DateTimeZone("UTC"));
                
                $defaultCrossfadeDuration = Application_Model_Preference::GetDefaultCrossfadeDuration();
                unset($values);
                $values = array();
                foreach ($linkedShowSchedule as $item) {
                    $endTimeDT = self::findEndTime($nextStartDT, 
                            $item["clip_length"]);
                    
                    if (is_null($item["file_id"])) {
                        $item["file_id"] = "null";
                    }
                    if (is_null($item["stream_id"])) {
                        $item["stream_id"] = "null";
                    }
                    
                    $values[] = "(" . "'{$nextStartDT->format("Y-m-d H:i:s")}', " .
                             "'{$endTimeDT->format("Y-m-d H:i:s")}', " .
                             "'{$item["clip_length"]}', " .
                             "'{$item["fade_in"]}', " . "'{$item["fade_out"]}', " .
                             "'{$item["cue_in"]}', " . "'{$item["cue_out"]}', " .
                             "{$item["file_id"]}, " . "{$item["stream_id"]}, " .
                             "{$id}, " . "{$item["position"]})";
                    
                    $nextStartDT = self::findTimeDifference($endTimeDT, 
                            $defaultCrossfadeDuration);
                } //foreach show item

                if (!empty($values)) {
                    $insert_sql = "INSERT INTO cc_schedule (starts, ends, ".
                        "clip_length, fade_in, fade_out, cue_in, cue_out, ".
                        "file_id, stream_id, instance_id, position)  VALUES ".
                        implode($values, ",");
                    Application_Common_Database::prepareAndExecute(
                        $insert_sql, array(), Application_Common_Database::EXECUTE);
                }
               
               //update cc_schedule status column
               $instance = CcShowInstancesQuery::create()->findPk($id);
               $instance->updateScheduleStatus($con);
           } //foreach linked instance

            //update time_filled and last_scheduled in cc_show_instances
            $now = gmdate("Y-m-d H:i:s");
            $whereClause = new Criteria();
            $whereClause->add(CcShowInstancesPeer::ID, $instanceIdsToFill, Criteria::IN);
            
            $updateClause = new Criteria();
            $updateClause->add(CcShowInstancesPeer::TIME_FILLED, $timeFilled);
            $updateClause->add(CcShowInstancesPeer::LAST_SCHEDULED, $now);
            
            BasePeer::doUpdate($whereClause, $updateClause, $con);

           $con->commit();
           Logging::info("finished fill");
        } catch (Exception $e) {
            $con->rollback();
            Logging::info("Error filling linked shows: ".$e->getMessage());
            exit();
        }
    }

    public static function fillPreservedLinkedShowContent($ccShow, $showStamp)
    {
        $item = $showStamp->getFirst();
        $timeFilled = $item->getCcShowInstances()->getDbTimeFilled();

        foreach ($ccShow->getCcShowInstancess() as $ccShowInstance) {
            $ccSchedules = CcScheduleQuery::create()
                ->filterByDbInstanceId($ccShowInstance->getDbId())
                ->find();

            if ($ccSchedules->isEmpty()) {

                $nextStartDT = $ccShowInstance->getDbStarts(null);

                foreach ($showStamp as $item) {
                    $endTimeDT = self::findEndTime($nextStartDT, $item->getDbClipLength());

                    $ccSchedule = new CcSchedule();
                    $ccSchedule
                        ->setDbStarts($nextStartDT)
                        ->setDbEnds($endTimeDT)
                        ->setDbFileId($item->getDbFileId())
                        ->setDbStreamId($item->getDbStreamId())
                        ->setDbClipLength($item->getDbClipLength())
                        ->setDbFadeIn($item->getDbFadeIn())
                        ->setDbFadeOut($item->getDbFadeOut())
                        ->setDbCuein($item->getDbCueIn())
                        ->setDbCueOut($item->getDbCueOut())
                        ->setDbInstanceId($ccShowInstance->getDbId())
                        ->setDbPosition($item->getDbPosition())
                        ->save();

                    $nextStartDT = self::findTimeDifference($endTimeDT,
                        Application_Model_Preference::GetDefaultCrossfadeDuration());
                } //foreach show item

                $ccShowInstance
                    ->setDbTimeFilled($timeFilled)
                    ->setDbLastScheduled(gmdate("Y-m-d H:i:s"))
                    ->save();
            }
        }
    }

    /** Clears a show instance's schedule (which is actually clearing cc_schedule during that show instance's time slot.) */
    private static function clearShowInstanceContents($instanceId)
    {
        //Application_Common_Database::prepareAndExecute($delete_sql, array(), Application_Common_Database::EXECUTE);
        $con = Propel::getConnection();
        $query = $con->prepare("DELETE FROM cc_schedule WHERE instance_id = :instance_id");
        $query->bindParam(':instance_id', $instanceId);
        $query->execute();
    }
    
    /*
    private static function replaceInstanceContentCheck($currentShowStamp, $showStamp, $instance_id)
    {
        $counter = 0;
        $erraseShow = false;
        if (count($currentShowStamp) != count($showStamp)) {
            $erraseShow = true;
        } else {
            foreach ($showStamp as $item) {
                if ($item["file_id"] != $currentShowStamp[$counter]["file_id"] ||
                    $item["stream_id"] != $currentShowStamp[$counter]["stream_id"]) {
                        $erraseShow = true;
                        break;
                    //CcScheduleQuery::create()
                    //    ->filterByDbInstanceId($ccShowInstance->getDbId())
                    //    ->delete();
                 }
                 $counter += 1;
            }
        }
        if ($erraseShow) {
            $delete_sql = "DELETE FROM cc_schedule ".
                    "WHERE instance_id = :instance_id";
            Application_Common_Database::prepareAndExecute(
            $delete_sql, array(), Application_Common_Database::EXECUTE);
            return true;
        }

        //If we get here, the content in the show instance is the same
        // as what we want to replace it with, so we can leave as is
         
        return false;
    }*/

    public function emptyShowContent($instanceId)
    {
        try {
            $ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);

            $instances = array();
            $instanceIds = array();

            if ($ccShowInstance->getCcShow()->isLinked()) {
                foreach ($ccShowInstance->getCcShow()->getFutureCcShowInstancess() as $instance) {
                    $instanceIds[] = $instance->getDbId();
                    $instances[] = $instance;
                }
            } else {
                $instanceIds[] = $ccShowInstance->getDbId();
                $instances[] = $ccShowInstance;
            }

            /* Get the file ids of the tracks we are about to delete
             * from cc_schedule. We need these so we can update the
             * is_scheduled flag in cc_files
             */
            $ccSchedules = CcScheduleQuery::create()
                ->filterByDbInstanceId($instanceIds, Criteria::IN)
                ->setDistinct(CcSchedulePeer::FILE_ID)
                ->find();
            $fileIds = array();
            foreach ($ccSchedules as $ccSchedule) {
                $fileIds[] = $ccSchedule->getDbFileId();
            }

            /* Clear out the schedule */
            CcScheduleQuery::create()
                ->filterByDbInstanceId($instanceIds, Criteria::IN)
                ->delete();

            /* Now that the schedule has been cleared we need to make
             * sure we do not update the is_scheduled flag for tracks
             * that are scheduled in other shows
             */
            $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles();
            foreach ($fileIds as $k => $v) {
                if (in_array($v, $futureScheduledFiles)) {
                    unset($fileIds[$k]);
                }
            }

            $selectCriteria = new Criteria();
            $selectCriteria->add(CcFilesPeer::ID, $fileIds, Criteria::IN);
            $updateCriteria = new Criteria();
            $updateCriteria->add(CcFilesPeer::IS_SCHEDULED, false);
            BasePeer::doUpdate($selectCriteria, $updateCriteria, Propel::getConnection());

            Application_Model_RabbitMq::PushSchedule();
            $con = Propel::getConnection(CcShowInstancesPeer::DATABASE_NAME);
            foreach ($instances as $instance) {
                $instance->updateDbTimeFilled($con);
            }

            return true;
        } catch (Exception $e) {
            Logging::info($e->getMessage());
            return false;
        }
    }
    
    /*
     * TODO in the future this should probably support webstreams.
     */
    public function updateFutureIsScheduled($scheduleId, $status) 
    {
    	$sched = CcScheduleQuery::create()->findPk($scheduleId);
    	$redraw = false;
    	
    	if (isset($sched)) {
    		
    		$fileId = $sched->getDbFileId();
    		
    		if (isset($fileId)) {
    			$redraw = Application_Model_StoredFile::setIsScheduled($fileId, $status);
    		}
    	}
    	
    	return $redraw;
    }
}