//   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.treeimpl;

import java.util.ArrayList;
import java.util.HashMap;

import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskMetric;

/**
 * <p>
 * this is the default implementation of the interface {@link ITaskInfo}. It
 * does not do anything fancy except implementing the interface.
 * </p>
 * 
 * @author Patrick Harms
 */
public class TaskInfo implements ITaskInfo {
    
    /**
     * <p>
     * the task to which the infos belong
     * </p>
     */
    private ITask task;
    
    /**
     * <p>
     * all available measures for the task
     * </p>
     */
    private ArrayList<Measure> measures = new ArrayList<Measure>();

    /**
     * <p>
     * initialized the task infos with the task to which they belong.
     * </p>
     * 
     * @param task
     */
    TaskInfo(ITask task) {
        this.task = task;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo#getTask()
     */
    @Override
    public ITask getTask() {
        return task;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo#getMeasures()
     */
    @Override
    public IMeasure[] getMeasures() {
        measures.trimToSize();
        return measures.toArray(new IMeasure[measures.size()]);
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo#getMeasureValue(java.lang.String)
     */
    @Override
    public int getMeasureValue(TaskMetric metric) {
        Measure measure = getMeasure(metric);
        
        if (measure == null) {
            throw new IllegalArgumentException("unknown metric " + metric);
        }
        
        return measure.getValue();
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo#getMeasureValue(java.lang.String, de.ugoe.cs.autoquest.tasktrees.treeifc.ITask)
     */
    @Override
    public int getMeasureValue(TaskMetric metric, ITask context) {
        Measure measure = getMeasure(metric);
        
        if (measure == null) {
            throw new IllegalArgumentException("unknown metric " + metric);
        }
        
        return measure.getValue(context);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public synchronized String toString() {
        return "TaskInfo(" + task + ")";
    }

    /**
     * <p>
     * must be called to indicate that a new new measures for the provided metric are about to
     * be calculated and added.
     * </p>
     *
     * @param metric the metric for which measures are about to be provided
     */
    void addMeasure(TaskMetric metric) {
        Measure measure = getMeasure(metric);
        
        if (measure != null) {
            throw new IllegalArgumentException("measure for metric " + metric + " already exists.");
        }
        
        measure = new Measure(metric);
        measures.add(measure);
    }

    /**
     * <p>
     * sets a specific value for a measure of a specific metric in the provided context of the task
     * </p>
     * 
     * @param metric  the metric to which the value belongs
     * @param context the context of the task in which the measure was recorded
     * @param value   the value of the measure
     */
    void setCount(TaskMetric metric, ITask context, int value) {
        Measure measure = getMeasure(metric);
        
        if (measure == null) {
            throw new IllegalArgumentException("unknown metric. Please create a measure " +
                                               "for the metric before using it.");
        }
        
        measure.set(context, value);
    }

    /**
     * <p>
     * increases a specific value for a measure of a specific metric in the provided context of the
     * task
     * </p>
     * 
     * @param metric    the metric to which the value belongs
     * @param context   the context of the task in which the measure was recorded
     * @param increment the increment to be added to the value of the measure
     */
    void increaseCount(TaskMetric metric, ITask context, int increment) {
        Measure measure = getMeasure(metric);
        
        if (measure == null) {
            throw new IllegalArgumentException("unknown metric. Please create a measure " +
                                               "for the metric before using it.");
        }
        
        measure.increase(context, increment);
    }

    /**
     * <p>
     * convenience method to internally determine the measure for a specific metric
     * </p>
     */
    private Measure getMeasure(TaskMetric metric) {
        for (Measure candidate : measures) {
            if (candidate.getMetric().equals(metric)) {
                return candidate;
            }
        }
        
        return null;
    }

    /**
     * <p>
     * implementation for the measure interface of the task info interface. Does nothing fancy
     * except implementing the interface
     * </p>
     * 
     * @author Patrick Harms
     */
    private static class Measure implements IMeasure {

        /**
         * <p>
         * the metric to which the measure belongs
         * </p>
         */
        private TaskMetric metric;
        
        /**
         * <p>
         * the observed values for the difference contexts of the task
         * </p>
         */
        private HashMap<ITask, Integer> values;
        
        /**
         * <p>
         * the context free value of the measure independent of the task context
         * </p>
         */
        private int contextFreeValue = 0;
        
        /**
         * <p>
         * initializes the measure with a specific metric
         * </p>
         */
        private Measure(TaskMetric metric) {
            super();
            this.metric = metric;
        }

        /* (non-Javadoc)
         * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo.IMeasure#getMetric()
         */
        @Override
        public TaskMetric getMetric() {
            return metric;
        }

        /* (non-Javadoc)
         * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo.IMeasure#getValue()
         */
        @Override
        public int getValue() {
            return contextFreeValue;
        }

        /* (non-Javadoc)
         * @see de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInfo.IMeasure#getValue(de.ugoe.cs.autoquest.tasktrees.treeifc.ITask)
         */
        @Override
        public int getValue(ITask context) {
            if ((context != null) && (values != null)) {
                Integer currentValue = values.get(context);
                
                if (currentValue != null) {
                    return currentValue;
                }
            }
            
            return Integer.MIN_VALUE;
        }

        /**
         * <p>
         * sets the value of the measure context free as well as specific to the provided context
         * </p>
         */
        private void set(ITask context, int value) {
            contextFreeValue = value;
            
            if (context != null) {
                if (values == null) {
                    values = new HashMap<ITask, Integer>();
                }
                
                values.put(context, value);
            }
        }

        /**
         * <p>
         * increases the value of the measure context free as well as specific to the provided
         * context according to the provided increment
         * </p>
         */
        private void increase(ITask context, int increment) {
            contextFreeValue += increment;
            
            if (context != null) {
                if (values == null) {
                    values = new HashMap<ITask, Integer>();
                }
                
                Integer currentValue = values.get(context);
                
                if (currentValue == null) {
                    currentValue = 0;
                }
                
                values.put(context, currentValue + increment);
            }
        }

    }

}
