<?php

/* Timezones
 * - Feeds (table `feeds`):
 *   - last_update: timestamp in site time (see Settings::TIMEZONE)
 * - Spot Messages (table `messages`): 
 *   - unix_time: UNIX (int) in UTC 
 *   - site_time: timestamp in site time (see Settings::TIMEZONE)
 *   - iso_time: raw ISO 8601 in local timezone
 * - Medias (table `medias`):
 *   - posted_on: timestamp in site time (see Settings::TIMEZONE)
 *   - taken_on: timestamp in site time (see Settings::TIMEZONE)
 * - Posts (table `posts`):
 *	 - site_time: timestamp in site time (see Settings::TIMEZONE)
 */

class Spot extends Main
{
	//Database
	const POST_TABLE = 'posts';
	
	const FORMAT_TIME = 'd/m/Y à H:i';
	
	const FEED_CHUNK_SIZE = 15;
	
	/**
	 * Active Project
	 * @var Project
	 */
	private $oProject;
	
	/**
	 * Media Class
	 * @var Media
	 */
	private $oMedia;
	
	public function __construct($oClassManagement, $sProcessPage)
	{
		$asClasses = array(
			array('name'=>'feed', 'project'=>true),
			array('name'=>'project', 'project'=>true),
			array('name'=>'media', 'project'=>true),
			array('name'=>'converter', 'project'=>true)
		);
		parent::__construct($oClassManagement, $sProcessPage, $asClasses);
		
		$this->oProject = new Project($this->oDb);
		$this->oMedia = new Media($this->oDb, $this->oProject);
	}
	
	protected function install()
	{
		//Install DB
		$this->oDb->install();
	}
	
	protected function getSqlOptions()
	{
		return array
		(
			'tables' 			=>	array
			(
				Feed::MSG_TABLE		=> array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'unix_time', 'content', 'battery_state'),
				Feed::FEED_TABLE	=> array('ref_feed_id', Db::getId(Feed::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'),
				Feed::SPOT_TABLE	=> array('ref_spot_id', 'name', 'model'),
				Project::PROJ_TABLE	=> array('name', 'codename', 'active_from', 'active_to', 'geofile', 'timezone'),
				self::POST_TABLE	=> array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'site_time'),
				Media::MEDIA_TABLE	=> array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'rotate')
			),
			'types' 			=>	array
			(
				'ref_msg_id'	=> "INT",
				'type'			=> "VARCHAR(20)",
				'latitude'		=> "DECIMAL(7,5)",
				'longitude'		=> "DECIMAL(8,5)",
				'iso_time'		=> "VARCHAR(24)",
				'site_time'		=> "TIMESTAMP DEFAULT 0",	//DEFAULT 0 removes auto-set to current time
				'unix_time'		=> "INT",
				'content'		=> "LONGTEXT",
				'battery_state'	=> "VARCHAR(10)",
				'ref_spot_id'	=> "VARCHAR(10)",
				'name'			=> "VARCHAR(100)",
				'codename'		=> "VARCHAR(100)",
				'model'			=> "VARCHAR(20)",
				'ref_feed_id'	=> "VARCHAR(40)",
				'description'	=> "VARCHAR(100)",
				'status'		=> "VARCHAR(10)",
				'active_from'	=> "TIMESTAMP DEFAULT 0",
				'active_to'		=> "TIMESTAMP DEFAULT 0",
				'geofile'		=> "VARCHAR(50)",
				'timezone'		=> "VARCHAR(100)",
				'last_update'	=> "TIMESTAMP DEFAULT 0",
				'filename'		=> "VARCHAR(100) NOT NULL",
				'taken_on'		=> "TIMESTAMP DEFAULT 0",
				'posted_on'		=> "TIMESTAMP DEFAULT 0",
				'rotate'		=> "SMALLINT"
			),
			'constraints'		=>	array
			(
				Feed::MSG_TABLE		=> array("UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)", "INDEX(`ref_msg_id`)"),
				Feed::FEED_TABLE	=> array("UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)", "INDEX(`ref_feed_id`)"),
				Feed::SPOT_TABLE	=> array("UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)", "INDEX(`ref_spot_id`)"),
				Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)",
				Media::MEDIA_TABLE	=> "UNIQUE KEY `uni_file_name` (`filename`)"
			),
			'cascading_delete'	=>	array
			(
					Feed::SPOT_TABLE=>array(Feed::MSG_TABLE)
			)
		);
	}
	
	public function getMainPage($asGlobalVars = array(), $sMainPage = 'index', $asMainPageTags=array())
	{
		return parent::getMainPage(
			array(
				'vars' => array(
					'chunk_size'				=> self::FEED_CHUNK_SIZE,
					'default_project_codename'	=> $this->oProject->getProjectCodeName(),
					'projects'					=> $this->oProject->getProjects()
				),
				'consts' => array(
					'geo_server'				=> Settings::GEO_SERVER,
					'modes'						=> Project::MODES,
					'site_timezone'				=> Settings::TIMEZONE,
					'site_timezone_desc'		=> self::getTimeZoneDesc(Settings::TIMEZONE)
				)
			),
			'index',
			array(
				'host_url'					=> $this->asContext['serv_name'],
				'filepath_css'				=> self::addTimestampToFilePath('style/spot.css'),
				'filepath_js_d3'			=> self::addTimestampToFilePath('script/d3.min.js'),
				'filepath_js_leaflet'		=> self::addTimestampToFilePath('script/leaflet.min.js'),
				'filepath_js_jquery'		=> self::addTimestampToFilePath('script/jquery.min.js'),
				'filepath_js_jquery_mods'	=> self::addTimestampToFilePath('script/jquery.mods.js'),
				'filepath_js_spot'			=> self::addTimestampToFilePath('script/spot.js')
			)
		);
	}
	
	/* Managing projects */
	
	public function setProjectId($iProjectId=0) {
		$this->oProject->setProjectId($iProjectId);
		
		//Update all feeds belonging to the project
		$asFeeds = $this->oProject->getFeedIds();
		foreach($asFeeds as $iFeedId) {
			$oFeed = new Feed($this->oDb, $iFeedId);
			$oFeed->checkUpdateFeed($this->oProject->getMode());
		}
	}
	
	public function getMarkers()
	{
		$asMessages = $this->getSpotMessages();
		$bSuccess = !empty($asMessages);
		$sDesc = $bSuccess?'':self::NO_DATA;
		
		//Add medias
		if($bSuccess) {
			$asMedias = $this->getMedias('taken_on');
			
			//Assign medias to closest message
			$iIndex = 0;
			$iMaxIndex = count($asMessages) - 1;
			foreach($asMedias as $asMedia) {
				while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) {
					$iIndex++;
				}
				if($iIndex == 0) $iMsgIndex = $iIndex;
				elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
				else {
					$iHalfWayPoint = ($asMessages[$iIndex - 1]['unix_time'] + $asMessages[$iIndex]['unix_time'])/2;
					$iMsgIndex = ($asMedia['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
				}
				
				$asMessages[$iMsgIndex]['medias'][] = $asMedia;
			}
		}
		
		return self::getJsonResult($bSuccess, $sDesc, $asMessages);
	}
	
	private function getSpotMessages()
	{
		//Get messages from all feeds belonging to the project
		$asFeeds = $this->oProject->getFeedIds();
		foreach($asFeeds as $iFeedId) {
			$oFeed = new Feed($this->oDb, $iFeedId);
			$asMessages = $oFeed->getMessages($this->oProject->getActivePeriod());
			foreach($asMessages as $iIndex=>&$asMessage)
			{
				$asMessage['latitude'] = floatval($asMessage['latitude']);
				$asMessage['longitude'] = floatval($asMessage['longitude']);
				$asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat');
				$asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon');
				$asMessage['displayed_id'] = 'N°'.($iIndex + 1);
				
				$this->addTimeStamp($asMessage, $asMessage['unix_time']);
			}
		}
		usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
		
		return $asMessages;
	}
	
	/**
	 * Get valid medias based on $sTimeRefField (both are on site time):
	 * - taken_on: Date/time on which the media was taken
	 * - posted_on: Date/time on which the media was uploaded
	 * @param String $sTimeRefField Field to calculate relative times
	 * @return Array Medias info
	 */
	private function getMedias($sTimeRefField)
	{
		$asMedias = $this->oMedia->getMediasInfo();
		$asValidMedias = array();
		foreach($asMedias as $iIndex=>$asMedia) {
			$sTimeRef = $asMedia[$sTimeRefField];
			if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) {
				$asMedia['taken_on_formatted'] = date(self::FORMAT_TIME, strtotime($asMedia['taken_on']));
				$asMedia['displayed_id'] = 'N°'.($iIndex + 1);
				
				$this->addTimeStamp($asMedia, strtotime($sTimeRef));
				$asValidMedias[] = $asMedia;
			}
		}
		usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
		
		return $asValidMedias;
	}
	
	private function getPosts()
	{
		$asInfo = array(
			'from'		=> self::POST_TABLE,
			'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()),
			'constOpe'	=> array(Db::getId(Project::PROJ_TABLE) => "=")
		);
		if($this->oProject->getMode()==Project::MODE_HISTO) {
			$asInfo['constraint']['site_time'] = $this->oProject->getActivePeriod();
			$asInfo['constOpe']['site_time'] = "BETWEEN";
		}
		$asPosts = $this->oDb->selectRows($asInfo);

		foreach($asPosts as &$asPost) {
			$iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE) 
			$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
			
			$this->addTimeStamp($asPost, $iUnixTimeStamp);
		}
		usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
		
		return $asPosts;
	}
	
	private function addTimeStamp(&$asData, $iTime) {
		$asData['unix_time'] = (int) $iTime;
		$asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, 'fr');
		$asData['formatted_time'] = date(self::FORMAT_TIME, $iTime);
	}
	
	public function getNewsFeed($iChunk=0)
	{
		$bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO);
		$asFeeds = array();
		$asFeedTypes = array(
			'message'	=> array(
				'table'		=> Feed::MSG_TABLE,
				'feed'		=> $this->getSpotMessages(),
				'priority'	=> 0
			),
			'media'	=> array(
				'table'		=> Media::MEDIA_TABLE,
				'feed'		=> $this->getMedias('posted_on'),
				'priority'	=> 1
			),
			'post'		=> array(
				'table'		=> self::POST_TABLE,
				'feed'		=> $this->getPosts(),
				'priority'	=> 2
			)
		);
		foreach($asFeedTypes as $sFeedType=>$asFeedTypeInfo) {
			foreach($asFeedTypeInfo['feed'] as $asFeed) {
				$sPriority = $asFeedTypeInfo['priority'];
				if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority'];
				
				$iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])];
				$asFeeds[$iId] = $asFeed;
				$asFeeds[$iId]['type'] = $sFeedType;
			}
		}
		
		//Sort by key
		if($bHistoMode)	krsort($asFeeds);
		else ksort($asFeeds);
		
		//Split chunks
		$asFeeds = array_slice($asFeeds, $iChunk*self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); 
		
		return self::getJsonResult(true, '', $asFeeds);
	}
	
	public function syncMedias() {
		return $this->oMedia->syncFileFolder();
	}
	
	public function addPost($sName, $sPost)
	{
		$asData = array(
			Db::getId(Project::PROJ_TABLE)	=> $this->oProject->getProjectId(), 
			'name'							=> mb_strtolower(trim($sName)), 
			'content'						=> trim($sPost),
			'site_time'						=> date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE)
		);
		$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
		return self::getJsonResult(($iPostId > 0), '');
	}
	
	public function upload()
	{
		$this->oClassManagement->incClass('uploader', true);
		$oUploader = new Uploader($this->oMedia);
		
		return $oUploader->sBody;
	}
	
	public function getAdminSettings() {
		return self::getJsonResult(true, '', array('projects'=>$this->oProject->getProjects()));
	}
	
	public function setAdminSettings($sField, $sValue) {
		$bSuccess = false;
		$sDesc = '';
		
		switch($sField) {
			case 'codename':
				$bSuccess = $this->oProject->setProjectCodeName($sValue);
				break;
			case 'active_from':
				$bSuccess = $this->oProject->setActivePeriod($sValue.' 00:00:00', 'from');
				break;
			case 'active_to':
				$bSuccess = $this->oProject->setActivePeriod($sValue.' 23:59:59', 'to');
				break;
			case 'timezone':
				$bSuccess = $this->oProject->setTimeZone($sValue);
				break;
		}
		if(!$bSuccess) $sDesc = 'Issue committing to DB';
		
		return self::getJsonResult($bSuccess, $sDesc, array('values'=>$this->oProject->getProject()));
	}
	
	public static function decToDms($dValue, $sType) {
		if($sType=='lat')	$sDirection = ($dValue >= 0)?'N':'S'; //Latitude
		else				$sDirection = ($dValue >= 0)?'E':'W'; //Longitude
		
		$dLeft = abs($dValue);
		
		//Degrees
		$iDegree = floor($dLeft);
		$dLeft -= $iDegree;
		
		//Minutes
		$iMinute = floor($dLeft * 60);
		$dLeft -=  $iMinute / 60;
		
		//Seconds
		$fSecond = round($dLeft * 3600, 1);
		
		return $iDegree.'°'.$iMinute.'\''.$fSecond.'"'.$sDirection;
	}
	
	public static function getTimeZoneDesc($sTimeZone) {
		$sCity = substr(strrchr($sTimeZone, '/'), 1);
		return 'heure d'.(in_array(strtolower(substr($sCity, 0, 1)), array('a', 'e', 'i', 'o', 'u', 'h'))?'\'':'e ').$sCity;
	}
}

?>