<?php
class Calendarista_GoogleCalendarHelper{
	public $client;
	public $service = null;
	const applicationName = 'Calendarista';
	protected $repo;
	public function __construct($userId){
		$this->repo = new Calendarista_GcalProfileRepository();
		$this->clientData = $this->repo->readByUserId($userId);
		if(!$this->clientData){
			return;
		}
		$this->client = self::getClient($this->clientData);
		if(!$this->clientData['accessToken']){
			return;
		}
		if(!$this->client){
			return;
		}
		try {
			$this->service = new Google_Service_Calendar($this->client);
		} catch (Exception $e) {
			Calendarista_ErrorLogHelper::insert($e->getMessage());
		}
	}
	public function getAuthorizationURL(){
		return $this->client ? $this->client->createAuthUrl() : null;
	}
	public function getCalendarList(){
		$result = array();
		if(!$this->service){
			return $result;
		}
		$calendarList = null;
		try{
			$calendarList = $this->service->calendarList->listCalendarList();
		} catch (Exception $e) {
			Calendarista_ErrorLogHelper::insert($e->getMessage());
		}
		if(!$calendarList){
			return $result;
		}
		while(true) {
			foreach ($calendarList->getItems() as $calendarListEntry) {
				array_push($result, array('id'=>$calendarListEntry->getId(), 'summary'=>$calendarListEntry->getSummary()));
			}
			$pageToken = $calendarList->getNextPageToken();
			if ($pageToken) {
				$optParams = array('pageToken' => $pageToken);
				$calendarList = $this->service->calendarList->listCalendarList($optParams);
			} else {
				break;
			}
		}
		return $result;
	}
	public static function getClient($clientData){
		require_once CALENDARISTA_GCAL . 'autoload.php';
		ini_set('max_execution_time', 1800); //1800 seconds = 30 minutes
		$repo = new Calendarista_GcalProfileRepository();
		$client = new Google_Client();
		$redirectUri = get_site_url();
		$state = self::base64UrlEncode('{ "calendarista_action" : "gcal" , "calendarista_userid" : ' . $clientData['userId'] . '}');
		$client->setApplicationName(self::applicationName);
		$client->setClientId($clientData['clientId']);
		$client->setClientSecret($clientData['clientSecret']);
		$client->setRedirectUri($redirectUri);
		$client->setState($state);
		$client->setApprovalPrompt('force');
		$client->setAccessType('offline');
		//request read/write access
		$client->addScope('https://www.googleapis.com/auth/calendar');
		if ($clientData['accessToken']){
			$client->setAccessToken($clientData['accessToken']);
			if($client->isAccessTokenExpired()){
				$accessToken = json_decode($clientData['accessToken']);
				$finally = true;
				try{
					$client->refreshToken($accessToken->refresh_token);
				} catch (Exception $e) {
					Calendarista_ErrorLogHelper::insert($e->getMessage());
					$finally = false;
				}
				if($finally){
					$clientData['accessToken'] = $client->getAccessToken();
					$repo->update($clientData);
				}else{
					//client access token broken, if we are here it means google says invalid_grant. reauthorize..
					$repo->update(array(
						'id'=>$clientData['id']
						, 'clientId'=>$clientData['clientId']
						, 'clientSecret'=>$clientData['clientSecret']
						, 'accessToken'=>null
					));
					return false;
				}
			}
		}
		return $client;
	}
	public static function handleAccessToken($userId, $token){
		require_once CALENDARISTA_GCAL . 'autoload.php';
		$redirectUri = get_site_url();
		$repo = new Calendarista_GcalProfileRepository();
		$clientData = $repo->readByUserId($userId);
		if(!$clientData){
			return;
		}
		$client = new Google_Client();
		$client->setApplicationName(self::applicationName);
		$client->setRedirectUri($redirectUri);
		$client->setClientId($clientData['clientId']);
		$client->setClientSecret($clientData['clientSecret']);
		$client->addScope('https://www.googleapis.com/auth/calendar');
		if ($token) {
			//first time, client has granted us permission
			$client->authenticate($token);  
			$clientData['accessToken'] = $client->getAccessToken();
			$repo->update($clientData);
		}
	}
	public static function syncAll(){
		$gcalProfileRepo = new Calendarista_GcalProfileRepository();
		$result = $gcalProfileRepo->readAll(array());
		if(!$result){
			return;
		}
		$clientDataList = $result['resultset'];
		$gcalRepo = new Calendarista_GcalRepository();
		foreach($clientDataList as $clientData){
			$client = self::getClient($clientData);
			if(!$client){
				continue;
			}
			$service = new Google_Service_Calendar($client);
			$entries = $gcalRepo->readByGcalProfileId($clientData['id']);
			if($entries){
				self::export($clientData, $service, $entries);
				self::import($clientData, $service, $entries);
			}
		}
	}
	public static function syncByGCal($gcalProfileId, $gcalId){
		$gcalProfileRepo = new Calendarista_GcalProfileRepository();
		$clientData = $gcalProfileRepo->read($gcalProfileId);
		$result = array('inserted'=>0, 'deleted'=>0, 'exported'=>0);
		if(!$clientData){
			return $result;
		}
		$client = self::getClient($clientData);
		if(!$client){
			return $result;
		}
		$service = new Google_Service_Calendar($client);
		$gcalRepo = new Calendarista_GcalRepository();
		$entries = $gcalRepo->read($gcalId);
		$exportedResult = self::export($clientData, $service, array($entries));
		$result = self::import($clientData, $service, array($entries));
		$result['exported'] = $exportedResult;
		return $result;
	}
	public static function export($clientData, $service, $entries){
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$result = 0;
		foreach($entries as $entry){
			$bookings = $bookedAvailabilityRepo->readAllByAvailabilityForExport($entry['availabilityId'], $entry['gcalProfileId']);
			$calendar = $service->calendars->get($entry['calendarId']);
			$timezone = $calendar->getTimezone();
			foreach($bookings as $booking){
				$event = self::populateEvent($booking, $timezone);
				$created = self::addEvent($event, $service, $entry, (int)$booking->id);
				if($created){
					++$result;
				}
			}
		}
		return $result;
	}
	public static function import($clientData, $service, $entries){
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$projectRepo = new Calendarista_ProjectRepository();
		$availabilityRepo = new Calendarista_AvailabilityRepository();
		$timeslotRepo = new Calendarista_TimeslotRepository();
		$result = array('inserted'=>0, 'updated'=>0, 'deleted'=>0);
		foreach($entries as $entry){
			$calendarId = $entry['calendarId'];
			$project = $projectRepo->read($entry['projectId']);
			$availability = $availabilityRepo->read($entry['availabilityId']);
			$eventList = array();
			$timeslots = new Calendarista_Timeslots();
			if(in_array($project->calendarMode, Calendarista_CalendarMode::$SUPPORTS_TIMESLOTS)){
				$timeslots = $timeslotRepo->readAllByAvailability($entry['availabilityId']);
			}
			$calendar = $service->calendars->get($calendarId);
			$timezone = $calendar->getTimezone();
			$events = $service->events->listEvents($calendarId, array('timeMin'=>date('c')));
			while(true){
				foreach($events->getItems() as $event){
					$eventId = $event->getId();
					array_push($eventList, $eventId);
					if(!$event->start || !$event->end){
						continue;
					}
					$appointment = $bookedAvailabilityRepo->readBySynchedBookingId($eventId);
					if($appointment && (!is_null($appointment->synchedMode) && (int)$appointment->synchedMode === Calendarista_SynchedMode::GCAL_EXPORTED)){
						continue;
					}
					$appointment = $bookedAvailabilityRepo->findBySyncInfo(array('synchedBookingId'=>$eventId, 'gcalId'=>$entry['gcalProfileId'], 'calendarId'=>$calendarId, 'availabilityId'=>$entry['availabilityId']));
					$flag = self::insertOrUpdateAppointment(array(
						'event'=>$event
						, 'appointment'=>$appointment
						, 'entry'=>$entry
						, 'project'=>$project
						, 'timeslots'=>$timeslots
						, 'availability'=>$availability
						, 'bookedAvailabilityRepo'=>$bookedAvailabilityRepo
						, 'timeslotRepo'=>$timeslotRepo
						, 'timezone'=>$timezone
						, 'calendarName'=>$calendar->getSummary()
					));
					$result['inserted'] += $flag['inserted'];
					$result['updated'] += $flag['updated'];
				}
				$pageToken = $events->getNextPageToken();
				if ($pageToken){
					$optParams = array('pageToken'=>$pageToken);
					$events = $service->events->listEvents($calendarId, $optParams);
				}else{
					break;
				}
			}
			//delete events that have been deleted @ google calendar
			$localEventList = $bookedAvailabilityRepo->readAllImportedAndExported($entry['availabilityId'], $entry['gcalProfileId']);
			$flag = self::deleteMultipleEvents($localEventList, $eventList);
			$result['deleted'] += $flag;
		}
		return $result;
	}
	protected static function getEmail($event){
		$attendees = $event->getAttendees();
		if(count($attendees) > 0){
			return $attendees[0]->getEmail();
		}
		$creator = $event->getCreator();
		if($creator){
			return $creator->getEmail();
		}
		return null;
	}
	protected static function insertOrUpdateAppointment($args){
		$result = array('inserted'=>0, 'updated'=>0);
		$event = $args['event'];
		$appointment = $args['appointment'];
		$entry = $args['entry'];
		$project = $args['project'];
		$timeslots = $args['timeslots'];
		$availability = $args['availability'];
		$bookedAvailabilityRepo = $args['bookedAvailabilityRepo'];
		$timeslotRepo = $args['timeslotRepo'];
		$timezone = $args['timezone'];
		$calendarName = $args['calendarName'];
		$error = __('Import Failed. Google Calendar name: [%s]. Service name: [%s]. Date to import: [%s - %s].', 'calendarista');
		$supportsTimeslots = in_array($project->calendarMode, Calendarista_CalendarMode::$SUPPORTS_TIMESLOTS);
		$eventId = $event->getId();
		$start = $event->start->dateTime;
		$hasTime = true;
		$startTimezone = $timezone;
		$endTimezone = $timezone;
        if (empty($start)) {
			$hasTime = false;
            $start = $event->start->date;
        }
		if($event->start->timeZone){
			$startTimezone = $event->start->timeZone;
		}
		if($event->end->timeZone){
			$endTimezone = $event->start->timeZone;
		}
		$end = $event->end->dateTime;
		if (empty($end)) {
			$end = $event->end->date;
		}
		
		$sdt = new Calendarista_DateTime($start);
		$edt = new Calendarista_DateTime($end);
		if($startTimezone){
			$originalTimezone = Calendarista_TimeHelper::setTimezone($startTimezone);
			$dtzStart = new DateTimeZone($startTimezone);
			$sdt->setTimezone($dtzStart);
			Calendarista_TimeHelper::setTimezone($originalTimezone);
		}
		if($endTimezone){
			$originalTimezone = Calendarista_TimeHelper::setTimezone($endTimezone);
			$dtzEnd = new DateTimeZone($endTimezone);
			$edt->setTimezone($dtzEnd);
			Calendarista_TimeHelper::setTimezone($originalTimezone);
		}
		if ($edt > $sdt && in_array($project->calendarMode, array(
			Calendarista_CalendarMode::SINGLE_DAY
			, Calendarista_CalendarMode::MULTI_DATE_RANGE
			, Calendarista_CalendarMode::CHANGEOVER
			, Calendarista_CalendarMode::PACKAGE
		))){
			$edt = $edt->modify('-1 day');
		}
		$st = $supportsTimeslots ? $sdt->format('g:i a') : null;
		$et = $supportsTimeslots ? $edt->format('g:i a') : null;
		$startTimeslots = array();
		$endTimeslots = array();
		if($supportsTimeslots && $hasTime){
			$startTimeslots = Calendarista_TimeslotHelper::filterTimeslots($sdt, $timeslots);
			$endTimeslots = Calendarista_TimeslotHelper::filterTimeslots($edt, $timeslots);
		}
		//now get the invidual timeslots, faster, no sql
		$startTimeslot = self::getTimeslot($st, $startTimeslots);
		$endTimeslot = self::getTimeslot($et, $endTimeslots);
		$sameDay = $sdt->format(CALENDARISTA_DATEFORMAT) == $edt->format(CALENDARISTA_DATEFORMAT);
		$msg = sprintf($error, $calendarName, $project->name, $sdt->format(CALENDARISTA_DATEFORMAT), $edt->format(CALENDARISTA_DATEFORMAT));
		if($supportsTimeslots){
			$msg = sprintf($error, $calendarName, $project->name, $sdt->format(CALENDARISTA_FULL_DATEFORMAT), $edt->format(CALENDARISTA_FULL_DATEFORMAT));
		}
		switch($project->calendarMode){
			case 0: //SINGLE_DAY
			if(!$sameDay || !self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 1: //SINGLE_DAY_AND_TIME
			case 9: //SINGLE_DAY_AND_TIME_WITH_PADDING
			if(!$sameDay || !self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			if($startTimeslot->id === -1 || ($availability->maxTimeslots === 1  && $startTimeslot->id !== $endTimeslot->id)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 2: //SINGLE_DAY_AND_TIME_RANGE
			if(!self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			if($startTimeslot->id === -1 || $endTimeslot->id === -1 || $startTimeslot->id === $endTimeslot->id){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 3: //MULTI_DATE_RANGE
			case 7: //ROUND_TRIP
			if(!self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 4: //MULTI_DATE_AND_TIME_RANGE
			if(($sameDay && $startTimeslot->id === $endTimeslot->id) || !self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			if($startTimeslot->id === -1 || $endTimeslot->id === -1){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 8: //ROUND_TRIP_WITH_TIME
			if(!self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			if($startTimeslot->id === -1){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			if(!$availability->returnSameDay){
				return $result;
			}
			break;
			case 5: //CHANGEOVER
			if($sameDay || !self::validDateDate($availability, $sdt, $edt)){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 6: //PACKAGE
			$validResult = self::validPackage($availability,  $sdt->format(CALENDARISTA_DATEFORMAT), $edt->format(CALENDARISTA_DATEFORMAT));
			if(!$validResult){
				//Calendarista_ErrorLogHelper::insert($msg);
				return $result;
			}
			break;
			case 10: //SIMPLE_EVENT
			break;
		}

		$status = self::getLocalStatus($event);
		$summary = $event->getSummary();
		$description = $event->getDescription();
		$location = $event->getLocation();
		$email = self::getEmail($event);
		if($appointment){
			//it's an existing event, update
			$appointment->userEmail = $email;
			$appointment->fromDate = $sdt->format(CALENDARISTA_FULL_DATEFORMAT);
			$appointment->toDate = $edt->format(CALENDARISTA_FULL_DATEFORMAT);
			$appointment->startTimeId = $startTimeslot->id !== -1 ? $startTimeslot->id : null;
			$appointment->endTimeId = $endTimeslot->id !== -1 ? $endTimeslot->id : null;
			$appointment->status = $status;
			$appointment->synchedBookingDescription = $description;
			$appointment->synchedBookingSummary = $summary;
			$appointment->synchedBookingLocation = $location;
			$bookedAvailabilityRepo->update((array)$appointment);
			++$result['updated'];
			return $result;
		}
		//it's a new event, insert
		$bookedAvailabilityRepo->insert(array(
			'availabilityId'=>$availability->id
			, 'projectId'=>$project->id
			, 'projectName'=>$project->name
			, 'availabilityName'=>$availability->name
			, 'fromDate'=>$sdt->format(CALENDARISTA_FULL_DATEFORMAT)
			, 'toDate'=>$edt->format(CALENDARISTA_FULL_DATEFORMAT)
			, 'startTimeId'=>$startTimeslot->id !== -1 ? $startTimeslot->id : null
			, 'endTimeId'=>$endTimeslot->id !== -1 ? $endTimeslot->id : null
			, 'seats'=>1
			, 'color'=>$availability->color
			, 'timezone'=>$availability->timezone
			, 'serverTimezone'=>$availability->timezone
			, 'fullDay'=>$availability->fullDay
			, 'cost'=>$availability->cost
			, 'returnCost'=>$availability->returnCost
			, 'calendarMode'=>$project->calendarMode
			, 'userEmail'=>$email
			, 'regionAddress'=>$availability->regionAddress
			, 'regionLat'=>$availability->regionLat
			, 'regionLng'=>$availability->regionLng
			, 'status'=>$status
			, 'synchedBookingId'=>$eventId
			, 'synchedBookingDescription'=>$description
			, 'synchedBookingSummary'=>$summary
			, 'synchedBookingLocation'=>$location
			, 'synchedMode'=>Calendarista_SynchedMode::GCAL_IMPORTED
			, 'gcalId'=>$entry['gcalProfileId']
			, 'calendarId'=>$entry['calendarId']
		));
		++$result['inserted'];
		if($startTimeslot->id !== -1){
			$timeslotRepo->updateSeat($startTimeslot);
		}
		if($endTimeslot->id !== -1){
			$singleDayTimeRange = $project->calendarMode === Calendarista_CalendarMode::SINGLE_DAY_AND_TIME_RANGE;
			if($supportsTimeslots && ($singleDayTimeRange || $availability->maxTimeslots > 1)){
				$start = $startTimeslot->id + 1;
				$end = $endTimeslot->id;
				for($i = $start;$i <= $end;$i++){
					if($i === $end && $singleDayTimeRange){
						//single day range, we're a step behind
						break;
					}
					$timeslotRepo->updateSeatById($i);
				}
			}else{
				$timeslotRepo->updateSeat($endTimeslot);
			}
		}
		return $result;
	}
	protected static function getTimeslot($t, $timeslots){
		if($t){
			foreach($timeslots as $timeslot){
				if($timeslot->timeslot == $t){
					return $timeslot;
				}
			}
		}
		return new Calendarista_Timeslot(array());
	}
	protected static function getLocalStatus($event){
		$status = $event->getStatus();
		$result = Calendarista_AvailabilityStatus::APPROVED;
		if($status == 'tentative'){
			$result = Calendarista_AvailabilityStatus::PENDING;
		}else if($status == 'cancelled'){
			$result = Calendarista_AvailabilityStatus::CANCELLED;
		}
		return $result;
	}
	protected static function populateEvent($booking, $timezone){
		$originalTimezone = null;
		if($timezone){
			$originalTimezone = Calendarista_TimeHelper::setTimezone($timezone);
		}
		$event = new Google_Service_Calendar_Event();
		$status = (int)$booking->status;
		if($status === Calendarista_AvailabilityStatus::APPROVED){
			$event->setStatus('confirmed');
		}else if($status === Calendarista_AvailabilityStatus::PENDING){
			$event->setStatus('tentative');
		}else if($status === Calendarista_AvailabilityStatus::CANCELLED){
			$event->setStatus('cancelled');
		}
		$summary = $booking->fullName;
		if((int)$booking->seats > 0){
			$summary = sprintf('%s (%s: %s)', $booking->fullName, __('Seats', 'calendarista'), $booking->seats);
		}
		$event->setSummary($summary);
		$description = self::getBookingDescription($booking);
		$event->setDescription($description);
		if($booking->regionAddress){
			$event->setLocation($booking->regionAddress);
		}
		$supportsTimeslots = in_array($booking->calendarMode, Calendarista_CalendarMode::$SUPPORTS_TIMESLOTS);
		$dateFormat = $supportsTimeslots ? 'c' : CALENDARISTA_DATEFORMAT;
		$fromDate = date($dateFormat, strtotime($booking->fromDate));
		$toDate = $fromDate;
		if(in_array($booking->calendarMode, array(Calendarista_CalendarMode::SINGLE_DAY_AND_TIME_RANGE, Calendarista_CalendarMode::MULTI_DATE_AND_TIME_RANGE, Calendarista_CalendarMode::ROUND_TRIP_WITH_TIME))){
			$toDate = date($dateFormat, strtotime($booking->toDate));
		}else if (in_array($booking->calendarMode, array(Calendarista_CalendarMode::MULTI_DATE_RANGE, Calendarista_CalendarMode::CHANGEOVER, Calendarista_CalendarMode::PACKAGE))){
			$toDate =  date($dateFormat, strtotime('+1 day', strtotime($booking->toDate)));
		}
		$start = new Google_Service_Calendar_EventDateTime();
		if($supportsTimeslots){
			$start->setDateTime($fromDate);
		}else{
			$start->setDate($fromDate);
		}
		$event->setStart($start);
		
		$end = new Google_Service_Calendar_EventDateTime();
		if($supportsTimeslots){
			$end->setDateTime($toDate);
		}else{
			$end->setDate($toDate);
		}
		$event->setEnd($end);
		
		$attendee = new Google_Service_Calendar_EventAttendee();
		$attendee->setEmail($booking->email);
		$attendee->setDisplayName($booking->fullName);
		$event->attendees = array($attendee);
		
		if($originalTimezone){
			Calendarista_TimeHelper::setTimezone($originalTimezone);
		}
		return $event;
	}
	protected static function addEvent($event, $service, $entry, $bookingId){
		$created = false;
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$created = true;
		$createdEvent = null;
		try 
		{
			$createdEvent = $service->events->insert($entry['calendarId'], $event);
		} catch (Exception $e) {
			//Calendarista_ErrorLogHelper::insert($e->getMessage());
			$created = false;
		}
		if($created){
			$eventId = $createdEvent->getId();
			$bookedAvailabilityRepo->update(array(
				'synchedBookingId'=>$eventId
				, 'synchedMode'=>Calendarista_SynchedMode::GCAL_EXPORTED
				, 'gcalId'=>$entry['gcalProfileId']
				, 'calendarId'=>$entry['calendarId']
				, 'id'=>$bookingId
			));
		}
		return $created;
	}
	public static function insertEvent($bookingId){
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$booking = $bookedAvailabilityRepo->read($bookingId);
		$synchedModes = array(Calendarista_SynchedMode::GCAL_EXPORTED, Calendarista_SynchedMode::GCAL_IMPORTED);
		if(!$booking || in_array((int)$booking->synchedMode, $synchedModes)){
			return;
		}
		$gcalRepo = new Calendarista_GcalRepository();
		$entry = $gcalRepo->readByAvailabilityId((int)$booking->availabilityId);
		if(!$entry){
			return;
		}
		if(in_array($booking->calendarMode, array(Calendarista_CalendarMode::ROUND_TRIP_WITH_TIME))){
			$availabilityRepo = new Calendarista_AvailabilityRepository();
			$availability = $availabilityRepo->read($entry['availabilityId']);
			if(!$availability->returnSameDay){
				return;
			}
		}
		$gcalProfileRepo = new Calendarista_GcalProfileRepository();
		$clientData = $gcalProfileRepo->read($entry['gcalProfileId']);
		if(!$clientData){
			return;
		}
		$client = self::getClient($clientData);
		if(!$client){
			return;
		}
		$service = new Google_Service_Calendar($client);
		$created = true;
		$createdEvent = null;
		try 
		{
			$calendar = $service->calendars->get($entry['calendarId']);
			$timezone = $calendar->getTimezone();
			$event = self::populateEvent($booking, $timezone);
			$createdEvent = $service->events->insert($entry['calendarId'], $event);
		} catch (Exception $e) {
			self::logError($booking, $e);
			$created = false;
		}
		if($created){
			//if we are here, error has been solved
			update_option('calendarista_google_calendar_has_error', false);
			$eventId = $createdEvent->getId();
			$bookedAvailabilityRepo->update(array(
				'synchedBookingId'=>$eventId
				, 'synchedMode'=>Calendarista_SynchedMode::GCAL_EXPORTED
				, 'gcalId'=>$entry['gcalProfileId']
				, 'calendarId'=>$entry['calendarId']
				, 'id'=>$bookingId
			));
		}
	}
	public static function deleteEvent($bookingId){
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$booking = $bookedAvailabilityRepo->read($bookingId);
		$synchedModes = array(Calendarista_SynchedMode::GCAL_IMPORTED, Calendarista_SynchedMode::GCAL_EXPORTED);
		if(!$booking || !in_array((int)$booking->synchedMode, $synchedModes)){
			return;
		}
		if((int)$booking->synchedMode === Calendarista_SynchedMode::GCAL_IMPORTED){
			$bookedAvailabilityRepo->delete((int)$booking->id);
			return;
		}
		$gcalRepo = new Calendarista_GcalProfileRepository();
		$clientData = $gcalRepo->read((int)$booking->gcalId);
		if(!$clientData){
			return;
		}
		$client = self::getClient($clientData);
		if(!$client){
			return;
		}
		$service = new Google_Service_Calendar($client);
		$finally = true;
		try{
			$result = $service->events->delete($booking->calendarId, $booking->synchedBookingId);
		}catch(Exception $e){
			//Calendarista_ErrorLogHelper::insert($e->getMessage());
			$finally = false;
		}
		if($finally){
			$bookedAvailabilityRepo->undoGcalSync(array('id'=>$bookingId));
		}
	}
	public static function deleteExportedByAvailability($availabilityId, $gcalProfileId){
		$result = 0;
		$gcalRepo = new Calendarista_GcalProfileRepository();
		$clientData = $gcalRepo->read($gcalProfileId);
		if(!$clientData){
			return $result;
		}
		$client = self::getClient($clientData);
		if(!$client){
			return $result;
		}
		$service = new Google_Service_Calendar($client);
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$exportedEventList = $bookedAvailabilityRepo->readAllExported($availabilityId, $gcalProfileId);
		foreach($exportedEventList as $booking){
			$finally = true;
			try{
				$service->events->delete($booking['calendarId'], $booking['eventId']);
			}catch(Exception $e){
				//Calendarista_ErrorLogHelper::insert($e->getMessage());
				$finally = false;
			}
			if($finally){
				++$result;
				$bookedAvailabilityRepo->undoGcalSync(array('id'=>$booking['bookingId']));
			}
		}
		return $result;
	}
	protected static function deleteMultipleEvents($localEvents, $eventList){
		$result = 0;
		//deleting events that were deleted via google calendar directly.
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$orderRepo = new Calendarista_OrderRepository();
		$items = array();
		$gcalRepo = new Calendarista_GcalRepository();
		$gcalProfileRepo = new Calendarista_GcalProfileRepository();
		foreach($localEvents as $key1=>$iel){
			foreach($eventList as $key2=>$eventId){
				if($iel['eventId'] == $eventId){
					unset($localEvents[$key1]);
					break;
				}
			}
		}
		$gcalId = null;
		$service = null;
		foreach($localEvents as $item){
			if((int)$item['synchedMode'] === Calendarista_SynchedMode::GCAL_EXPORTED && !in_array($item['eventId'], $eventList)){
				if($gcalId != $item['gcalId']){
					$gcalId = $item['gcalId'];
					$clientData = $gcalProfileRepo->read($gcalId);
					if(!$clientData){
						$client = self::getClient($clientData);
						if(!$client){
							continue;
						}
						$service = new Google_Service_Calendar($client);
					}
				}
				if($service){
					try{
						$service->events->delete($item['calendarId'], $item['synchedBookingId']);
					}catch(Exception $e){
						//Calendarista_ErrorLogHelper::insert($e->getMessage());
					}	
				}
			}
			if(!in_array($item['eventId'], $eventList)){
				//imported booking has already been deleted at the source (google calendar), so remove it
				if((int)$item['synchedMode'] === Calendarista_SynchedMode::GCAL_EXPORTED){
					//a local event (one that has been exported) has an order associated with it, so delete the entire order as well.
					$orderRepo->delete($item['orderId']);
				}
				$bookedAvailabilityRepo->delete($item['bookingId']);
				++$result;
			}
		}
		return $result;
	}
	public static function deleteAllEvents($gcalId){
		$gcalRepo = new Calendarista_GcalRepository();
		$gcalProfileRepo = new Calendarista_GcalProfileRepository();
		$entries = $gcalRepo->readByGcalProfileId($gcalId);
		$clientData = $gcalProfileRepo->read($gcalId);
		if(!$clientData){
			return;
		}
		foreach($entries as $entry){
			$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
			$synchedModes = array(Calendarista_SynchedMode::GCAL_IMPORTED);
			$client = self::getClient($clientData);
			if(!$client){
				continue;
			}
			$service = new Google_Service_Calendar($client);
			$bookings = $bookedAvailabilityRepo->readAllImportedAndExported($entry['availabilityId'], $entry['gcalProfileId']);
			foreach($bookings as $booking){
				$synchedModes = array(Calendarista_SynchedMode::GCAL_IMPORTED, Calendarista_SynchedMode::GCAL_EXPORTED);
				if(!$booking || !in_array((int)$booking->synchedMode, $synchedModes)){
					return;
				}
				if($booking['synchedMode'] === Calendarista_SynchedMode::GCAL_IMPORTED){
					//delete also imported bookings from gcal
					$bookedAvailabilityRepo->delete($booking['bookingId']);
					continue;
				}
				//begin deleting exported bookings to gcal
				$finally = true;
				try{
					$service->events->delete($booking['calendarId'], $booking['eventId']);
				}catch(Exception $e){
					//Calendarista_ErrorLogHelper::insert($e->getMessage());
					$finally = false;
				}
				if($finally){
					$bookedAvailabilityRepo->undoGcalSync(array('id'=>$booking['bookingId']));
				}
			}
		}
	}
	public static function updateEvent($bookingId){
		$bookedAvailabilityRepo = new Calendarista_BookedAvailabilityRepository();
		$booking = $bookedAvailabilityRepo->read($bookingId);
		$synchedModes = array(Calendarista_SynchedMode::GCAL_EXPORTED);
		if(!$booking || !in_array((int)$booking->synchedMode, $synchedModes)){
			return;
		}
		$gcalRepo = new Calendarista_GcalProfileRepository();
		$clientData = $gcalRepo->read((int)$booking->gcalId);
		if(!$clientData){
			return;
		}
		$client = self::getClient($clientData);
		if(!$client){
			return;
		}
		$service = new Google_Service_Calendar($client);
		$calendar = $service->calendars->get($booking->calendarId);
		$timezone = $calendar->getTimezone();
		$event = self::populateEvent($booking, $timezone);
		try{
			$service->events->update($booking->calendarId, $booking->synchedBookingId, $event);
		}catch(Exception $e){
			//Calendarista_ErrorLogHelper::insert($e->getMessage());
		}
	}
	protected static function getBookingDescription($booking){
		$description = array();
		$orderId = (int)$booking->orderId;
		array_push($description, sprintf('%s: %s', __('ID', 'calendarista'), $booking->invoiceId));
		array_push($description, sprintf('%s - %s', $booking->projectName, $booking->availabilityName));
		if((int)$booking->seats > 0){
			array_push($description, sprintf('%s: %s', __('Seats', 'calendarista'), $booking->seats));
		}
		$optionals = self::getOptionals($orderId);
		if($optionals){
			array_push($description, $optionals);
		}
		$customFormFields = self::getCustomFormElements($orderId);
		if($customFormFields){
			array_push($description, $customFormFields);
		}
		$dynamicFields = self::getDynamicFields($orderId);
		if($dynamicFields){
			array_push($description, $dynamicFields);
		}
		$providerName = self::getServiceProviderName((int)$booking->availabilityId);
		if($providerName){
			array_push($description, $providerName);
		}
		$map = self::getMap($orderId, $booking->projectId);
		if($map){
			$description = array_merge($description, $map);
		}
		return join("\n", $description);
	}
	public static function getMap($orderId, $projectId){
		$stringResources = Calendarista_StringResourceHelper::getResource((int)$projectId);
		$data = Calendarista_NotificationEmailer::getMap($orderId);
		$result = array();
		if($data){
			if($data->fromAddress){
				array_push($result, sprintf('%s: %s', $stringResources['MAP_DEPARTURE_LABEL'], $data->fromAddress));
			}
			$waypoints = self::getWaypoints($orderId);
			if($waypoints){
				array_push($result, $waypoints);
			}
			if($data->toAddress){
				array_push($result, sprintf('%s: %s', $stringResources['MAP_DESTINATION_LABEL'], $data->toAddress));
			}
			if((float)$data->distance > 0){
				$unit = (int)$data->unitType === 0 ? __('km', 'calendarista') : __('miles', 'calendarista');
				array_push($result, sprintf('%s: %s %s', $stringResources['MAP_DISTANCE_LABEL'], $data->distance, $unit));
			}
			if((float)$data->duration > 0){
				$timeUnitLabels = Calendarista_StringResources::getTimeUnitLabels($stringResources);
				$duration = Calendarista_TimeHelper::secondsToTime((float)$data->duration, $timeUnitLabels);
				array_push($result, sprintf('%s: %s', $stringResources['MAP_DURATION_LABEL'], $duration));
			}
		}
		if(count($result) > 0){
			return $result;
		}
		return null;
	}
	public static function getWaypoints($orderId){
		$result = Calendarista_NotificationEmailer::getWaypoints($orderId);
		if($result){
			$result = str_replace('<br>', "\n", $result);
			$result = str_replace('<strong>', "**", $result);
			$result = str_replace('</strong>', "**", $result);
			$result = str_replace('<i>', "*", $result);
			$result = str_replace('</i>', "*", $result);
			return strip_tags($result);
		}
		return null;
	}
	public static function getOptionals($orderId){
		$result = Calendarista_NotificationEmailer::getOptionals($orderId);
		if($result){
			$result = str_replace('<br>', "\n", $result);
			$result = str_replace('<strong>', "**", $result);
			$result = str_replace('</strong>', "**", $result);
			$result = str_replace('<i>', "*", $result);
			$result = str_replace('</i>', "*", $result);
			return strip_tags($result);
		}
		return null;
	}
	public static function getCustomFormElements($orderId){
		$result = Calendarista_NotificationEmailer::getCustomFormElements($orderId);
		if($result){
			$result = str_replace('<br>', "\n", $result);
			$result = str_replace('<strong>', "**", $result);
			$result = str_replace('</strong>', "**", $result);
			$result = str_replace('<i>', "*", $result);
			$result = str_replace('</i>', "*", $result);
			return strip_tags($result);
		}
		return null;
	}
	public static function getDynamicFields($orderId){
		$result = Calendarista_NotificationEmailer::getDynamicFields($orderId);
		if($result){
			$result = str_replace('<br>', "\n", $result);
			$result = str_replace('<strong>', "**", $result);
			$result = str_replace('</strong>', "**", $result);
			$result = str_replace('<i>', "*", $result);
			$result = str_replace('</i>', "*", $result);
			return strip_tags($result);
		}
		return null;
	}
	public static function getServiceProviderName($availabilityId){
		$repo = new Calendarista_StaffRepository();
		$staff = $repo->readAll(array('availabilityId'=>$availabilityId));
		if($staff !== false && $staff['total'] > 0){
			return sprintf(__('Staff: %s', 'calendarista'), $staff['items'][0]['name']);
		}
		return null;
	}
	protected function serviceActive(){
		return $this->service !== null;
	}
	protected static function validDateDate($availability, $start, $end){
		$availabilityHelper = new Calendarista_AvailabilityHelper(array(
			'projectId'=>$availability->projectId
			, 'availabilityId'=>$availability->id
		));
		$result = $availabilityHelper->getAllExcludedDates(strtotime($start));
		$weekdayStart = (int)date('N', strtotime($start));
		$weekdayEnd = (int)date('N', strtotime($end));
		if(in_array($start, $result['exclusions']) || in_array($end, $result['exclusions'])){
			return false;
		}
		if(in_array($weekdayStart, $result['checkoutWeekdayList']) || in_array($weekdayEnd, $result['checkinWeekdayList'])){
			return false;
		}
		$s = strtotime($start);
		$e = strtotime($end);
		while ($s <= $e) {
			if(in_array(date(CALENDARISTA_DATEFORMAT, $s), $result['bookedAvailabilityList'])){
				return false;
			}
			$s = strtotime('+1 day', $s);
		}
		return true;
	}
	protected static function validPackage($availability, $start, $end){
		$availabilityHelper = new Calendarista_AvailabilityHelper(array(
			'projectId'=>$availability->projectId
			, 'availabilityId'=>$availability->id
		));
		$result = $availabilityHelper->getNextOccurrenceByPackage();
		if($result){
			$startDate = date(CALENDARISTA_DATEFORMAT, $result['startDate']);
			$endDate = date(CALENDARISTA_DATEFORMAT, $result['endDate']);
			if($start == $startDate && $end == $endDate){
				return true;
			}
		}
		return false;
	}
	public static function base64UrlEncode($value)
	{
		return strtr(base64_encode($value), '+/=', '-_,');
	}
	protected static function logError($booking, $e){
		$unsolvedError = boolval(get_option('calendarista_google_calendar_has_error'));
		if(!$unsolvedError){
			$errors = $e->getErrors();
			$message = null;
			if(is_array($errors) && count($errors) > 0){
				$message = $errors[0]['message'];
			}
			if(!$message){
				$message = $e->getMessage();
			}
			Calendarista_ErrorLogHelper::insert($message);
			$notification = new Calendarista_NotificationEmailer(array(
				'orderId'=>$booking->orderId
				, 'emailType'=>Calendarista_EmailType::GOOGLE_CALENDAR_AUTHENTICATION_FAILURE
			));
			$notification->send();
			update_option('calendarista_google_calendar_has_error', true);
		}
	}
}
?>