<?php

include_once 'Horde/SyncML/Sync.php';
include_once 'Horde/SyncML/Command/Sync/ContentSyncElement.php';

/**
 * $Horde: framework/SyncML/SyncML/Sync/TwoWaySync.php,v 1.12 2004/07/26 09:24:38 jan Exp $
 *
 * Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Anthony Mills <amills@pyramid6.com>
 * @author  Karsten Fourmont <fourmont@gmx.de>
 *
 * @version $Revision$
 * @since   Horde 3.0
 * @package Horde_SyncML
 */
class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync {

    function endSync($currentCmdID, &$output)
    {
        global $registry;

        $state = &$_SESSION['SyncML.state'];

        $syncType = $this->_targetLocURI;
        $hordeType = str_replace('./','',$syncType);

        $refts = $state->getServerAnchorLast($syncType);
        $currentCmdID = $this->handleSync($currentCmdID,
                                          $hordeType,
                                          $syncType,
                                          $output,
                                          $refts);
        if ($syncType == 'calendar' && $state->handleTasksInCalendar()) {
            Horde::logMessage("SyncML: handling tasks in calendar sync", __FILE__, __LINE__, PEAR_LOG_DEBUG);

            $currentCmdID = $this->handleSync($currentCmdID, 'tasks', $syncType,
                                              $output, $refts);
        }
        
        return $currentCmdID;
    }

    function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts)
    {
        global $registry;
        
        // array of Items which got modified, but got never send to the client before
        $missedAdds = array();
        
        #require_once 'Horde/History.php';
        #$history = &Horde_History::singleton();
        $history = $GLOBALS['phpgw']->contenthistory;
        $state = &$_SESSION['SyncML.state'];
	$counter = 0;
        
        $changes = &$state->getChangedItems($hordeType);
        $deletes = &$state->getDeletedItems($hordeType);
        $adds = &$state->getAddedItems($hordeType);
        
        Horde::logMessage("SyncML: ".count($changes).' changed items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
        Horde::logMessage("SyncML: ".count($deletes).' deleted items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
        Horde::logMessage("SyncML: ".count($adds).   ' added items found for '.$hordeType  , __FILE__, __LINE__, PEAR_LOG_DEBUG);

        while($guid = array_shift($changes))
        {
            $guid_ts = $history->getTSforAction($guid, 'modify');
            $sync_ts = $state->getChangeTS($syncType, $guid);
            Horde::logMessage("SyncML: timestamp modify guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
            if ($sync_ts && $sync_ts == $guid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                Horde::logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }
            Horde::logMessage("SyncML: change $guid hs_ts:$guid_ts dt_ts:" . $state->getChangeTS($syncType, $guid), __FILE__, __LINE__, PEAR_LOG_DEBUG);
            $locid = $state->getLocID($syncType, $guid);
            if (!$locid) {
            	// somehow we missed to add, lets store the uid, so we add this entry later
            	$missedAdds[] = $guid;
                Horde::logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING);
                continue;
            }

            // Create a replace request for client.
# LK            $contentType = $state->getPreferedContentTypeClient($syncType);
            $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI);
            $c = $registry->call($hordeType. '/export',
                                 array('guid' => $guid, 'contentType' => $contentType));
            if (!is_a($c, 'PEAR_Error')) {
                // Item in history but not in database. Strange, but
                // can happen.
                Horde::logMessage("SyncML: change: $guid export content: $c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
# LK		$cmd->setContent($state->convertServer2Client($c, $contentType));
                $cmd->setContent($c);
                $cmd->setSourceURI($guid);
                $cmd->setTargetURI($locid);
                $cmd->setContentType($contentType['ContentType']);
                $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace');
                $state->log('Server-Replace');
                
                // return if we have to much data
                if(++$counter >= MAX_ENTRIES)
                {
                	$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
                	return $currentCmdID;
                }
            }
        }
	Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);

	// deletes
        while($guid = array_shift($deletes))
        {
            $guid_ts = $history->getTSforAction($guid, 'delete');
            $sync_ts = $state->getChangeTS($syncType, $guid);
            Horde::logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
            if ($sync_ts && $sync_ts == $guid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                Horde::logMessage("SyncML: delete $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }
            $locid = $state->getLocID($syncType, $guid);
            if (!$locid) {
                Horde::logMessage("SyncML: unable to create delete for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING);
                continue;
            }

            Horde::logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
            // Create a Delete request for client.
            $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
            $cmd->setTargetURI($locid);
            $cmd->setSourceURI($guid);
            $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete');
            $state->log('Server-Delete');
            $state->removeUID($syncType, $locid);

	    // return if we have to much data
	    if(++$counter >= MAX_ENTRIES)
	    {
               	$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
	    	return $currentCmdID;
	    }
        }
	Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);

        // Get adds.
        if(count($missedAdds) > 0)
        {
        	Horde::logMessage("SyncML: add missed changes as adds ".count($adds).' / '.$missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
        	$state->setAddedItems($hordeType, array_merge($adds, $missedAdds));
        	$adds = &$state->getAddedItems($hordeType);
        	Horde::logMessage("SyncML: merged adds counter ".count($adds).' / '.$adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
        }
	while($guid = array_shift($adds))
	{
            #if($tempCounter > 10) continue;
            $guid_ts = $history->getTSforAction($guid, 'add');
            $sync_ts = $state->getChangeTS($syncType, $guid);
            Horde::logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
            if ($sync_ts && $sync_ts == $guid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                Horde::logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }

            $locid = $state->getLocID($syncType, $guid);

            if ($locid && $refts == 0) {
                // For slow sync (ts=0): do not add data for which we
                // have a locid again.  This is a heuristic to avoid
                // duplication of entries.
                Horde::logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }
            Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);

            // Create an Add request for client.
# LK            $contentType = $state->getPreferedContentTypeClient($syncType);
            $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI);

            $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
            $c = $registry->call($hordeType . '/export',
                                 array('guid' => $guid,
                                       'contentType' => $contentType));
            if (!is_a($c, 'PEAR_Error')) {
                // Item in history but not in database. Strange, but
                // can happen.
#LK		$cmd->setContent($state->convertServer2Client($c, $contentType));
                $cmd->setContent($c);
                $cmd->setContentType($contentType['ContentType']);
                $cmd->setSourceURI($guid);
                $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
                $state->log('Server-Add');

                // return if we have to much data
                if(++$counter >= MAX_ENTRIES)
                {
	               	$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
                	return $currentCmdID;
                }
            }
        }
	Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
	
       	$state->clearSync($syncType);

        return $currentCmdID;
    }
    
    function loadData()
    {
        global $registry;

	$state = &$_SESSION['SyncML.state'];
        $syncType = $this->_targetLocURI;
        $hordeType = str_replace('./','',$syncType);
        $refts = $state->getServerAnchorLast($syncType);
	
	Horde::logMessage("SyncML: reading changed items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG);
	$state->setChangedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts)));
	
	Horde::logMessage("SyncML: reading deleted items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG);
	$state->setDeletedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts)));
	
	Horde::logMessage("SyncML: reading added items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG);
	$state->setAddedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts)));
	
	$this->_syncDataLoaded = TRUE;

	return count($state->getChangedItems($hordeType)) +
		count($state->getDeletedItems($hordeType)) +
		count($state->getAddedItems($hordeType));
    }

}