<?php
/**
 * class.calendar.php
 *
 * @package workflow.engine.classes
 *
 * ProcessMaker Open Source Edition
 * Copyright (C) 2004 - 2011 Colosa Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, contact Colosa Inc, 2566 Le Jeune Rd.,
 * Coral Gables, FL, 33134, USA, or email info@colosa.com.
 *
 *
 *
 * @name calendar
 * created 2010-03-22
 *
 */

require_once ("classes/model/CalendarDefinition.php");

/**
 * A Calendar object where it is defined Working Days, Business Hours and Holidays
 * A Calendar is applied to a User, Process or Task
 * Extends CalendarDefinition.
 *
 * @author Hugo Loza <hugo@colosa.com> 2010-03-22
 * @uses CalendarDefinition
 * @package workflow.engine.classes
 *
 */
class Calendar extends CalendarDefinition
{
	public $pmCalendarUid = '';
	public $pmCalendarData = array();

	public function getCalendar ($userUid, $proUid = null, $tasUid = null)
	{
		require_once 'classes/model/CalendarAssignments.php';

		$criteria = new Criteria ( 'workflow' );
		$criteria->clearSelectColumns ( );

		$calendarData = array();

		//Default Calendar
		$calendarData['UID']  = '00000000000000000000000000000001';
		$calendarData['TYPE'] = 'DEFAULT';

		//Load User,Task and Process calendars (if exist)
		$criteria->addSelectColumn ( CalendarAssignmentsPeer::CALENDAR_UID );
		$criteria->addSelectColumn ( CalendarAssignmentsPeer::OBJECT_UID );
		$criteria->addSelectColumn ( CalendarAssignmentsPeer::OBJECT_TYPE );
		$criteria->add ( CalendarAssignmentsPeer::OBJECT_UID, array($userUid, $proUid, $tasUid), CRITERIA::IN );
		$oDataset = CalendarAssignmentsPeer::doSelectRS ( $criteria );
		$oDataset->setFetchmode ( ResultSet::FETCHMODE_ASSOC );
		$oDataset->next ();

		$calendarArray = array();
		while (is_array($aRow = $oDataset->getRow ())) {
			if ($aRow['OBJECT_UID']==$userUid) {
				$calendarArray['USER'] = $aRow ['CALENDAR_UID'];
			}
			if ($aRow['OBJECT_UID']==$proUid) {
				$calendarArray['PROCESS'] = $aRow ['CALENDAR_UID'];
			}
			if ($aRow['OBJECT_UID']==$tasUid) {
				$calendarArray['TASK'] = $aRow ['CALENDAR_UID'];
			}
			$oDataset->next ();
		}

		if (isset($calendarArray['USER'])) {
			$calendarData['UID']  = $calendarArray['USER'];
			$calendarData['TYPE'] = 'USER';
		} elseif (isset($calendarArray['PROCESS'])) {
			$calendarData['UID']  = $calendarArray['PROCESS'];
			$calendarData['TYPE'] = 'PROCESS';
		} elseif (isset($calendarArray['TASK'])) {
			$calendarData['UID']  = $calendarArray['TASK'];
			$calendarData['TYPE'] = 'TASK';
		}

		$this->pmCalendarUid = $calendarData['UID'];
		return $this->pmCalendarUid;
	}

	public function getCalendarData ($calendarUid = null)
	{
		require_once ( 'classes/model/CalendarDefinition.php' );

		$calendarUid = (is_null($calendarUid)) ? $this->pmCalendarUid : $calendarUid;
		$this->pmCalendarUid = $calendarUid;

		//if exists the row in the database propel will update it, otherwise will insert.
		$tr = CalendarDefinitionPeer::retrieveByPK ( $calendarUid );

		$defaultCalendar ['CALENDAR_UID'] = '00000000000000000000000000000001';
		$defaultCalendar ['CALENDAR_NAME'] = 'Default';
		$defaultCalendar ['CALENDAR_CREATE_DATE'] = date ( 'Y-m-d' );
		$defaultCalendar ['CALENDAR_UPDATE_DATE'] = date ( 'Y-m-d' );
		$defaultCalendar ['CALENDAR_DESCRIPTION'] = 'Default';
		$defaultCalendar ['CALENDAR_STATUS'] = 'ACTIVE';
		$defaultCalendar ['CALENDAR_WORK_DAYS'] = '1|2|3|4|5';
		$defaultCalendar ['CALENDAR_WORK_DAYS'] = explode ( '|', '1|2|3|4|5' );
		$defaultCalendar ['BUSINESS_DAY'] [1] ['CALENDAR_BUSINESS_DAY'] = 7;
		$defaultCalendar ['BUSINESS_DAY'] [1] ['CALENDAR_BUSINESS_START'] = '09:00';
		$defaultCalendar ['BUSINESS_DAY'] [1] ['CALENDAR_BUSINESS_END'] = '17:00';
		$defaultCalendar ['BUSINESS_DAY'] [1] ['DIFF_HOURS'] = '8';
		$defaultCalendar ['HOURS_FOR_DAY'] = '8';
		$defaultCalendar ['HOLIDAY'] = array ();

		if ((is_object ( $tr ) && get_class ( $tr ) == 'CalendarDefinition')) {
			$fields ['CALENDAR_UID'] = $tr->getCalendarUid ();
			$fields ['CALENDAR_NAME'] = $tr->getCalendarName ();
			$fields ['CALENDAR_CREATE_DATE'] = $tr->getCalendarCreateDate ();
			$fields ['CALENDAR_UPDATE_DATE'] = $tr->getCalendarUpdateDate ();
			$fields ['CALENDAR_DESCRIPTION'] = $tr->getCalendarDescription ();
			$fields ['CALENDAR_STATUS'] = $tr->getCalendarStatus ();
			$fields ['CALENDAR_WORK_DAYS'] = $tr->getCalendarWorkDays ();
			$fields ['CALENDAR_WORK_DAYS_A'] = explode ( '|', $tr->getCalendarWorkDays () );
		} else {
			$fields=$defaultCalendar;
			//$this->saveCalendarInfo ( $fields );
			$fields ['CALENDAR_WORK_DAYS'] = '1|2|3|4|5';
			$fields ['CALENDAR_WORK_DAYS_A'] = explode ( '|', '1|2|3|4|5' );
			//$tr = CalendarDefinitionPeer::retrieveByPK ( $calendarUid );
		}

		$CalendarBusinessHoursObj = new CalendarBusinessHours ( );
		$CalendarBusinessHours = $this->getCalendarBusinessHours ( $calendarUid );

		$numDay = 8;
		$daysHours = array();
		$hoursCant = array();
		$modaHours = 0;
		$keyModa = 0;
		foreach ($CalendarBusinessHours as $value) {
			if ($value['CALENDAR_BUSINESS_DAY'] != $numDay) {
				$numDay = $value['CALENDAR_BUSINESS_DAY'];
				$daysHours[$numDay] = 0;
			}
			$daysHours[$numDay] += $value['DIFF_HOURS'];
		}
		foreach ($daysHours as $value) {
			if (isset($hoursCant[$value])) {
				$hoursCant[$value]++;
			} else {
				$hoursCant[$value] = 1;
			}
		}

		foreach ($hoursCant as $key => $value) {
			if ($value > $modaHours ) {
				$modaHours = $value;
				$keyModa = $key;
			}
		}

		$fields ['HOURS_FOR_DAY'] = $keyModa;
		$fields ['BUSINESS_DAY'] = $CalendarBusinessHours;

		$CalendarHolidaysObj = new CalendarHolidays ( );
		$CalendarHolidays = $this->getCalendarHolidays ( $calendarUid );
		$fields ['HOLIDAY'] = $CalendarHolidays;
		$fields=$this->validateCalendarInfo($fields, $defaultCalendar);

		$this->pmCalendarData = $fields;
		return $this->pmCalendarData;
	}

	public function getCalendarBusinessHours ($calendarUid = null)
	{
		require_once ( 'classes/model/CalendarBusinessHours.php' );

		$calendarUid = (is_null($calendarUid)) ? $this->pmCalendarUid : $calendarUid;
		$this->pmCalendarUid = $calendarUid;

		$criteria = new Criteria('workflow');
		$criteria->clearSelectColumns ( );

		$criteria->addSelectColumn (  CalendarBusinessHoursPeer::CALENDAR_UID );
		$criteria->addSelectColumn (  CalendarBusinessHoursPeer::CALENDAR_BUSINESS_DAY );
		$criteria->addSelectColumn (  CalendarBusinessHoursPeer::CALENDAR_BUSINESS_START );
		$criteria->addSelectColumn (  CalendarBusinessHoursPeer::CALENDAR_BUSINESS_END );

		$criteria->add (  CalendarBusinessHoursPeer::CALENDAR_UID, $calendarUid , CRITERIA::EQUAL );
		$criteria->addDescendingOrderByColumn ( CalendarBusinessHoursPeer::CALENDAR_BUSINESS_DAY );
		$criteria->addAscendingOrderByColumn ( CalendarBusinessHoursPeer::CALENDAR_BUSINESS_START );

		$rs = CalendarBusinessHoursPeer::doSelectRS($criteria);
		$rs->setFetchmode(ResultSet::FETCHMODE_ASSOC);
		$rs->next();
		$row = $rs->getRow();

		$fields = array();
		$count = 0;

		while (is_array($row)) {
			$count++;
			$iniTime = (float)str_replace(':', '', $row['CALENDAR_BUSINESS_START']);
			$finTime = (float)str_replace(':', '', $row['CALENDAR_BUSINESS_END']);
			$row['DIFF_HOURS'] = (($finTime-$iniTime)/100);
			$fields[$count] = $row;
			$rs->next();
			$row = $rs->getRow();
		}

		return $fields;
	}

	public function getCalendarHolidays ($calendarUid = null)
	{
		require_once ( 'classes/model/CalendarHolidays.php' );

		$calendarUid = (is_null($calendarUid)) ? $this->pmCalendarUid : $calendarUid;
		$this->pmCalendarUid = $calendarUid;

		$criteria = new Criteria('workflow');
		$criteria->clearSelectColumns ( );

		$criteria->addSelectColumn (  CalendarHolidaysPeer::CALENDAR_UID );
		$criteria->addSelectColumn (  CalendarHolidaysPeer::CALENDAR_HOLIDAY_NAME );
		$criteria->addSelectColumn (  CalendarHolidaysPeer::CALENDAR_HOLIDAY_START );
		$criteria->addSelectColumn (  CalendarHolidaysPeer::CALENDAR_HOLIDAY_END );

		$criteria->add (  CalendarHolidaysPeer::CALENDAR_UID, $calendarUid , CRITERIA::EQUAL );

		$rs = CalendarHolidaysPeer::doSelectRS($criteria);
		$rs->setFetchmode(ResultSet::FETCHMODE_ASSOC);
		$rs->next();
		$row = $rs->getRow();
		$fields=array();
	
		$count=0;

		while (is_array($row)) {
			$count++;
			$a=explode(' ',$row['CALENDAR_HOLIDAY_START']);
			$row['CALENDAR_HOLIDAY_START']=$a[0];
			$a=explode(' ',$row['CALENDAR_HOLIDAY_END']);
			$row['CALENDAR_HOLIDAY_END']=$a[0];
			$fields[$count] = $row;
			$rs->next();
			$row = $rs->getRow();
		}

		return $fields;
	}

	public function validateCalendarInfo ($fields, $defaultCalendar)
	{
		try {
			//Validate if Working days are Correct
			//Minimun 3 ?
			$workingDays=explode ( '|', $fields['CALENDAR_WORK_DAYS'] );
			if (count($workingDays)<3) {
				throw (new Exception ( 'You must define at least 3 Working Days!' ));
			}
			//Validate that all Working Days have Bussines Hours
			if (count($fields ['BUSINESS_DAY'])<1) {
				throw (new Exception ( 'You must define at least one Business Day for all days' ));
			}

			$workingDaysOK=array();
			foreach ($workingDays as $key => $day) {
				$workingDaysOK[$day]=false;
			}

			$sw_all = false;
			foreach ($fields ['BUSINESS_DAY'] as $keyB => $businessHours) {
				if (($businessHours['CALENDAR_BUSINESS_DAY']==7)) {
					$sw_all=true;
				} elseif((in_array($businessHours['CALENDAR_BUSINESS_DAY'],$workingDays))) {
					$workingDaysOK[$businessHours['CALENDAR_BUSINESS_DAY']]=true;
				}
			}

			$sw_days = true;
			foreach ($workingDaysOK as $day => $sw_day) {
				$sw_days = $sw_days && $sw_day;
			}

			if (!($sw_all || $sw_days)) {
				throw (new Exception ( 'Not all working days have their correspondent business day' ));
			}
			//Validate Holidays

			return $fields;
		} catch (Exception $e) {
			//print $e->getMessage();
			//$this->addCalendarLog('!!!!!!! BAD CALENDAR DEFINITION. '.$e->getMessage());
			$defaultCalendar ['CALENDAR_WORK_DAYS'] = '1|2|3|4|5';
			$defaultCalendar ['CALENDAR_WORK_DAYS_A'] = explode ( '|', '1|2|3|4|5' );
			return $defaultCalendar;
		}
	}

    /**
     *
     * @param string(32) $userUid
     * @param string(32) $proUid
     * @param string(32) $tasUid
     */
    function __construct($userUid = NULL, $proUid = NULL, $tasUid = NULL)
    {
        $this->userUid = $userUid;
        $this->proUid = $proUid;
        $this->tasUid = $tasUid;
        $this->calendarLog = "";
        $this->setupCalendar( $userUid, $proUid, $tasUid );
    }

    /**
     * Small function used to add important information about the calcs and actions
     * to the log (that log will be saved)
     *
     * @name addCalendarLog
     * @param text $msg
     * @access public
     *
     */
    function addCalendarLog ($msg)
    {
        $this->calendarLog .= "\n" . date( "D M j G:i:s T Y" ) . ": " . $msg;
    }

    /**
     * setupCalendar is used to generate a valid instance of calendar using $userUid, $proUid and $tasUid
     * to find a valid calendar.
     * If there is no valid calendar then use the Default
     *
     * @name setupCalendar
     * @param string(32) $userUid
     * @param string(32) $proUid
     * @param string(32) $tasUid
     * @return void
     */
    function setupCalendar ($userUid, $proUid, $tasUid)
    {
        $calendarDefinition = $this->getCalendarFor( $userUid, $proUid, $tasUid );
        $this->calendarUid = $calendarDefinition['CALENDAR_UID'];
        $this->calendarDefinition = $calendarDefinition;
    }

    //// FUNTION CALCULATE DATE
    public function calculateDate ($iniDate, $duration, $formatDuration, $calendarData = array())
    {
    	$calendarData = (count($calendarData)) ? $calendarData : $this->pmCalendarData;
    	$this->pmCalendarData = $calendarData;

    	if ( G::toUpper($formatDuration) == 'DAYS' ) {
    		$duration = $duration*$this->pmCalendarData['HOURS_FOR_DAY'];
    	}

    	/*
    	 $log = array();
    	$titles = array();
    	$titles[] = 'HOURS';
    	$titles[] = 'DATE';
    	$titles[] = '**DAY';
    	$titles[] = '**RANGE';
    	$titles[] = '**HOURS RANGE';
    	$titles[] = '**SUM HOURS';
    	$titles[] = '**NEXT DATE';
    	$log[] = $titles;

    	$dataLog = array();
    	$dataLog[] = $duration;
    	$dataLog[] = $iniDate;
    	$dataLog[] = '-----';
    	$dataLog[] = '-----';
    	$dataLog[] = '-----';
    	$dataLog[] = '-----';
    	$dataLog[] = '-----';
    	$log[] = $dataLog;
    	*/
    	$hoursDuration = (float)$duration;
    	$newDate = $iniDate;
    	
    	if ( G::toUpper($formatDuration) == 'MINUTES' ) {
    		$hoursDuration = $duration / 60;
    	}

    	while ($hoursDuration > 0) {
    		//$dataLog = array();
    		$newDate = $this->getIniDate($newDate);
    
    		//$dataLog[] = $hoursDuration;
    		//$dataLog[] = $newDate;
    
    		$rangeWorkHour = $this->getRangeWorkHours($newDate, $calendarData['BUSINESS_DAY']);
    		$onlyDate = (date('Y-m-d',strtotime($newDate))) . ' ' . $rangeWorkHour['END'];
    
    		//$dataLog[] = date('l',strtotime($newDate));
    		//$dataLog[] = $rangeWorkHour['START'] . ' / ' . $rangeWorkHour['END'];
    		//$dataLog[] = $rangeWorkHour['TOTAL'];
    
    		if ( (((float)$hoursDuration) >= ((float)$rangeWorkHour['TOTAL'])) ||
    				((strtotime($onlyDate) - strtotime($newDate)) < (((float)$hoursDuration)*3600))
    		) {
    			$secondRes = (float)(strtotime($onlyDate) - strtotime($newDate));
    			$newDate = $onlyDate;
    			$hoursDuration -= (float)($secondRes/3600);
    			//$dataLog[] = (float)($secondRes/3600);
    		} else {
    			$newDate = date("Y-m-d H:i:s", strtotime("+" . round(((float)($hoursDuration)) * 3600) . " seconds", strtotime($newDate)));
    			//$dataLog[] = (float)($hoursDuration);
    			$hoursDuration = 0;
    		}
    		//$dataLog[] = $newDate;
    		//$log[] = $dataLog;
    	}

    	//$this->showLog($log);
    	$result['DUE_DATE'] = $newDate;
    	$result['DUE_DATE_SECONDS'] = strtotime($newDate);
    	return $result;
    }
    
    public function calculateDuration ($iniDate, $finDate = null, $calendarData = array())
    {
    	if ((is_null($finDate)) || ($finDate == '')) {
    		$finDate = date('Y-m-d H:i:s');
    	}
    
    	$calendarData = (count($calendarData)) ? $calendarData : $this->pmCalendarData;
    	$this->pmCalendarData = $calendarData;
    
    	$secondDuration = 0.00;
    
    	if ( (strtotime($iniDate)) < (strtotime($finDate)) ) {
    		$timeIniDate = strtotime($iniDate);
    		$timeFinDate = strtotime($finDate);
    
    	} elseif ( (strtotime($finDate)) < (strtotime($iniDate)) ) {
    		$timeIniDate = strtotime($finDate);
    		$timeFinDate = strtotime($iniDate);
    		$auxDate = $iniDate;
    		$iniDate = $finDate;
    		$finDate = $auxDate;
    	} else {
    		return $secondDuration;
    	}
    
    	$finDate = $this->getIniDate($finDate);
    	$newDate = $iniDate;
    	while ($timeIniDate < $timeFinDate) {
    		//$dataLog = array();
    		$newDate = $this->getIniDate($newDate);
    
    		//$dataLog[] = $hoursDuration;
    		//$dataLog[] = $newDate;
    
    		$rangeWorkHour = $this->getRangeWorkHours($newDate, $calendarData['BUSINESS_DAY']);
    		$onlyDate = (date('Y-m-d',strtotime($newDate))) . ' ' . $rangeWorkHour['END'];
    
    		//$dataLog[] = date('l',strtotime($newDate));
    		//$dataLog[] = $rangeWorkHour['START'] . ' / ' . $rangeWorkHour['END'];
    		//$dataLog[] = $rangeWorkHour['TOTAL'];
    
    		if ( (strtotime($finDate)) < (strtotime($onlyDate)) ) {
    			$secondRes = ( ((float)strtotime($finDate)) - ((float)strtotime($newDate)) );
    			$timeIniDate = strtotime($finDate);
    			$secondDuration += (float)$secondRes;
    		} else {
    			$secondRes = ( ((float)strtotime($onlyDate)) - ((float)strtotime($newDate)) );
    			$newDate = $onlyDate;
    			$timeIniDate = strtotime($onlyDate);
    			$secondDuration += (float)$secondRes;
    		}
    		//$dataLog[] = $newDate;
    		//$log[] = $dataLog;
    	}
    	return $secondDuration;
    }
    
    public function getRangeWorkHours ($date, $workHours)
    {
    	$auxIniDate = explode(' ', $date);
    	$timeDate = $auxIniDate['1'];
    	$timeDate = (float)str_replace(':', '', ((strlen($timeDate) == 8) ? $timeDate : $timeDate.':00') );
    	$weekDay = date('w',strtotime($date));
    
    	$workHoursDay = array();
    	$tempWorkHoursDay = array();
    
    	foreach ($workHours as $value) {
    		if ($value['CALENDAR_BUSINESS_DAY'] == $weekDay) {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$workHoursDay[] = $rangeWorkHour;
    		}
    
    		if ($value['CALENDAR_BUSINESS_DAY'] == '7') {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$tempWorkHoursDay[] = $rangeWorkHour;
    		}
    	}
    
    	if ( !(count($workHoursDay)) ) {
    		$workHoursDay = $tempWorkHoursDay;
    	}
    
    	foreach ($workHoursDay as $value) {
    		$iniTime = (float)str_replace(':', '', $value['START']);
    		$finTime = (float)str_replace(':', '', $value['END']);
    
    		if ( ($iniTime <= $timeDate)  && ($timeDate <= $finTime) ) {
    			//pr($finTime .' menos '.$iniTime .' = '.($finTime-$iniTime));
    			$value['TOTAL'] = (($finTime-$iniTime)/10000);
    			return $value;
    		}
    	}
    	return false;
    }
    
    public function getIniDate ($iniDate, $calendarData = array())
    {
    	$calendarData = (count($calendarData)) ? $calendarData : $this->pmCalendarData;
    	$this->pmCalendarData = $calendarData;
    	$flagIniDate = true;

    	while ($flagIniDate) {
    		// 1 if it's a work day
    		$weekDay = date('w',strtotime($iniDate));
    		if ( !(in_array($weekDay, $calendarData['CALENDAR_WORK_DAYS_A'])) ) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		}

    		// 2 if it's a holiday
    		$iniDateHolidayDay = $this->is_holiday($iniDate);
    		if ($iniDateHolidayDay) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		}

    		// 3 if it's work time
    		$workHours = $this->nextWorkHours($iniDate, $weekDay);
    		if ( !($workHours['STATUS']) ) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		} else {
    			$iniDate = $workHours['DATE'];
    		}

    		$flagIniDate = false;
    	}

    	return $iniDate;
    }
    
    public function nextWorkHours ($date, $weekDay, $workHours = array())
    {
    	$workHours = (count($workHours)) ? $workHours : $this->pmCalendarData['BUSINESS_DAY'];
    
    	$auxIniDate = explode(' ', $date);
    	$timeDate = $auxIniDate['1'];
    	$timeDate = (float)str_replace(':', '', ((strlen($timeDate) == 8) ? $timeDate : $timeDate.':00') );
    	$nextWorkHours = array();
    
    	$workHoursDay = array();
    	$tempWorkHoursDay = array();
    
    	foreach ($workHours as $value) {
    		if ($value['CALENDAR_BUSINESS_DAY'] == $weekDay) {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$workHoursDay[] = $rangeWorkHour;
    		}
    
    		if ($value['CALENDAR_BUSINESS_DAY'] == '7') {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$tempWorkHoursDay[] = $rangeWorkHour;
    		}
    	}
    
    	if ( !(count($workHoursDay)) ) {
    		$workHoursDay = $tempWorkHoursDay;
    	}
    
    	$countHours = count($workHoursDay);
    	if ($countHours) {
    		for ($i = 1; $i < $countHours; $i++) {
    			for ($j = 0; $j < $countHours-$i; $j++) {
    				$dataft = (float)str_replace(':', '', $workHoursDay[$j]['START']);
    				$datasc = (float)str_replace(':', '', $workHoursDay[$j+1]['END']);
    				if ($dataft > $datasc) {
    					$aux = $workHoursDay[$j+1];
    					$workHoursDay[$j+1] = $workHoursDay[$j];
    					$workHoursDay[$j] = $aux;
    				}
    			}
    		}
    
    		foreach ($workHoursDay as $value) {
    			$iniTime = (float)str_replace(':', '', ((strlen($value['START']) == 8) ? $value['START'] : $value['START'].':00'));
    			$finTime = (float)str_replace(':', '', ((strlen($value['END']) == 8) ? $value['END'] : $value['END'].':00'));
    
    			if ( $timeDate <= $iniTime ) {
    				$nextWorkHours['STATUS'] = true;
    				$nextWorkHours['DATE']   = $auxIniDate['0'] . ' ' . ((strlen($value['START']) == 8) ? $value['START'] : $value['START'].':00');
    				return $nextWorkHours;
    			} elseif ( ($iniTime <= $timeDate)  && ($timeDate < $finTime) ) {
    				$nextWorkHours['STATUS'] = true;
    				$nextWorkHours['DATE']   = $date;
    				return $nextWorkHours;
    			}
    		}
    	}
    
    	$nextWorkHours['STATUS'] = false;
    	return $nextWorkHours;
    }
    
    public function is_holiday ($date, $holidays = array())
    {
    	$holidays = (count($holidays)) ? $holidays : $this->pmCalendarData['HOLIDAY'];
    
    	$auxIniDate = explode(' ', $date);
    	$iniDate = $auxIniDate['0'];
    	$iniDate = strtotime($iniDate);
    
    	foreach ($holidays as $value) {
    		$holidayStartDate = strtotime(date('Y-m-d',strtotime($value['CALENDAR_HOLIDAY_START'])));
    		$holidayEndDate   = strtotime(date('Y-m-d',strtotime($value['CALENDAR_HOLIDAY_END'])));
    
    		if ( ($holidayStartDate <= $iniDate) && ($iniDate <= $holidayEndDate) ) {
    			return true;
    		}
    	}
    	return false;
    }

    /**
     * getnextValidBusinessHoursrange is used recursivily to find a valid BusinessHour
     * for the given $date and $time.
     * This function use all the exeptions defined for
     * Working days, Business Hours and Holidays.
     *
     * @author Hugo Loza <hugo@colosa.com>
     * @name getNextValidBusinessHoursRange
     * @param date $date
     * @param time $time
     *
     * @var array $businessHoursA Object with all the infromation about the valid Business Hours found
     * $return array('DATE'=>$date,'TIME'=>$time,'BUSINESS_HOURS'=>$businessHoursA)
     */
    function getNextValidBusinessHoursRange ($date, $time)
    {
        $this->addCalendarLog( "================= Start : $date,$time ================" );

        //First Validate if is a valid date
        $sw_valid_date = false;
        $sw_date_changed = false;
        while (! $sw_valid_date) {
            $dateArray = explode( "-", $date );
            $hour = 0;
            $minute = 0;
            $second = 0;
            $month = $dateArray[1];
            $day = $dateArray[2];
            $year = $dateArray[0];
            $weekDay = date( "w", mktime( $hour, $minute, $second, $month, $day, $year ) );
            $weekDayLabel = date( "l", mktime( $hour, $minute, $second, $month, $day, $year ) );
            $dateInt = mktime( $hour, $minute, $second, $month, $day, $year );

            $this->addCalendarLog( "**** $weekDayLabel ($weekDay) * $date" );
            $sw_week_day = false;
            $sw_not_holiday = true;

            if (in_array( $weekDay, $this->calendarDefinition['CALENDAR_WORK_DAYS_A'] )) {
                $sw_week_day = true;
            }
            if (! $sw_week_day) {
                $this->addCalendarLog( "Valid working Dates: " . $this->calendarDefinition['CALENDAR_WORK_DAYS_A'] );
                $this->addCalendarLog( "- Non working Day" );
            }

            foreach ($this->calendarDefinition['HOLIDAY'] as $key => $holiday) {
                //Normalize Holiday date to SAME year of date


                $holidayStartA = explode( " ", $holiday['CALENDAR_HOLIDAY_START'] );
                $holidayStartA = explode( "-", $holidayStartA[0] );

                $normalizedHolidayStart = date( "Y-m-d", mktime( $hour, $minute, $second, $holidayStartA[1], $holidayStartA[2], $year ) );
                $normalizedHolidayStartInt = mktime( $hour, $minute, $second, $holidayStartA[1], $holidayStartA[2], $year );

                $holidayEndA = explode( " ", $holiday['CALENDAR_HOLIDAY_END'] );
                $holidayEndA = explode( "-", $holidayEndA[0] );
                $normalizedHolidayEnd = date( "Y-m-d", mktime( $hour, $minute, $second, $holidayEndA[1], $holidayEndA[2], $year ) );
                $normalizedHolidayEndInt = mktime( $hour, $minute, $second, $holidayEndA[1], $holidayEndA[2], $year );
                $sw_not_holiday_aux = true;
                if ($dateInt >= $normalizedHolidayStartInt && $dateInt <= $normalizedHolidayEndInt) {
                    $sw_not_holiday = false;
                    $sw_not_holiday_aux = false;
                }
                if (! $sw_not_holiday_aux) {
                    $this->addCalendarLog( "It is a holiday -> " . $holiday['CALENDAR_HOLIDAY_NAME'] . " ($normalizedHolidayStart - $normalizedHolidayEnd)" );
                }
            }
            $sw_valid_date = $sw_week_day && $sw_not_holiday;

            if (! $sw_valid_date) { //Go to next day
                $date = date( "Y-m-d", mktime( $hour, $minute + 1, $second, $month, $day + 1, $year ) );
                $sw_date_changed = true;
            } else {
                $this->addCalendarLog( "FOUND VALID DATE -> $date" );

                //We got a valid day, now get the valid Business Hours
                //Here Need to find a rule to get the most nea
                $businessHoursA = array ();
                $prevHour = "00:00";

                if ($sw_date_changed) { // If date has changed then Use the first available period
                    $time = "00:01";
                }

                foreach ($this->calendarDefinition['BUSINESS_DAY'] as $keyBH => $businessHours) {

                    // First the period may correspond to ALL or to the current week day
                    if (($businessHours['CALENDAR_BUSINESS_DAY'] == 7) || ($businessHours['CALENDAR_BUSINESS_DAY'] == $weekDay)) {
                        $this->addCalendarLog( "Validating ($time/$prevHour) From: " . $businessHours['CALENDAR_BUSINESS_START'] . " to " . $businessHours['CALENDAR_BUSINESS_END'] );

                        //Prev Hour
                        $prevHourA = explode( ":", $prevHour );
                        $prevHourSeconds = ($prevHourA[0] * 60 * 60) + ($prevHour[1] * 60);

                        $calendarBusinessStartA = explode( ":", $businessHours['CALENDAR_BUSINESS_START'] );
                        $calendarBusinessStartSeconds = ($calendarBusinessStartA[0] * 60 * 60) + ($calendarBusinessStartA[1] * 60);

                        $calendarBusinessEndA = explode( ":", $businessHours['CALENDAR_BUSINESS_END'] );
                        $calendarBusinessEndSeconds = ($calendarBusinessEndA[0] * 60 * 60) + ($calendarBusinessEndA[1] * 60);

                        $timeAuxA = explode( ":", $time );
                        $timeAuxSeconds = ($timeAuxA[0] * 60 * 60) + ($timeAuxA[1] * 60);

                        if (($timeAuxSeconds >= $prevHourSeconds) && ($timeAuxSeconds < $calendarBusinessEndSeconds)) {
                            $this->addCalendarLog( "*** FOUND VALID BUSINESS HOUR " . $businessHours['CALENDAR_BUSINESS_START'] . " - " . $businessHours['CALENDAR_BUSINESS_END'] );

                            if ($timeAuxSeconds < $calendarBusinessStartSeconds) { //Out of range then assign first hour
                                $this->addCalendarLog( "Set to default start hour to: " . $businessHours['CALENDAR_BUSINESS_START'] );
                                $time = $businessHours['CALENDAR_BUSINESS_START'];
                            }
                            $prevHour = $businessHours['CALENDAR_BUSINESS_END'];
                            $businessHoursA = $businessHours;
                        }

                    }
                }
            }

            if (empty( $businessHoursA )) {
                $this->addCalendarLog( "> No Valid Business Hour found for current date, go to next" );
                $date = date( "Y-m-d", mktime( $hour, $minute + 1, $second, $month, $day + 1, $year ) );
                $sw_date_changed = true;
                $sw_valid_date = false;
            }

        }

        $return['DATE'] = $date;
        $return['TIME'] = $time;
        $return['BUSINESS_HOURS'] = $businessHoursA;

        return $return;
    }

    /**
	 * Calculate date adding a duration, will considerate the calendar
	 *
	 * @param string $initialDate
	 * @param string $duration
	 * @param string $formatDuration
	 * @param array $calendarData
	 *
	 * @return string
    */
	public function dashCalculateDate($initialDate, $duration, $formatDuration, $calendarData = [])
	{
		if (G::toUpper($formatDuration) == 'DAYS') {
			$duration = $duration * $calendarData['HOURS_FOR_DAY'];
		}
		if (G::toUpper($formatDuration) == 'MINUTES') {
			$duration = $duration / 60;
		}
		$hoursDuration = (float)$duration;
		$newDate = $initialDate;

		while ($hoursDuration > 0) {
			$newDate = $this->dashGetIniDate($newDate, $calendarData);
			$rangeWorkHour = $this->dashGetRangeWorkHours($newDate, $calendarData['BUSINESS_DAY']);
			$onlyDate = (date('Y-m-d', strtotime($newDate))) . ' ' . $rangeWorkHour['END'];
			$rangeWorkHourTotal = (float)$rangeWorkHour['TOTAL'];
			if ($hoursDuration >= $rangeWorkHourTotal ||
				((strtotime($onlyDate) - strtotime($newDate)) < (($hoursDuration) * 3600))
			) {
				$secondRes = (float)(strtotime($onlyDate) - strtotime($newDate));
				$newDate = $onlyDate;
				$hoursDuration -= (float)($secondRes / 3600);
			} else {
				$newDate = date('Y-m-d H:i:s', strtotime('+' . round((($hoursDuration) * 3600), 5) . ' seconds', strtotime($newDate)));
				$hoursDuration = 0;
			}
		}
		return $newDate;
	}
    
    //Calculate the duration betwen two dates with a calendar
    public function dashCalculateDurationWithCalendar ($iniDate, $finDate = null, $calendarData = array())
    {
    	if ((is_null($finDate)) || ($finDate == '')) {
    		$finDate = date('Y-m-d H:i:s');
    	}
    

        if ((strtotime($finDate)) <= (strtotime($iniDate))) {
            return 0.00;
        }
	
        $secondDuration = 0.00;
    
    	$finDate = $this->dashGetIniDate($finDate, $calendarData);
    	$newDate = $iniDate;

		$timeIniDate = strtotime($iniDate);
		$timeFinDate = strtotime($finDate);

    	while ($timeIniDate < $timeFinDate) {
    		$newDate = $this->dashGetIniDate($newDate, $calendarData);
    
    		$rangeWorkHour = $this->dashGetRangeWorkHours($newDate, $calendarData['BUSINESS_DAY']);
    		$onlyDate = (date('Y-m-d',strtotime($newDate))) . ' ' . $rangeWorkHour['END'];
    
    		if ( (strtotime($finDate)) < (strtotime($onlyDate)) ) {
    			$secondRes = ( ((float)strtotime($finDate)) - ((float)strtotime($newDate)) );
    			$timeIniDate = strtotime($finDate);
    			$secondDuration += (float)$secondRes;
    		} else {
    			$secondRes = ( ((float)strtotime($onlyDate)) - ((float)strtotime($newDate)) );
    			$newDate = $onlyDate;
    			$timeIniDate = strtotime($onlyDate);
    			$secondDuration += (float)$secondRes;
    		}
    	}
    	return $secondDuration;
    }
    
    public function dashGetIniDate ($iniDate, $calendarData = array())
    {
    	$flagIniDate = true;
    
    	while ($flagIniDate) {
    		// 1 if it's a work day
    		$weekDay = date('w',strtotime($iniDate));
    		if ( !(in_array($weekDay, $calendarData['CALENDAR_WORK_DAYS_A'])) ) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		}
    
    		// 2 if it's a holiday
    		$iniDateHolidayDay = $this->dashIs_holiday($iniDate, $calendarData['HOLIDAY']);
    		if ($iniDateHolidayDay) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		}
    
    		// 3 if it's work time
    		$workHours = $this->dashNextWorkHours($iniDate, $weekDay, $calendarData['BUSINESS_DAY']);
    		if ( !($workHours['STATUS']) ) {
    			$iniDate = date('Y-m-d'.' 00:00:00' , strtotime('+1 day', strtotime($iniDate)));
    			continue;
    		} else {
    			$iniDate = $workHours['DATE'];
    		}
    		$flagIniDate = false;
    	}
    
    	return $iniDate;
    }
    
    public function dashNextWorkHours ($date, $weekDay, $workHours = array())
    {
    	$auxIniDate = explode(' ', $date);
    
    	$timeDate = $auxIniDate['1'];
    	$timeDate = (float)str_replace(':', '', ((strlen($timeDate) == 8) ? $timeDate : $timeDate.':00') );
    	$nextWorkHours = array();
    
    	$workHoursDay = array();
    	$tempWorkHoursDay = array();
    
    	foreach ($workHours as $value) {
    		if ($value['CALENDAR_BUSINESS_DAY'] == $weekDay) {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$workHoursDay[] = $rangeWorkHour;
    		}
    
    		if ($value['CALENDAR_BUSINESS_DAY'] == '7') {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$tempWorkHoursDay[] = $rangeWorkHour;
    		}
    	}
    
    	if ( !(count($workHoursDay)) ) {
    		$workHoursDay = $tempWorkHoursDay;
    	}
    
    	$countHours = count($workHoursDay);
    	if ($countHours) {
    		for ($i = 1; $i < $countHours; $i++) {
    			for ($j = 0; $j < $countHours-$i; $j++) {
    				$dataft = (float)str_replace(':', '', $workHoursDay[$j]['START']);
    				$datasc = (float)str_replace(':', '', $workHoursDay[$j+1]['END']);
    				if ($dataft > $datasc) {
    					$aux = $workHoursDay[$j+1];
    					$workHoursDay[$j+1] = $workHoursDay[$j];
    					$workHoursDay[$j] = $aux;
    				}
    			}
    		}
    
    		foreach ($workHoursDay as $value) {
    			$iniTime = (float)str_replace(':', '', ((strlen($value['START']) == 8) ? $value['START'] : $value['START'].':00'));
    			$finTime = (float)str_replace(':', '', ((strlen($value['END']) == 8) ? $value['END'] : $value['END'].':00'));
    
    			if ( $timeDate <= $iniTime ) {
    				$nextWorkHours['STATUS'] = true;
    				$nextWorkHours['DATE']   = $auxIniDate['0'] . ' ' . ((strlen($value['START']) == 8) ? $value['START'] : $value['START'].':00');
    				return $nextWorkHours;
    			} elseif ( ($iniTime <= $timeDate)  && ($timeDate < $finTime) ) {
    				$nextWorkHours['STATUS'] = true;
    				$nextWorkHours['DATE']   = $date;
    				return $nextWorkHours;
    			}
    		}
    	}
    
    	$nextWorkHours['STATUS'] = false;
    	return $nextWorkHours;
    }
    
    public function dashIs_holiday ($date, $holidays = array())
    {
    	$auxIniDate = explode(' ', $date);
    	$iniDate = $auxIniDate['0'];
    	$iniDate = strtotime($iniDate);
    
    	foreach ($holidays as $value) {
    		$holidayStartDate = strtotime(date('Y-m-d',strtotime($value['CALENDAR_HOLIDAY_START'])));
    		$holidayEndDate   = strtotime(date('Y-m-d',strtotime($value['CALENDAR_HOLIDAY_END'])));
    
    		if ( ($holidayStartDate <= $iniDate) && ($iniDate <= $holidayEndDate) ) {
    			return true;
    		}
    	}
    	return false;
    }
    
    public function dashGetRangeWorkHours ($date, $workHours)
    {
    	$auxIniDate = explode(' ', $date);
    	$timeDate = $auxIniDate['1'];
    	$timeDate = (float)str_replace(':', '', ((strlen($timeDate) == 8) ? $timeDate : $timeDate.':00') );
    	$weekDay = date('w',strtotime($date));
    
    	$workHoursDay = array();
    	$tempWorkHoursDay = array();
    
    	foreach ($workHours as $value) {
    		if ($value['CALENDAR_BUSINESS_DAY'] == $weekDay) {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$workHoursDay[] = $rangeWorkHour;
    		}
    
    		if ($value['CALENDAR_BUSINESS_DAY'] == '7') {
    			$rangeWorkHour = array();
    			$timeStart = $value['CALENDAR_BUSINESS_START'];
    			$timeEnd   = $value['CALENDAR_BUSINESS_END'];
    			$rangeWorkHour['START'] = ((strlen($timeStart) == 8) ? $timeStart : $timeStart.':00');
    			$rangeWorkHour['END']   = ((strlen($timeEnd) == 8) ? $timeEnd : $timeEnd.':00');
    
    			$tempWorkHoursDay[] = $rangeWorkHour;
    		}
    	}
    
    	if ( !(count($workHoursDay)) ) {
    		$workHoursDay = $tempWorkHoursDay;
    	}
    
    	foreach ($workHoursDay as $value) {
    		$iniTime = (float)str_replace(':', '', $value['START']);
    		$finTime = (float)str_replace(':', '', $value['END']);
    
    		if ( ($iniTime <= $timeDate)  && ($timeDate <= $finTime) ) {
    			//pr($finTime .' menos '.$iniTime .' = '.($finTime-$iniTime));
    			$value['TOTAL'] = (($finTime-$iniTime)/10000);
    			return $value;
    		}
    	}
    	return false;
    }

}
?>
