package ifs.solution;

import ifs.model.*;
import ifs.solver.*;
import ifs.util.*;
import ifs.perturbations.*;
import java.util.*;

/**
 * Generic solution.
 * <br><br>
 * It consist from the model and information about current iteration and solution time.
 * 
 * @see Model
 * @see ifs.solver.Solver
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class Solution {
    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Solution.class);
    private static java.text.DecimalFormat sTimeFormat = new java.text.DecimalFormat("0.00",new java.text.DecimalFormatSymbols(Locale.US));
    
    private Model iModel;
    private long iIteration = 0;
    private double iTime = 0.0;
    
    private boolean iBestComplete = false;
    private Hashtable iBestInfo = null;
    private long iBestIteration = -1;
    private double iBestTime = -1;
    private double iBestPerturbationsPenaly = -1.0;
    private int iBestValue = 0;
    private int iBestPertirbations = -1;
    
    private Vector iSolutionListeners = new FastVector();
    private PerturbationsCounter iPerturbationsCounter = null;

    /** Constructor */
    public Solution(Model model) {
        this(model, 0, 0.0);
    }

    /** Constructor */
    public Solution(Model model, long iteration, double time) {
        iModel =  model;
        iIteration = iteration;
        iTime = time;
    }
    
    /** Current iteration */
    public long getIteration() {
        return iIteration;
    }
    
    /** The model associated with the solution */
    public Model getModel() {
        return iModel;
    }
    
    /** Current solution time (time in seconds from the start of the solver) */
    public double getTime() {
        return iTime;
    }
    
    /** Update time, increment current iteration */
    public void update(double time) { 
        iTime = time; iIteration++;
        for (Enumeration i=iSolutionListeners.elements();i.hasMoreElements();)
            ((SolutionListener)i.nextElement()).solutionUpdated(this);
    }
    
    /** Initialization */
    public void init(Solver solver) { 
        iIteration=0; 
        iTime=0;
        if (iModel!=null) iModel.init(solver);
        iPerturbationsCounter = solver.getPerturbationsCounter();
    }
    
    public String toString() {
        return "Solution{\n  model="+iModel+",\n  iteration="+iIteration+",\n  time="+iTime+"\n}";
    }
    
    /** Solution information. It consits from info from the model which is associated with the solution, 
     * time, iteration, speed and infos from all solution listeners.
     */
    public Hashtable getInfo() {
        Hashtable ret=getModel().getInfo();
        if (getPerturbationsCounter()!=null) getPerturbationsCounter().getInfo(ret,this);
        ret.put("Time",sTimeFormat.format(getTime())+" sec");
        ret.put("Iteration",String.valueOf(getIteration()));
        if (getTime()>0) ret.put("Speed",sTimeFormat.format((getIteration())/(double)getTime())+" it/s");
        for (Enumeration i=iSolutionListeners.elements();i.hasMoreElements();)
            ((SolutionListener)i.nextElement()).getInfo(this, ret);
        return ret;
    }
    
    /** Info of the best ever found solution */
    public Hashtable getBestInfo() { return iBestInfo; }
    /** Iteration when the best ever found solution was found */
    public long getBestIteration() { return (iBestIteration<0?getIteration():iBestIteration); }
    /** Solution time when the best ever found solution was found */
    public double getBestTime() { return (iBestTime<0?getTime():iBestTime); }
    /** Number of perturbation variables (variables with non-initial value assigned) if the best ever solution found */
    public int getBestPertirbations() { return iBestPertirbations; }
    /** Returns true, if all variables of the best ever solution found are assigned */
    public boolean isBestComplete() { return iBestComplete; }
    /** Total value of the best ever found solution -- sum of all assigned values (see {@link Value#toInt()}).*/
    public int getBestValue() { return iBestValue; }
    /** Perturbation penalty of the best ever found solution (see {@link PerturbationsCounter}) */
    public double getBestPerturbationsPenalty() { return iBestPerturbationsPenaly; }
    
    /** Returns perturbation counter */
    public PerturbationsCounter getPerturbationsCounter() { return iPerturbationsCounter; }
    
    /** Clear the best ever found solution */
    public void clearBest() {
        iBestInfo = null;
        iBestTime = 0;
        iBestIteration = 0;
        iBestPertirbations = -1;
        iBestComplete = false;
        iBestValue = 0;
        iBestPerturbationsPenaly = -1.0;
        for (Enumeration i=iSolutionListeners.elements();i.hasMoreElements();)
            ((SolutionListener)i.nextElement()).bestCleared(this);
    }
    
    /** Save the current solution as the best ever found solution (it also calls {@link Model#saveBest()}) */
    public void saveBest() {
        getModel().saveBest();
        iBestInfo = getInfo();
        iBestTime = getTime();
        iBestIteration = getIteration();
        iBestPertirbations = getModel().perturbVariables().size();
        iBestComplete = getModel().unassignedVariables().isEmpty();
        iBestValue = getModel().getTotalValue();
        iBestPerturbationsPenaly = iPerturbationsCounter.getPerturbationPenalty(this);
        for (Enumeration i=iSolutionListeners.elements();i.hasMoreElements();)
            ((SolutionListener)i.nextElement()).bestSaved(this);
    }
    
    /** Restore the best ever found solution into the current solution (it also calls {@link Model#restoreBest()})*/
    public void restoreBest() {
        if (iBestInfo==null) return;
        getModel().restoreBest();
        iTime = iBestTime;
        iIteration = iBestIteration;
        for (Enumeration i=iSolutionListeners.elements();i.hasMoreElements();)
            ((SolutionListener)i.nextElement()).bestRestored(this);
    }

    /** Adds solution listner */
    public void addSolutionListener(SolutionListener listener) {
        iSolutionListeners.addElement(listener);
    }
    /** Removes solution listener */
    public void removeSolutionListener(SolutionListener listener) {
        iSolutionListeners.removeElement(listener);
    }
}
