//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.tasktrees.temporalrelation;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;

import de.ugoe.cs.autoquest.tasktrees.taskequality.TaskEquality;
import de.ugoe.cs.autoquest.tasktrees.taskequality.TaskEqualityRuleManager;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.usageprofiles.SymbolComparator;

/**
 * <p>
 * implementation of a symbol comparator for task instances. Internally, it uses comparison buffers
 * to prevent comparing two tasks or task instances several times. It internally instantiates
 * comparers being the implementation strategy of the comparisons required for a specific level
 * of task equality. The comparers internally use the {@link TaskEqualityRuleManager} for
 * performing comparisons.
 * </p>
 */
public class TaskInstanceComparator implements SymbolComparator<ITaskInstance> {
    
    /**  */
    private static final long serialVersionUID = 1L;
    
    /**
     * the maximum size of the internal buffer used for storing comparison results
     */
    private static final int MAX_BUFFER_SIZE = 2 * 1024 * 1024;

    /**
     * the considered level of task equality
     */
    private TaskEquality minimalTaskEquality;

    /**
     * the comparer used internally for comparing two tasks
     */
    private transient Comparer comparer;

    /**
     * the comparer used for comparing two tasks on the lexical level
     */
    private transient Comparer lexicalComparer;

    /**
     * internal buffer used for storing comparison results
     */
    private transient HashMap<Long, Boolean> equalityBuffer = new HashMap<Long, Boolean>();

    /**
     * internal buffer used for storing comparison results only for lexical comparisons
     */
    private transient HashMap<Long, Boolean> lexicalEqualityBuffer;

    /**
     * <p>
     * initializes the comparator with a considered task equality level
     * </p>
     * 
     * @param minimalTaskEquality the considered task equality level
     */
    public TaskInstanceComparator(TaskEquality minimalTaskEquality) {
        this.minimalTaskEquality = minimalTaskEquality;
        init();
    }

    /* (non-Javadoc)
     * @see SymbolComparator#equals(Object, Object)
     */
    @Override
    public boolean equals(ITaskInstance taskInstance1, ITaskInstance taskInstance2) {
        return equals(taskInstance1.getTask(), taskInstance2.getTask());
    }        

    /**
     * <p>
     * returns true, if this comparator considers the provided tasks as equal, false else
     * </p>
     * 
     * @param task1 the first task to compare
     * @param task2 the second task to compare
     * 
     * @return as described
     */
    public boolean equals(ITask task1, ITask task2) {
        Boolean result;
        
        if (task1 != task2) {
            //if ((task1 instanceof IEventTask) && (task2 instanceof IEventTask)) {
                long key = ((long) System.identityHashCode(task1)) << 32;
                key += System.identityHashCode(task2);
            
                result = equalityBuffer.get(key);
            
                if (result == null) {
                    result = comparer.compare(task1, task2);
                    
                    if (equalityBuffer.size() < MAX_BUFFER_SIZE) {
                        equalityBuffer.put(key, result);
                    }
                }
            /*}
            else {
                result = false;
            }*/
        }
        else {
            result = true;
        }
        
        return result;
    }

    /**
     * <p>
     * returns true, if this comparator considers the provided tasks as lexically equal, false else
     * </p>
     * 
     * @param task1 the first task to compare
     * @param task2 the second task to compare
     * 
     * @return as described
     */
    public boolean areLexicallyEqual(ITask task1, ITask task2) {
        Boolean result;
        
        if (task1 != task2) {
            long key = ((long) System.identityHashCode(task1)) << 32;
            key += System.identityHashCode(task2);
            
            result = lexicalEqualityBuffer.get(key);
            
            if (result == null) {
                result = lexicalComparer.compare(task1, task2);
                if (equalityBuffer.size() < MAX_BUFFER_SIZE) {
                    lexicalEqualityBuffer.put(key, result);
                }
            }
        }
        else {
            result = true;
        }
        
        return result;
    }
    
    /**
     * <p>
     * can be called externally to clear the internal comparison buffers
     * </p>
     */
    public void clearBuffers() {
        equalityBuffer.clear();
        init();
    }
    
    /**
     * <p>
     * initializes the comparator with comparers depending on the different comparison levels as
     * well as with the required comparison buffers. Comparers and buffers for lexical comparison
     * may be reused if the considered equality level is also lexical.
     * </p>
     */
    private void init() {
        if (minimalTaskEquality == TaskEquality.LEXICALLY_EQUAL) {
            comparer = new LexicalComparer();
        }
        else if (minimalTaskEquality == TaskEquality.SYNTACTICALLY_EQUAL) {
            comparer = new SyntacticalComparer();
        }
        else if (minimalTaskEquality == TaskEquality.SEMANTICALLY_EQUAL) {
            comparer = new SemanticalComparer();
        }
        else {
            comparer = new DefaultComparer(this.minimalTaskEquality);
        }
        
        if (minimalTaskEquality == TaskEquality.LEXICALLY_EQUAL) {
            lexicalComparer = comparer;
            lexicalEqualityBuffer = equalityBuffer;
        }
        else {
            lexicalComparer = new LexicalComparer();
            lexicalEqualityBuffer = new HashMap<Long, Boolean>();
        }
    }
    
    /**
     * <p>
     * deserialize this object and reinitialize the buffers
     * </p>
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        init();
    }


    /**
     * <p>
     * interface for internally used comparers containing only a compare method
     * </p>
     */
    private static interface Comparer {
        
        /**
         * <p>
         * returns true, if this comparator considers the provided tasks as equal, false else
         * </p>
         * 
         * @param task1 the first task to compare
         * @param task2 the second task to compare
         * 
         * @return as described
         */
        boolean compare(ITask task1, ITask task2);
    }

    /**
     * <p>
     * comparer that performs comparisons only on the lexical level
     * </p>
     */
    private static class LexicalComparer implements Comparer {
        
        /* (non-Javadoc)
         * @see Comparer#compare(ITask, ITask)
         */
        public boolean compare(ITask task1, ITask task2) {
            return TaskEqualityRuleManager.getInstance().areLexicallyEqual(task1, task2);
        }
    }

    /**
     * <p>
     * comparer that performs comparisons only on the syntactical level
     * </p>
     * 
     */
    private static class SyntacticalComparer implements Comparer {
        
        /* (non-Javadoc)
         * @see Comparer#compare(ITask, ITask)
         */
        public boolean compare(ITask task1, ITask task2) {
            return TaskEqualityRuleManager.getInstance().areSyntacticallyEqual(task1, task2);
        }
    }

    /**
     * <p>
     * comparer that performs comparisons only on the semantical level
     * </p>
     */
    private static class SemanticalComparer implements Comparer {
        
        /* (non-Javadoc)
         * @see Comparer#compare(ITask, ITask)
         */
        public boolean compare(ITask task1, ITask task2) {
            return TaskEqualityRuleManager.getInstance().areSemanticallyEqual(task1, task2);
        }
    }

    /**
     * <p>
     * comparer that performs comparisons only on the provided level
     * </p>
     */
    private static class DefaultComparer implements Comparer {
        
        /**
         * <p>
         * the minimal task equality considered by this comparer
         * </p>
         */
        private TaskEquality minimalTaskEquality;
        
        /**
         * <p>
         * initializes this comparer with the task equality to be considered
         * </p>
         */
        public DefaultComparer(TaskEquality minimalTaskEquality) {
           this.minimalTaskEquality = minimalTaskEquality;
        }
        
        /* (non-Javadoc)
         * @see Comparer#compare(ITask, ITask)
         */
        public boolean compare(ITask task1, ITask task2) {
            return TaskEqualityRuleManager.getInstance().areAtLeastEqual
                (task1, task2, minimalTaskEquality);
        }
    }

}