<?php
/***********************************************
* File      :   statemachine.php
* Descr     :   This class handles state requests;
*               Each differential mechanism can
*               store its own state information,
*               which is stored through the
*               state machine. SyncKey's are
*               of the  form {UUID}N, in which
*               UUID is allocated during the
*               first sync, and N is incremented
*               for each request to 'getNewSyncKey'.
*               A sync state is simple an opaque
*               string value that can differ
*               for each backend used - normally
*               a list of items as the backend has
*               sent them to the PIM. The backend
*               can then use this backend
*               information to compute the increments
*               with current data.
*
*               Old sync states are not deleted
*               until a sync state is requested.
*               At that moment, the PIM is
*               apparently requesting an update
*               since sync key X, so any sync
*               states before X are already on
*               the PIM, and can therefore be
*               removed. This algorithm is
*               automatically enforced by the
*               StateMachine class.
*
* Original by Zarafa Deutschland GmbH released under GPLv2.
* Modifications by Atmail published under GPLv2
* Consult LICENSE file for details
************************************************/


class StateMachine {
    // Gets the sync state for a specified sync key. Requesting a sync key also implies
    // that previous sync states for this sync key series are no longer needed, and the
    // state machine will tidy up these files.
    
	function StateMachine($backend)	{
		$this->backend = $backend;
	}
	
	function getSyncState($synckey) 
	{
    
    	// No sync state for sync key '0'
        if($synckey == "0" || $synckey == "s0")
            return "";

        // Check if synckey is allowed
        if(!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $synckey, $matches)) {
            return false;
        }

        // Remember synckey GUID and ID
        $guid = $matches[1];
        $n = $matches[2];

        
		if( SERIALIZE_TO_DB )
		{

			//TODO: delete old <$n matches
			$Account = $this->backend->_username;
			$dbAdapter = Zend_Registry::get('dbAdapter');
			$dbAdapter->query("SET NAMES 'utf8'");
			$q = "SELECT Name from SerialConf where Account='$Account' and Name LIKE '{$guid}%'";
			$rows = $dbAdapter->fetchAll($q);
			foreach($rows as $row)	
			{
	            
	    		if(preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $row, $matches)) 
				{
	                if($matches[1] == $guid && $matches[2] < $n) 
					{
					
						$dbAdapter->query("DELETE FROM SerialConf WHERE Account = '$Account' AND Name = '" . $row['Name'] . "'");
	                
					}
					
	            }

			}
			
			
			$q = "SELECT Value FROM SerialConf WHERE `Account` = '$Account' && `Name` = '$synckey'";
			$rows = $dbAdapter->fetchAll($q);
            if(!array_key_exists(0, $rows)) $rows[0] = array();
			$row = $rows[0];
			if( count($rows) > 0 )
				return array_key_exists('Value', $row) ? $row['Value'] : false;
			else
				return false;
				
		}
		else
		{
			
			// Cleanup all older syncstates
	        $dir = opendir(BASE_PATH . STATE_DIR);
	        if(!$dir)
	            return false;

	        while($entry = readdir($dir)) {
	            if(preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $entry, $matches)) {
	                if($matches[1] == $guid && $matches[2] < $n) {
	                    unlink(BASE_PATH . STATE_DIR . "/$entry");
	                }
	            }
	        }

	        // Read current sync state
            $filename = BASE_PATH . STATE_DIR . "/$synckey";
	        if(file_exists($filename))
	            return file_get_contents(BASE_PATH . STATE_DIR . "/$synckey");
	        else return false;
			
		}
		
    }

    // Gets the new sync key for a specified sync key. You must save the new sync state
    // under this sync key when done sync'ing (by calling setSyncState);
    function getNewSyncKey($synckey) {
        if(!isset($synckey) || $synckey == "0") {
            return "{" . $this->uuid() . "}" . "1";
        } else {
            if(preg_match('/^s{0,1}\{([a-fA-F0-9-]+)\}([0-9]+)$/', $synckey, $matches)) {
                $n = $matches[2];
                $n++;
                return "{" . $matches[1] . "}" . $n;
            } else return false;
        }
    }

    // Writes the sync state to a new synckey
	function setSyncState($synckey, $syncstate) 
	{
		
		// Check if synckey is allowed
        if(!preg_match('/^s{0,1}\{[0-9A-Za-z-]+\}[0-9]+$/', $synckey)) {
            //debugLog('synckey doesnt match expected format \$synckey: ' . $synckey );
			return false;
        }

        if( SERIALIZE_TO_DB )
		{

			$Account = $this->backend->_username;
			$dbAdapter = Zend_Registry::get('dbAdapter');
			$dbAdapter->query("SET NAMES 'utf8'");
			$q = "SELECT id FROM SerialConf WHERE `Account` = '$Account' && `Name` = '$synckey'";

			$rows = $dbAdapter->fetchAll($q);
			if( count($rows) > 0 )
			{
			
				$data = array();
				$data['DateAdded'] = new Zend_Db_Expr("NOW()");
				$data['Value'] = $syncstate;
				$result = null;
				$result = $dbAdapter->update('SerialConf', $data, "Account = '$Account' AND Name = '$synckey'");
				
			}
			else
			{
				
				$data = array();
				$data['Account'] = $Account;
				$data['Name'] = $synckey;
				$data['DateAdded'] = new Zend_Db_Expr("NOW()");
				$data['Value'] = $syncstate;
				$result = null;
				$result = $dbAdapter->insert('SerialConf', $data);
				
			}

		}
		else
			file_put_contents(BASE_PATH . STATE_DIR . "/$synckey", $syncstate);
			
    }

    function uuid()
    {
        return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
                    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
                    mt_rand( 0, 0x0fff ) | 0x4000,
                    mt_rand( 0, 0x3fff ) | 0x8000,
                    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) );
    }
}