<?php

/**
 * TODO List
 * save before quit / read
 * Signature automatique (ajouter dans settings)
 * connexion : diary cover + cadenas
 * writing pad : open book (2 pages) ou plusieurs feuilles superposées
 * separate textarea / text layout
 * all hovering / active images on the same one + positioning
 * thougt layout ! replace " with image quote
 */

class PhpObject
{
	private $asMessageStack;
	private $iExtractMode;

	const ERROR_TAB = 'error';
	const WARNING_TAB = 'warning'; 
	const MODE_ARRAY = 0;
	const MODE_TEXT = 1;
	const MODE_FILE = 2;
	
	function __construct()
	{
		$this->asMessageStack = array();
		$this->asMessageStack[self::ERROR_TAB] = array();
		$this->asMessageStack[self::WARNING_TAB] = array();
		$this->iExtractMode = self::MODE_ARRAY;
	}
	
	protected function addError($sError)
	{
		$this->addMessage(self::ERROR_TAB, $sError);
	}
	
	protected function addWarning($sWarning)
	{
		$this->addMessage(self::WARNING_TAB, $sError);
	}
	
	private function addMessage($sType, $sMessage)
	{
		$this->asMessageStack[$sType][] = $sMessage;
	}
	
	protected function getCleanErrorStack()
	{
		return $this->getCleanMessageStack(self::ERROR_TAB);
	}
	
	protected function getCleanWarningStack()
	{
		return $this->getCleanMessageStack(self::WARNING_TAB);
	}
	
	private function getCleanMessageStack($sType)
	{
		switch($this->iExtractMode)
		{
			case self::MODE_TEXT:
				$oMessageStack = implode("\n", $this->asMessageStack[$sType]);
				break;
			case self::MODE_ARRAY:
				$oMessageStack = $this->asMessageStack[$sType];
				break;
			case self::MODE_FILE:
				$oMessageStack = implode("</p><p>", $this->asMessageStack[$sType]);
				break;
		}
		$this->asMessageStack[$sType] = array();
		return $oMessageStack;
	}
	
	function __destruct()
	{
		$sErrorStack = $this->getCleanErrorStack();
		
		switch($this->iExtractMode)
		{
			case self::MODE_TEXT:
				echo $sErrorStack;
				break;
			case self::MODE_ARRAY:
				if(count($sErrorStack)>0)
				{
					pre($sErrorStack, 'Error Stack');
				}
				break;
			case self::MODE_FILE:
				if($sErrorStack!='')
				{
					@file_put_contents('log.html', '<p style="font-weight:bold;">'.date('r')."</p><p>".$sErrorStack.'</p>', FILE_APPEND);
				}
				break;
		}
	}
}

class Session extends PhpObject
{
	private $iUserId;
	private $sLogin;
	private $sToken;
	private $sPostToken;
	
	private $oMySql;
	
	const SESSION_ID_USER = 'id_user';
	const SESSION_USER = 'user';
	const SESSION_TOKEN = 'token';
	const SESSION_POST_TOKEN = 'post_token';
	
	public function __construct($oMySql)
	{
		parent::__construct();
		$this->iUserId = $this->sLogin = $this->sToken = $this->sPostToken = false;
		$this->oMySql = $oMySql;
		$this->syncSession();
	}
	
	public function getUserId()
	{
		return $this->iUserId;
	}
	
	private function syncSession()
	{
		if(isset($_SESSION[self::SESSION_ID_USER]))
		{
			$this->iUserId = $_SESSION[self::SESSION_ID_USER];
		}
		if(isset($_SESSION[self::SESSION_USER]))
		{
			$this->sLogin = $_SESSION[self::SESSION_USER];
		}
		if(isset($_SESSION[self::SESSION_TOKEN]))
		{
			$this->sToken = $_SESSION[self::SESSION_TOKEN];
		}
		if(isset($_SESSION[self::SESSION_POST_TOKEN]))
		{
			$this->sPostToken = $_SESSION[self::SESSION_POST_TOKEN];
		}
	}
	
	private function setSession($iUserId, $sLogin)
	{
		$_SESSION[self::SESSION_ID_USER] = $iUserId;
		$_SESSION[self::SESSION_USER] = $sLogin;
		
		//Token
		$sToken = $this->createToken();
		$_SESSION[self::SESSION_TOKEN] = $sToken;
		$this->setTokenCookie($sToken);

		$this->syncSession();
	}
	
	public function register($asData)
	{
		$sLogin = strtolower($asData['user']);
		$sPass = $asData['pass'];
		
		if($sLogin=='' || $sPass=='')
		{
			$this->addError('Empty mandatory fields (Nickname or password)');
		}
		elseif(htmlspecialchars($sLogin, ENT_QUOTES)!=$sLogin)
		{
			$this->addError('Nickname: HTML characters are forbidden');
		}
		elseif($this->oMySql->selectRow(MySqlManager::USERS_TABLE, array('user'=>$sLogin)))
		{
			$this->addError('Nickname: This is already a user called by that name, choose a different one');
		}
		else
		{
			$asData['pass'] = self::encryptPassword($sPass);
			$iUserId = $this->oMySql->insertRow(MySqlManager::USERS_TABLE, $asData);
			return $this->logMeIn($sLogin, $sPass);
		}
		return false;
	}
	
	public function logMeIn($sLogin, $sPass)
	{
		$bResult = false;
		$asUser = $this->oMySql->selectRow(MySqlManager::USERS_TABLE, array('user'=>$sLogin));
		if(!$asUser)
		{
			$this->addError('Utilisateur inconnu');
		}
		elseif(!$this->checkPassword($sPass, $asUser['pass']))
		{
			$this->addError('mot de pass incorrect');
		}
		else
		{
			$this->setSession($asUser[MySqlManager::getId(MySqlManager::USERS_TABLE)], $sLogin);
			$bResult = true;
		}
		return $bResult;
	}
	
	public function isLogguedIn()
	{
		$bLogguedIn = false;
		if($this->iUserId && $this->sLogin && $this->sToken)
		{
			//check if token is set and valid
			if($this->checkToken())
			{
				//Check if user got a actual account in the database
				$bLogguedIn = $this->checkAccount($this->sLogin, $this->iUserId);
			}
			else
			{
				$this->addError('Authentication problem, please sign in again');
			}
		}
		return $bLogguedIn;
	}
	
	private function checkAccount($sUserName, $iUserId=0)
	{
		$asConstraints = array('user'=>$sUserName);
		if($iUserId>0)
		{
			$asConstraints[MySqlManager::getId(MySqlManager::USERS_TABLE)] = $iUserId;
		}
		return $this->oMySql->selectValue(MySqlManager::USERS_TABLE, 'COUNT(1)', $asConstraints);
	}
	
	public function logMeOut()
	{
		$_SESSION = array();
		$this->setTokenCookie('', -1);
		return session_destroy();
	}
	
	private static function encryptPassword($sPass)
	{
		$sRandomText = 'F_RA-1H"2{bvj)5f?0sd3r#fP,K]U|w}hGiN@(sZ.sDe!7*x/:Mq+&';
		for($iIndex=0; $iIndex < strlen($sPass); $iIndex++)
		{
			$sPass[$iIndex] = $sRandomText[$iIndex%strlen($sRandomText)] ^ $sPass[$iIndex];
		}
		return md5($sPass);
	}
	
	private static function createToken()
	{
		return self::encryptPassword(	$_SERVER['HTTP_USER_AGENT'].
										$_SERVER['REMOTE_ADDR'].
										$_SERVER['REQUEST_TIME'].
										strstr(microtime(), ' ', true).
										$_SERVER['SERVER_SIGNATURE'].
										$_SERVER['SERVER_ADMIN']);
	}
	
	//Session Token
	
	private static function setTokenCookie($sToken, $iTime=1)
	{
		setcookie(self::SESSION_TOKEN, $sToken, time()+60*60*24*$iTime);
		$_COOKIE[self::SESSION_TOKEN] = $sToken;
	}
	
	private function checkToken()
	{
		return ($this->sToken && array_key_exists(self::SESSION_TOKEN, $_COOKIE) && $_COOKIE[self::SESSION_TOKEN] == $this->sToken);
	}
	
	//Post Token
	
	private function refreshPostToken()
	{
		$this->sPostToken = self::createToken();
		$_SESSION[self::SESSION_POST_TOKEN] = $this->sPostToken;
	}
	
	public function getNewPostToken()
	{
		$this->refreshPostToken();
		return $this->sPostToken;
	}
	
	public function checkPostToken($sPostToken)
	{
		return ($this->sPostToken && $sPostToken!='' && $sPostToken == $this->sPostToken); 
	}
	
	private static function checkPassword($sClearPass, $sEncodedPass)
	{
		return self::encryptPassword($sClearPass) == $sEncodedPass;
	}
}

class Mask extends PhpObject
{
	public $sMaskName;
	public $sFilePath;
	private $sMask;
	private $asTags;
	private $asPartsSource;
	private $aoInstances;
	
	const MASK_FOLDER = 'mask/';
	const START_TAG = 'START';
	const END_TAG = 'END';
	const TAG_MARK = '#';
	
	public function __construct($sFileName='')
	{
		//init
		parent::__construct();
		$this->sMaskName = '';
		$this->sFilePath = '';
		$this->sMask = '';
		$this->asTags = array();
		$this->asPartsSource = array();
		$this->aoInstances = array();
		$this->sFilePath = '';
		
		//load file
		if($sFileName!='')
		{
			$this->initFile($sFileName);
		}
	}
	
	public function initFile($sFileName)
	{
		$sFilePath = self::MASK_FOLDER.$sFileName.'.html';
		if(file_exists($sFilePath))
		{
			$this->sFilePath = $sFilePath;
			$sSource = file_get_contents($this->sFilePath);
			$this->initMask($sFileName, $sSource);
		}
		else
		{
			$this->addError('Fichier introuvable &agrave; l\'adresse : '.$sFilePath);
		}
	}
	
	public function initFileFromString($sSource, $sPartName='', $iInstanceNb=0)
	{
		$this->initMask($sPartName.' (from row) '.$iInstanceNb, $sSource);
	}
	
	private function initMask($sMaskName, $sSource)
	{
		$this->sMaskName = $sMaskName;
		$this->sMask = $sSource;
		$this->setParts();
	}
	
	private function setParts()
	{
		while(preg_match('/\<\!-- \[PART\] (?P<part>\S+) \[START\] --\>/', $this->sMask, $asMatch))
		{
			$sPartName = $asMatch['part'];

			$this->asPartsSource[$sPartName] = $this->getCleanPart($sPartName);
			$this->aoInstances[$sPartName] = array();
		}
	}
	
	private function getCleanPart($sPartName)
	{
		$iStartPos = $this->getPartStartPos($sPartName);
		$iEndPos = $this->getPartEndPos($sPartName);
		$sPart = substr($this->sMask, $iStartPos, $iEndPos-$iStartPos);
		$sExtendedPart = $this->getPartPattern($sPartName, self::START_TAG).$sPart. $this->getPartPattern($sPartName, self::END_TAG);
		$this->sMask = str_replace($sExtendedPart, $this->getPartTagPattern($sPartName), $this->sMask);
		return $sPart;
	}
	
	private function getPartStartPos($sPartName)
	{
		$sPartStartPattern = $this->getPartPattern($sPartName, self::START_TAG);
		return strpos($this->sMask, $sPartStartPattern) + strlen($sPartStartPattern);
	}
	
	private function getPartEndPos($sPartName)
	{
		$sPartEndPattern = $this->getPartPattern($sPartName, self::END_TAG);
		return strpos($this->sMask, $sPartEndPattern);
	}
	
	private function getPartPattern($sPartName, $sAction)
	{
		return '<!-- [PART] '.$sPartName.' ['.$sAction.'] -->';
	}
	
	private function getPartTagPattern($sPartName, $bMark=true)
	{
		$sPartTag = 'PART '.$sPartName;
		return $bMark?$this->addTagMark($sPartTag):$sPartTag;
	}
	
	public function addInstance($sPartName, $asTags)
	{
		$this->newInstance($sPartName);
		foreach($asTags as $sTagName=>$sTagValue)
		{
			$this->setInstanceTag($sPartName, $sTagName, $sTagValue);
		}
	}
	
	public function newInstance($sPartName)
	{
		//Finding the part
		$oMask = &$this->findPart($this, $sPartName);
		
		//Retrieving source html
		$sPartSource = $oMask->asPartsSource[$sPartName];
		
		//Creating new instance
		$oInstance = new Mask();
		$oInstance->initFileFromString($sPartSource, $sPartName);
		$oMask->aoInstances[$sPartName][] = $oInstance;
	}
	
	public function setInstanceTag($sPartName, $sTagName, $sTagValue)
	{
		$oMask = &$this->findPart($this, $sPartName);
		$oMask->getCurrentInstance($sPartName)->setTag($sTagName, $sTagValue);
	}
	
	private function &findPart($oMask, $sPartName)
	{
		if(array_key_exists($sPartName, $oMask->aoInstances))
		{
			return $oMask;
		}
		else //not tested
		{
			foreach($oMask->aoInstances as $sLevelPartName=>$aoInstances)
			{
				if(!empty($aoInstances))
				{
					//take last instances
					return $this->findPart($oMask->getCurrentInstance($sLevelPartName), $sPartName);
				}
			}
		}
		$this->addError('No part found : '.$sPartName);
	}
	
	private function getCurrentInstance($sPartName)
	{
		if(!empty($this->aoInstances[$sPartName]))
		{
			return end($this->aoInstances[$sPartName]);
		}
		else
		{
			return false;
		}
	}
	
	public function setTag($sTagName, $sTagValue)
	{
		$this->asTags[$sTagName] = $sTagValue;
	}
	
	public function getMask()
	{
		$sCompletedMask = $this->sMask;
		
		//build parts
		foreach($this->aoInstances as $sPart=>$aoParts)
		{
			$sTagValue = '';
			foreach($aoParts as $oInstance)
			{
				$sTagValue .= $oInstance->getMask();
			}
			$this->setTag($this->getPartTagPattern($sPart, false), $sTagValue);
		}
		
		//replace tags
		if(!empty($this->asTags))
		{
			$asTags = $this->addTagMark(array_keys($this->asTags));
			$sCompletedMask = str_replace($asTags, $this->asTags, $sCompletedMask);
		}
		return $sCompletedMask;
	}
	
	private function addTagMark($oData)
	{
		return array_map_encapsulate($oData, self::TAG_MARK);
	}
}

class MyThoughts extends PhpObject
{	
	//Constants
	const URL_DATE_FORMAT = 'Ymd';
	const LAYOUT_DATE_FORMAT = 'F \t\h\e jS, Y';
	const MYSQL_DATE_FORMAT = 'Y-m-d';
	const LAYOUT_TIME_FORMAT = 'G:i';
	const WELCOME_MSG_FILE = 'welcome';
	
	//settings
	const SETTING_LAYOUT = 'layout';
	const LAYOUT_ONE_PAGE = '1';
	const LAYOUT_TWO_PAGES = '2';
	const SETTING_FONT = 'font';
	const FONT_THOUGHTS = 'thoughts';
	const FONT_ARIAL = 'Arial';
	const FONT_VERDANA = 'Verdana';
	const SETTING_SIZE = 'Size';
	const SIZE_16 = '16';
	const SIZE_18 = '18';
	const SIZE_20 = '20';
	
	//Objects
	private $oMySql;
	private $oSession;
	private $oCalendar;
	private $asSettings;
	
	//Masks
	private $oMainMask;
	private $oPageMask;
	private $oMenuMask;
	
	function __construct()
	{
		parent::__construct();
		$this->oMySql = new MySqlManager();
		$this->oSession = new Session($this->oMySql);
		$this->oCalendar = new Calendar($this->oMySql, $this->oSession);
		$this->oMainMask = new Mask('index');
		$this->oPageMask = new Mask();
		$this->oMenuMask = new Mask();
		$this->asSettings = array();
	}
	
	

	
	public function logMeOut()
	{
		$this->oSession->logMeOut();
		self::relocate();
	}
	
	public function isLogguedIn()
	{
		return $this->oSession->isLogguedIn();
	}
	
	public function checkPostToken($sPostToken)
	{
		return $this->oSession->checkPostToken($sPostToken);
	}
	
	public function setPage($sPage)
	{
		$this->oPageMask->initFile($sPage);
	}
	
	public function setPageTitle($sTitle)
	{
		$this->oMainMask->setTag('title', $sTitle);
	}
	
	public function setCalendarDate($iYear=0, $iMonth=0)
	{
		$this->oCalendar->setDate($iYear, $iMonth);
	}
	
	private static function relocate($sPage='', $asVar=array())
	{
		$asVar['p'] = $sPage;
		header('Location:index.php?'.implodeAll($asVar, '=', '&'));
		die();
	}
	

	
	public function updateThought($iThoughtId, $sThought)
	{
		$asValues = array('thought'=>$this->encodeThought($sThought));
		$asConstraints = array(	MySqlManager::getId(MySqlManager::THOUGHTS_TABLE)=>$iThoughtId, 
								MySqlManager::getId(MySqlManager::USERS_TABLE)=>$this->oSession->getUserId());
		$this->oMySql->updateRow(MySqlManager::THOUGHTS_TABLE, $asConstraints, $asValues);
	}
	

	
	private function shuffleText($sText)
	{
		$sRandomText = "let's_mess%a&bit;with~it,!just§for¨the^sake*of-it";
		for($iIndex=0; $iIndex < strlen($sText); $iIndex++)
		{
			$sText[$iIndex] = $sRandomText[$iIndex%strlen($sRandomText)] ^ $sText[$iIndex];
		}
		return $sText;
	}
	
	public function activateMenu()
	{
		$this->oMenuMask->initFile('menu');
	}
	
	//settings	
	
	public static function getSettingsList()
	{
		//TODO Save on database (param table)
		return array(self::SETTING_FONT, self::SETTING_SIZE, self::SETTING_LAYOUT);
	}
	
	private static function getDefaultSetting($sSettingName)
	{
		switch($sSettingName)
		{
			case self::SETTING_FONT:
				return self::FONT_THOUGHTS;
			case self::SETTING_LAYOUT:
				return self::LAYOUT_ONE_PAGE;
		}
		return false;
	}
	
	private function getSetting($sSettingName)
	{
		if(!array_key_exists($sSettingName, $this->asSettings))
		{
			$asConstraint = array(MySqlManager::getText(MySqlManager::SETTINGS_TABLE)=>$sSettingName, MySqlManager::getId(MySqlManager::USERS_TABLE)=>$this->oSession->getUserId());
			$oValue = $this->oMySql->selectValue(MySqlManager::SETTINGS_TABLE, 'value', $asConstraint);
			$this->asSettings[$sSettingName] = (!$oValue)?self::getDefaultSetting($sSettingName):$oValue;
		}
		return $this->asSettings[$sSettingName];
	}
	
	private function setSetting($sValue, $sSettingName)
	{
		$this->oMySql->insertUpdateRow(MySqlManager::SETTINGS_TABLE, array('setting'=>$sSettingName, MySqlManager::getId(MySqlManager::USERS_TABLE)=>$this->oSession->getUserId()), array('value'=>$sValue));
	}
	
	public function setSettings($asSettings)
	{
		array_walk($asSettings, array($this, 'setSetting'));
	}
	
	/* Pages */
	
	public function logonPage($sPreviousLogin)
	{
		$this->setPageTitle('Login');
		$this->setPage('logon');
		$sPreviousLogin = ($sPreviousLogin=='')?'':$sPreviousLogin;
		$this->oPageMask->setTag('login', $sPreviousLogin);
	}
	
	public function writingPage($iThoughtId=0)
	{
		$sThought = '';
		$iThoughtTime = '';
		if($iThoughtId!=0)
		{
			//load a thought
			$asConstraints = array(	MySqlManager::getId(MySqlManager::THOUGHTS_TABLE)=>$iThoughtId, 
									MySqlManager::getId(MySqlManager::USERS_TABLE)=>$this->oSession->getUserId());
			$asThought = $this->oMySql->selectRow(MySqlManager::THOUGHTS_TABLE, $asConstraints);
			$sThought = $this->decodeThought($asThought['thought']);
			$iThoughtTime = 'Saved at '.date(self::LAYOUT_TIME_FORMAT, strtotime($asThought['led']));
		}

		$this->setPage('write_thought');
		$this->oPageMask->setTag('font', $this->getSetting(self::SETTING_FONT));
		$this->oPageMask->setTag('size', $this->getSetting(self::SETTING_SIZE));
		$this->oPageMask->setTag('thought', $sThought);
		$this->oPageMask->setTag('thought_id', $iThoughtId);
		$this->oPageMask->setTag('last_saved', $iThoughtTime);
		$this->setPageTitle('Talk to me');
	}
	
	public function readingPage($iTimeStamp=0)
	{
		if($iTimeStamp==0)
		{
			$iTimeStamp = strtotime('now');
		}
		$sMySqlDate = date(self::MYSQL_DATE_FORMAT, $iTimeStamp);
		$sLayoutDate = date(self::LAYOUT_DATE_FORMAT, $iTimeStamp);
		
		$asConstraints = array('DATE(led)'=>$sMySqlDate, MySqlManager::getId(MySqlManager::USERS_TABLE)=>$this->oSession->getUserId());
		$asThougths = $this->oMySql->selectRows(array('from'=>MySqlManager::THOUGHTS_TABLE, 'constraint'=>$asConstraints));
		
		$this->setPage('read_thought');
		$this->setPageTitle('Thoughts on '.$sLayoutDate);
		$this->oPageMask->setTag('date', $sLayoutDate);
		
		foreach($asThougths as $asThought)
		{	
			$asThoughtParagraphs = explode("\n", $this->decodeThought($asThought['thought']));
			$this->oPageMask->newInstance('THOUGHT');
			$this->oPageMask->setInstanceTag('THOUGHT', 'time', date(self::LAYOUT_TIME_FORMAT, strtotime($asThought['led'])));
			foreach($asThoughtParagraphs as $sParagraph)
			{
				$asParagraphTags = array('thought_paragraph'=>$sParagraph);
				$this->oPageMask->addInstance('THOUGHT_PARA', $asParagraphTags);
			}
		}
		
		//calendar update
		$this->setCalendarDate(date('Y', $iTimeStamp), date('m', $iTimeStamp));
	
		return (count($asThougths)>0);
	}
	
	public function settingsPage()
	{
		$this->setPage('settings');
		$this->setPageTitle('Settings');
		$asSettingsOptions = array(	self::SETTING_LAYOUT => array(
										'One extensible page' => self::LAYOUT_ONE_PAGE,
										'Two Pages, Diary like' => self::LAYOUT_TWO_PAGES),
									self::SETTING_FONT => array(
										'AES Crawl' => self::FONT_THOUGHTS,
										'Arial' => self::FONT_ARIAL,
										'Verdana' => self::FONT_VERDANA),
									self::SETTING_SIZE => array(
										'16pt' => self::SIZE_16,
										'18pt' => self::SIZE_18,
										'20pt' => self::SIZE_20));
		
		foreach(self::getSettingsList() as $sSettingName)
		{
			$this->oPageMask->newInstance('SETTING');
			$this->oPageMask->setInstanceTag('SETTING', 'setting_name', $sSettingName);
			$sUserSetting = $this->getSetting($sSettingName);
			foreach($asSettingsOptions[$sSettingName] as $sOptionName=>$sOptionValue)
			{
				if($sOptionValue == self::getDefaultSetting($sSettingName))
				{
					$sOptionName .= ' (Default)';
				}
				$sSelectedOption = ($sUserSetting==$sOptionValue)?'selected':'';
				$asSettingOptions = array(	'setting_option_value'=>$sOptionValue, 
											'setting_option_selected'=>$sSelectedOption, 
											'setting_option_name'=>$sOptionName);
				$this->oPageMask->addInstance('SETTING_OPTION', $asSettingOptions);
			}
		}
	}
	
	/* Final processes */
	
	private function collectErrors()
	{	
		$asErrors = array_merge($this->getCleanErrorStack(), 
								$this->oMySql->getCleanErrorStack(), 
								$this->oSession->getCleanErrorStack(),
								$this->oCalendar->getCleanErrorStack(),
								$this->oMainMask->getCleanErrorStack(),
								$this->oPageMask->getCleanErrorStack(),
								$this->oMenuMask->getCleanErrorStack());

		//$asErrors = array_map('getCleanErrorStack', array($this, $this->oMySql, $this->oSession, $this->oCalendar, $this->oMainMask, $this->oPageMask, $this->oMenuMask));
		//pre($asErrors, 'static error stock', true);
		
		$oErrorMask = new Mask();
		if(!empty($asErrors))
		{
			$oErrorMask->initFile('errors');
			foreach($asErrors as $sError)
			{
				$oErrorMask->addInstance('ERROR', array('error'=>$sError));
			}
		}
		return $oErrorMask->getMask();
	}
	
}

class Calendar extends PhpObject
{
	const CAL_YEAR = 'cy';
	const CAL_MONTH = 'cm';
	
	private $oMySql;
	private $oSession;
	private $oMask;
	
	private $iUserId;
	private $iYear;
	private $iMonth;
	
	function __construct($oMySql, $oSession)
	{
		parent::__construct();
		$this->oMySql = $oMySql;
		$this->oSession = $oSession;
		$this->oMask = new Mask('calendar');
		$this->iYear = 0;
		$this->iMonth = 0;
	}
	
	public function setDate($iYear=0, $iMonth=0)
	{
		if($iYear==0)
		{
			$iYear = date('Y');
		}
		if($iMonth==0)
		{
			$iMonth = date('m');
		}
		$this->iYear = $iYear;
		$this->iMonth = $iMonth;
	}
	
	private function getThoughts()
	{
		//TODO essayer avec selectRows
		$sQuery = "SELECT DATE_FORMAT(led, '%d') AS day
					FROM ".MySqlManager::THOUGHTS_TABLE."
					WHERE ".MySqlManager::getId(MySqlManager::USERS_TABLE)." = ".$this->oSession->getUserId()."
					AND YEAR(led) = ".$this->iYear."
					AND MONTH(led) = ".$this->iMonth."
					GROUP BY day
					ORDER BY day";
		
		return $this->oMySql->getArrayQuery($sQuery, true);
	}
	
	private function getUpdatedLink($asParams)
	{
		$sCurrentVariables = $_SERVER['QUERY_STRING'];
		$asCurrentVariables = explode('&', $sCurrentVariables);
		foreach($asCurrentVariables as $sParam)
		{
			$sKey = strstr($sParam, '=', true);
			$sValue = substr(strstr($sParam, '='), 1);
			$asVariables[$sKey] = $sValue;
		}
		return '?'.implodeAll(array_merge($asVariables, $asParams), '=', '&');
	}
	
	private function getLink($iOffset)
	{
		$iTimeStamp = mktime(0, 0, 0, $this->iMonth + $iOffset, 1, $this->iYear);	
		return $this->getUpdatedLink(array(self::CAL_MONTH=>date('n', $iTimeStamp), self::CAL_YEAR=>date('Y', $iTimeStamp)));
	}
	
	private function setMaskItems()
	{
		//week starting on the sunday : offset = 0, monday : offset = 1
		$iOffset = 1;
		
		//days in the month	
		$iMonthLastDay = date('d', mktime(0, 0, 0, $this->iMonth+1, 0, $this->iYear));
		$asDays = range(1, $iMonthLastDay);
		
		$iDayNb = 1 - date($iOffset?'N':'w', mktime(0, 0, 0, $this->iMonth, 1, $this->iYear)) + $iOffset;
		$iCalendarLastDay = $iMonthLastDay + (7 - date($iOffset?'N':'w', mktime(0, 0, 0, $this->iMonth+1, 0, $this->iYear))) + $iOffset;
		
		//days with thoughts
		$asThoughts = $this->getThoughts();
		
		while($iDayNb < $iCalendarLastDay)
		{
			$iCurrentDayTimeStamp = mktime(0, 0, 0, $this->iMonth, $iDayNb, $this->iYear);
			$sItemDate = date('d', $iCurrentDayTimeStamp);
			
			//new week
			if(date('w', $iCurrentDayTimeStamp) == $iOffset)
			{
				$this->oMask->newInstance('WEEK');
			}
			
			//day within month
			if(date('n', $iCurrentDayTimeStamp)==$this->iMonth)
			{
				$bThoughts = in_array($iDayNb, $asThoughts);
				
				$sItemClass = $bThoughts?'full':'empty';
				$sItemLink = $bThoughts?$this->getUpdatedLink(array('d'=>date(MyThoughts::URL_DATE_FORMAT, $iCurrentDayTimeStamp), 'p'=>'r')):'#';
				$sItemLinkTitle = $bThoughts?'See my thoughts on '.date(MyThoughts::LAYOUT_DATE_FORMAT, $iCurrentDayTimeStamp):'';
			}
			else
			{
				$sItemClass = 'disabled';
				$sItemLink = '#';
				$sItemLinkTitle = '';
			}
			
			$this->oMask->addInstance('DAY', array('item_day'=>$sItemDate, 'item_class'=>$sItemClass, 'item_link'=>$sItemLink, 'item_link_title'=>$sItemLinkTitle));
			$iDayNb++;
		}
		
		//column titles
		$asDayNames = array('1'=>'Mon', '2'=>'Tue', '3'=>'Wed', '4'=>'Thu', '5'=>'Fri', '6'=>'Sat', $iOffset?'7':'0'=>'Sun');
		ksort($asDayNames);
		foreach($asDayNames as $sDayName)
		{
			$this->oMask->addInstance('TITLE', array('day_name'=>$sDayName));
		}

	}
	
	public function getCalendar()
	{
		$sResult = '';
		if($this->iYear!=0 && $this->iMonth!=0)
		{
			$this->oMask->setTag('link_prev', $this->getLink(-1));
			$this->oMask->setTag('current_month', date('F', mktime(0, 0, 0, $this->iMonth, 1, $this->iYear)));
			$this->oMask->setTag('link_next', $this->getLink(1));
			$this->setMaskItems();
			$sResult = $this->oMask->getMask();
		}
		return $sResult;
	}
}

function arrayKeyFilter($asArray, $sCallBack)
{
	$asValidKeys = array_flip(array_filter(array_keys($asArray), $sCallBack));
	return array_intersect_key($asArray, $asValidKeys);
}

function array_map_encapsulate($oData, $sChar)
{
	if(is_array($oData))
	{
		$asChar = array_fill(1, count($oData), $sChar);
		return array_combine(array_keys($oData), array_map('array_map_encapsulate', $oData, $asChar));
	}
	else
	{
		return $sChar.$oData.$sChar;
	}
}

function implodeAll($asText, $sKeyValueSeparator='',  $sRowSeparator='', $sKeyPre='', $sValuePost=false)
{
	if($sValuePost===false)
	{
		$sValuePost = $sKeyPre;
	}
	$asCombinedText = array();
	foreach($asText as $sKey=>$sValue)
	{
		$asCombinedText[] = $sKeyPre.$sKey.$sKeyValueSeparator.$sValue.$sValuePost;
	}
	return implode($sRowSeparator, $asCombinedText);
}

function cleanPost(&$asData)
{
	//get rid of magic quotes
	if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
	{
	    cleanData($asData, 'stripslashes');
	}
}
function cleanData(&$oData, $sCleaningFunc)
{
	if(!is_array($oData))
	{
		$oData = call_user_func($sCleaningFunc, $oData);
	}
	elseif(!empty($oData))
	{
		$asKeys = array_map($sCleaningFunc, array_keys($oData));
		$asValues = array_map($sCleaningFunc, $oData);
		$oData = array_combine($asKeys, $asValues);
	}
}

//debug
function pre($sText, $sTitle='Test', $bDie=false, $bLog=false)
{
	if($bLog)
	{
		file_put_contents('log', ($sTitle!=''?$sTitle." :\n":'').print_r($sText, true)."\n\n");
	}
	echo '<fieldset class="rounded"><legend class="rounded">'.$sTitle.'</legend><pre>'.print_r($sText, true).'</pre></fieldset>';
	if($bDie)
	{
		die('[die() called by the test function '.__FUNCTION__.'()]');
	}
}
?>