package org.biomoby.registry.sync.handler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import org.biomoby.client.CentralDigestImpl;
import org.biomoby.registry.sync.CentralFactory;
import org.biomoby.registry.sync.logging.MobySecondaryLogging;
import org.biomoby.shared.LSIDAccessible;
import org.biomoby.shared.MobyException;

/**
 * Basic abstract implementation of the <code>MobyHandler</code> interface.<br>
 * The implementing classes are responsible for the synchronization of the centrals
 * 
 * @author groscurt
 * @param <
 *            T > one of the objects from Biomoby which are synchronized (e.g. MobyService, MobyDatatype etc). All of
 *            these classes have to implement the two interfaces <code>Comparable</code> and
 *            <code>LSIDAccessible</code>
 */
public abstract class AbstractMobyHandler< T extends Comparable< T > & LSIDAccessible > implements MobyHandler< T > {
    protected static Logger logger = Logger.getLogger( MobySecondaryLogging.LOGGER_NAME );

    protected CentralDigestImpl masterCentral;
    protected CentralDigestImpl localCentral;

    private String authority;

    public AbstractMobyHandler( String authority ) throws MobyException {
        masterCentral = CentralFactory.getMasterCentral();
        localCentral = CentralFactory.getLocalCentral();
        this.authority = authority;
    }

    /**
     * Checks whether the local object (represented by the lsid) is older than the central object (represented by the
     * lsid)
     * 
     * @param localLSID
     *            the lsid of the local object
     * @param centralLSID
     *            the lsid of the central object
     * @return if the local object is older than the central one
     */
    public boolean hasChanged( String localLSID, String centralLSID ) {
        // if the comparision is greater than 0, then the local LSID is older
        if ( localLSID == null || centralLSID == null ) {
            return true;
        }
        return centralLSID.compareToIgnoreCase( localLSID ) > 0;
    }

    public boolean change( T object ) {
        if ( unregister( object ) ) {
            logger.fine( object.getLSID() + " was unregistered and will now be added to the local repository !" );
            if ( register( object ) ) {
                return true;
            }
            logger.warning( object.getLSID() + " was deleted from the registry but could not been added again !" );
            return false;
        }
        logger.warning( object.getLSID() + " could not been deleted and therefore no changes were applied !" );
        return false;
    }

    /**
     * Synchronize the two centrals.<br>
     * The list parameters are representing the collection of the entries currently found at the centrals. Each entry in
     * the central list is checked whether it exists in the local central.<br>
     * If not, the object is registered at the local central<br>
     * If yes, the object is tested whether the local one is out of date, and if so, the changes are applied.<br>
     * At the end all entries which havent been found in the master list, but in the local list are unregistered from
     * the local central.
     * 
     * @param centralList
     *            the entries of the master central
     * @param local
     *            the entries of the local central
     */
    @SuppressWarnings( "unchecked" )
    public void synchronizeCentrals( T[] centralList, T[] local ) {
        List< T > localList = new ArrayList< T >( Arrays.asList( local ) );
        // the local list is sorted, to enable a binary search later.
        // the sorting is guaranteed, because the typparameter T must be a comparable instance
        Collections.sort( localList );

        // for every entry in the central list check whether this entry is new or has changed
        for ( T centralEntry : centralList ) {
            // search for the occurency of the entry in the locallist
            int index = Collections.binarySearch( localList, centralEntry );

            // get the lsid for pretty printing
            String centralLSID = centralEntry.getLSID();

            if(centralLSID == null) {
                logger.warning(centralEntry + " does not have a LISD ! No changes applied !");
                continue;
            }
            
            // the entry was not found in the local list
            if ( index < 0 ) {
                logger.fine( centralLSID + " is unkown locally - try to register it !" );
                // try to register the entry
                if ( register( centralEntry ) ) {
                    logger.info( centralLSID + " has been successfully registered !" );
                }
            }
            else {
                // fetch the found entry
                T localEntry = localList.remove( index );

                // get the lsid for pretty printing
                String localLSID = localEntry.getLSID();
                
                if(localLSID == null) {
                    logger.warning(localEntry + " does not have a LISD ! No changes applied !");
                    continue;
                }
                
                // check whether the entry has changed
                if ( hasChanged( localLSID, centralLSID ) ) {
                    logger.fine( localLSID + " has changed - try to apply the changes !" );
                    // try to update the entry
                    if ( change( centralEntry ) ) {
                        logger.info( localLSID + " has been successfully changed !" );
                    }
                }
            }
        }

        // because every found entry was removed from the local list, the remaining entries have
        // been deleted at the amster central and therefore have to be unregistered at the local
        // central.
        for ( T remainingLocals : localList ) {
            String lsid = remainingLocals.getLSID();
            
            if(lsid == null) {
                logger.warning(remainingLocals + " does not have a LISD ! No changes applied !");
                continue;
            }
            
            // if the current service is not from the authortiy where the program is running
            // it means it was unregistered at the master central and therefore will be unregistered
            // locally.
            // Services which do have the authority in the LSID are considered to be private services
            // and are therefore untouched by this procedure.
            if ( !lsid.contains( authority ) ) {
                logger.fine( remainingLocals.getLSID() + " is unkown at the central - try to unregister it locally !" );
                // try to unregister it
                if ( unregister( remainingLocals ) ) {
                    logger.info( remainingLocals.getLSID() + " has been successfully unregistered ! " );
                }
            }
        }
    }
}