package ttsolver;

import ifs.extension.*;
import ifs.model.*;
import ifs.solution.*;
import ifs.solver.*;
import ifs.util.*;

import java.io.*;
import java.util.*;
import org.apache.log4j.helpers.FileWatchdog;

import ttsolver.constraint.*;
import ttsolver.model.*;
import ttsolver.heuristics.*;

/**
 * A main class for running of the solver from command line.
 * <br><br>
 * Usage:<br>
 * java -cp ifs.jar;ttsolver.jar;log4j.jar;dom4j.jar ttsolver.Test test.properties
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class Test implements SolutionListener {
    private static java.text.SimpleDateFormat sDateFormat = new java.text.SimpleDateFormat("dd-MMM-yy_HHmmss",java.util.Locale.US);
    private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.000",new java.text.DecimalFormatSymbols(Locale.US));
    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Test.class);
    
    private PrintWriter iCSVFile = null;
    
    private ConflictStatistics iStat = null;
    private MacPropagation iProp = null;
    private ViolatedInitials iViolatedInitials = null;
    private int iLastNotified = -1;
    
    public void init(Solver solver) {
        for (Enumeration i=solver.getExtensions().elements();i.hasMoreElements();) {
            Extension extension = (Extension)i.nextElement();
            if (extension instanceof ConflictStatistics)
                iStat = (ConflictStatistics) extension;
            if (extension instanceof MacPropagation)
                iProp = (MacPropagation)extension;
            if (extension instanceof ViolatedInitials)
                iViolatedInitials = (ViolatedInitials)extension;
        }
        solver.currentSolution().addSolutionListener(this);
    }
    
    private String getTimetableLoaderClass(DataProperties properties) {
        String loader = properties.getProperty("TimetableLoader");
        if (loader!=null) return loader;
        if (properties.getPropertyInt("General.InputVersion",-1)>=0)
            return "ttsolver.TimetableDatabaseLoader";
        else
            return "ttsolver.TimetablePrologLoader";
    }
    
    private String getTimetableSaverClass(DataProperties properties) {
        String saver = properties.getProperty("TimetableSaver");
        if (saver!=null) return saver;
        if (properties.getPropertyInt("General.InputVersion",-1)>=0)
            return "ttsolver.TimetableDatabaseSaver";
        else
            return "ttsolver.TimetablePrologSaver";
    }

    public Test(String[] args) {
        try {
            Progress.getInstance().addProgressListener(new ProgressWriter(System.out));
            DataProperties properties = ToolBox.loadProperties(new java.io.File(args[0]));
            properties.setProperty("General.Output", properties.getProperty("General.Output",".")+File.separator+(sDateFormat.format(new Date())));
            if (args.length>1)
                properties.setProperty("General.Output", args[1]+File.separator+(sDateFormat.format(new Date())));
            System.out.println("Output folder: "+properties.getProperty("General.Output"));
            ToolBox.configureLogging(properties.getProperty("General.Output"),null);
            
            edu.purdue.smas.timetable.data.Preferences.loadStatic();
            edu.purdue.smas.timetable.data.GroupConstraintTypes.loadStatic();
            
            File outDir = new File(properties.getProperty("General.Output","."));
            outDir.mkdirs();
            
            Solver solver = new Solver(properties);
            TimetableModel model = new TimetableModel(properties);
            
            TimetableLoader loader = (TimetableLoader)Class.forName(getTimetableLoaderClass(properties)).getConstructor(new Class[] {TimetableModel.class}).newInstance(new Object[] {model});
            PrintWriter out = new PrintWriter(new FileWriter(new File(outDir, "load.txt")));
            loader.load(out);
            out.flush(); out.close();
            
            printSomeStuff(model);
            
            solver.setInitalSolution(model);
            init(solver);
            
            iCSVFile = new PrintWriter(new FileWriter(outDir.toString()+File.separator+"stat.csv"));
            String colSeparator = ";";
            iCSVFile.println(
                    "Assigned"+colSeparator+
                    "Assigned[%]"+colSeparator+
                    "Time[min]"+colSeparator+
                    "Iter"+colSeparator+
                    "IterYield[%]"+colSeparator+
                    "Speed[it/s]"+colSeparator+
                    "AddedPert"+colSeparator+
                    "AddedPert[%]"+colSeparator+
                    "HardStudentConf"+colSeparator+
                    "StudentConf"+colSeparator+
                    "DistStudentConf"+colSeparator+
                    "TimePref"+colSeparator+
                    "RoomPref"+colSeparator+
                    "DistInstrPref"+colSeparator+
                    "GrConstPref"+colSeparator+
                    "UselessSlots"+colSeparator+
                    "TooBigRooms"+
                    (iProp!=null?colSeparator+"GoodVars"+colSeparator+
                    "GoodVars[%]"+colSeparator+
                    "GoodVals"+colSeparator+
                    "GoodVals[%]":"")
                    );
            iCSVFile.flush();
            
            solver.start();
            solver.getSolverThread().join();
            
            long lastIt = solver.lastSolution().getIteration();
            double lastTime = solver.lastSolution().getTime();
            
            if (solver.lastSolution().getBestInfo()!=null) {
                Solution bestSolution = solver.lastSolution();//.cloneBest();
                sLogger.info("Last solution: "+ToolBox.dict2string(bestSolution.getInfo(),1));
                bestSolution.restoreBest();
                sLogger.info("Best solution: "+ToolBox.dict2string(bestSolution.getInfo(),1));
                ((TimetableModel)bestSolution.getModel()).switchStudents();
                sLogger.info("Best solution: "+ToolBox.dict2string(bestSolution.getInfo(),1));
                saveOutputCSV(bestSolution,new File(outDir,"output.csv"));
                
                TimetableSaver saver = (TimetableSaver)Class.forName(getTimetableSaverClass(properties)).getConstructor(new Class[] {Solution.class}).newInstance(new Object[] {bestSolution});
                out = new PrintWriter(new FileWriter(new File(outDir, "save.txt")));
                saver.save(out);
                out.flush(); out.close();
            } else sLogger.info("Last solution:"+ToolBox.dict2string(solver.lastSolution().getInfo(),1));
            
            iCSVFile.close();
            
            sLogger.info("Total number of done iteration steps:"+lastIt);
            sLogger.info("Achieved speed: "+sDoubleFormat.format(lastIt/lastTime)+" iterations/second");
        } catch (Throwable t) {
            sLogger.error("Test failed.",t);
        }
    }
    
    public static void main(String[] args) {
        new Test(args);
    }
    
    public void bestCleared(Solution solution) {
    }
    
    public void bestRestored(Solution solution) {
    }
    
    public void bestSaved(Solution solution) {
        notify(solution);
    }
    
    public void getInfo(Solution solution, java.util.Dictionary info) {
    }
    
    public void solutionUpdated(Solution solution) {
    }
    
    public void notify(Solution solution) {
        String colSeparator = ";";
        if (!solution.getModel().unassignedVariables().isEmpty() && iLastNotified==solution.getModel().assignedVariables().size()) return;
        iLastNotified=solution.getModel().assignedVariables().size();
        if (iCSVFile!=null) {
            TimetableModel model = (TimetableModel) solution.getModel();
            iCSVFile.print(model.variables().size()-model.unassignedVariables().size());
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(100.0*(model.variables().size()-model.unassignedVariables().size())/model.variables().size()));
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(((double)solution.getTime())/60.0));
            iCSVFile.print(colSeparator);
            iCSVFile.print(solution.getIteration());
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(100.0*(model.variables().size()-model.unassignedVariables().size())/solution.getIteration()));
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(((double)solution.getIteration())/(double)solution.getTime()));
            iCSVFile.print(colSeparator);
            iCSVFile.print(model.perturbVariables().size());
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(100.0*model.perturbVariables().size()/model.variables().size()));
            
            long studentConflicts = 0;
            long hardStudentConflicts = 0;
            long uselessSlots = 0;
            for (Enumeration it1=((TimetableModel)solution.getModel()).getRoomConstraints().elements();it1.hasMoreElements();) {
                RoomConstraint constraint = (RoomConstraint)it1.nextElement();
                uselessSlots+=constraint.countUselessSlots();
            }
            for (Enumeration it1=((TimetableModel)solution.getModel()).getJenrlConstraints().elements();it1.hasMoreElements();) {
                JenrlConstraint jenrl = (JenrlConstraint) it1.nextElement();
                if (jenrl.isInConflict()) {
                    studentConflicts+=jenrl.getJenrl();
                    Lecture l1 = (Lecture)jenrl.first();
                    Lecture l2 = (Lecture)jenrl.second();
                    if (l1.sameLectures()!=null && l1.sameLectures().size()==1 &&
                            l2.sameLectures()!=null && l2.sameLectures().size()==1)
                        hardStudentConflicts+=jenrl.getJenrl();
                }
            }
            iCSVFile.print(colSeparator);
            iCSVFile.print(hardStudentConflicts);
            iCSVFile.print(colSeparator);
            iCSVFile.print(studentConflicts);
            iCSVFile.print(colSeparator);
            iCSVFile.print(((TimetableModel)solution.getModel()).getStudentDistanceConflicts());
            iCSVFile.print(colSeparator);
            iCSVFile.print(sDoubleFormat.format(((TimetableModel)solution.getModel()).getGlobalTimePreference()));
            iCSVFile.print(colSeparator);
            iCSVFile.print(((TimetableModel)solution.getModel()).getGlobalRoomPreference());
            iCSVFile.print(colSeparator);
            iCSVFile.print(((TimetableModel)solution.getModel()).getInstructorDistancePreference());
            iCSVFile.print(colSeparator);
            iCSVFile.print(((TimetableModel)solution.getModel()).getGlobalGroupConstraintPreference());
            iCSVFile.print(colSeparator);
            iCSVFile.print(uselessSlots);
            iCSVFile.print(colSeparator);
            iCSVFile.print(((TimetableModel)solution.getModel()).countTooBigRooms());
            if (iProp!=null) {
                if (solution.getModel().unassignedVariables().size()>0) {
                    int goodVariables=0;
                    long goodValues=0;
                    long allValues=0;
                    for (Enumeration i=solution.getModel().unassignedVariables().elements();i.hasMoreElements();) {
                        Lecture variable = (Lecture)i.nextElement();
                        goodValues += iProp.goodValues(variable).size();
                        allValues += variable.values().size();
                        if (!iProp.goodValues(variable).isEmpty()) goodVariables++;
                    }
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(goodVariables);
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(sDoubleFormat.format(100.0*goodVariables/solution.getModel().unassignedVariables().size()));
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(goodValues);
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(sDoubleFormat.format(100.0*goodValues/allValues));
                } else {
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(colSeparator);
                    iCSVFile.print(colSeparator);
                }
            }
            iCSVFile.println();
            iCSVFile.flush();
        }
    }
    
    private void printSomeStuff(TimetableModel model) {
        long nrValues = 0;
        double totalMaxNormTimePref = 0.0;
        double totalMinNormTimePref = 0.0;
        int totalMaxTimePref = 0;
        int totalMinTimePref = 0;
        int totalMaxRoomPref = 0;
        int totalMinRoomPref = 0;
        long nrStudentEnrls = 0;
        long nrInevitableStudentConflicts = 0;
        long nrJenrls = 0;
        int nrHalfHours = 0;
        int nrMeetings = 0;
        int nrPairs = 0;
        int nrPairsNoSameClass = 0;
        int nrStudentSharingPairs = 0;
        int nrRoomSharingPairs = 0;
        int nrInstructorSharingPairs = 0;
        int nrSingleValueVariables = 0;
        for (Enumeration e1=model.variables().elements();e1.hasMoreElements();) {
            Lecture lect = (Lecture)e1.nextElement();
            double maxNormTimePref = Double.MIN_VALUE;
            double minNormTimePref = Double.MAX_VALUE;
            int maxTimePref = Integer.MIN_VALUE;
            int minTimePref = Integer.MAX_VALUE;
            int maxRoomPref = Integer.MIN_VALUE;
            int minRoomPref = Integer.MAX_VALUE;
            nrStudentEnrls += (lect.allStudents()==null?0:lect.allStudents().size());
            nrValues += lect.values().size();
            for (Enumeration e2=lect.values().elements();e2.hasMoreElements();) {
                Placement placement = (Placement)e2.nextElement();
                maxNormTimePref = Math.max(maxNormTimePref,placement.getTimeLocation().getNormalizedPreference());
                minNormTimePref = Math.min(minNormTimePref,placement.getTimeLocation().getNormalizedPreference());
                maxTimePref = Math.max(maxTimePref,placement.getTimeLocation().getPreference());
                minTimePref = Math.min(maxTimePref,placement.getTimeLocation().getPreference());
                maxRoomPref = Math.max(maxRoomPref,placement.getRoomLocation().getPreference());
                minRoomPref = Math.min(minRoomPref,placement.getRoomLocation().getPreference());
            }
            if (!lect.values().isEmpty()) {
                Placement p = (Placement)lect.values().firstElement();
                nrMeetings += p.getTimeLocation().getNrMeetings();
                nrHalfHours += p.getTimeLocation().getNrMeetings() * p.getTimeLocation().getNrHalfHoursPerMeeting();
                totalMaxNormTimePref += maxNormTimePref;
                totalMinNormTimePref += minNormTimePref;
                totalMaxTimePref += maxTimePref;
                totalMinTimePref += minTimePref;
                totalMaxRoomPref += maxRoomPref;
                totalMinRoomPref += minRoomPref;
            }
            if (lect.values().size()==1) {
                nrSingleValueVariables++;
                Placement p1 = (Placement)lect.values().firstElement();
                for (Enumeration e2=lect.jenrlConstraints().elements();e2.hasMoreElements();) {
                    JenrlConstraint jenrl = (JenrlConstraint)e2.nextElement();
                    Lecture another = (Lecture)jenrl.another(lect);
                    if (another.values().size()==1) {
                        Placement p2 = (Placement)another.values().firstElement();
                        if (JenrlConstraint.isInConflict(p1,p2)) {
                            nrInevitableStudentConflicts += jenrl.getJenrl();
                        }
                    }
                }
            }
        }
        for (Enumeration e=model.getJenrlConstraints().elements();e.hasMoreElements();) {
            JenrlConstraint jenrl = (JenrlConstraint)e.nextElement();
            nrJenrls += jenrl.getJenrl();
        }
        sLogger.debug("Total number of variables: "+model.variables().size());
        sLogger.debug("Total number of variables with only one value: "+nrSingleValueVariables);
        sLogger.debug("Total number of values: "+nrValues);
        sLogger.debug("Total maximum normalized time preference: "+sDoubleFormat.format(totalMaxNormTimePref));
        sLogger.debug("Total minimum normalized time preference: "+sDoubleFormat.format(totalMinNormTimePref));
        sLogger.debug("Total maximum time preferences: "+totalMaxTimePref);
        sLogger.debug("Total minimum time preferences: "+totalMinTimePref);
        sLogger.debug("Total maximum room preferences: "+totalMaxRoomPref);
        sLogger.debug("Total minimum room preferences: "+totalMinRoomPref);
        sLogger.debug("Total amount of student enrollments: "+nrStudentEnrls);
        sLogger.debug("Total amount of joined enrollments: "+nrJenrls);
        sLogger.debug("Total amount of inevitable student conflicts: "+nrInevitableStudentConflicts);
        sLogger.debug("Total number of meetings: "+nrMeetings);
        sLogger.debug("Total number of half-hours: "+nrHalfHours);
        sLogger.debug("Total number of rooms: "+model.getRoomConstraints().size());
    }
    
    private void saveOutputCSV(Solution s,File f) {
        try {
            PrintWriter w = new PrintWriter(new FileWriter(f));
            TimetableModel m = (TimetableModel)s.getModel();
            w.println("Assigned variables;"+m.assignedVariables().size());
            if (m.getProperties().getPropertyBoolean("General.MPP",false))
                w.println("Add. perturbancies;"+s.getPerturbationsCounter().getPerturbationPenalty(s));
            w.println("Time [sec];"+sDoubleFormat.format(s.getBestTime()));
            w.println("Hard student conflicts;"+m.getHardStudentConflicts());
            if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
                w.println("Distance student conf.;"+m.getStudentDistanceConflicts());
            w.println("Student conflicts;"+m.getViolatedStudentConflicts());
            w.println("Time preferences;"+sDoubleFormat.format(m.getGlobalTimePreference()));
            w.println("Room preferences;"+m.getGlobalRoomPreference());
            w.println("Useless half-hours;"+m.getUselessSlots());
            w.println("Too big room;"+m.countTooBigRooms());
            if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
                w.println("Distance instructor pref.;"+m.getInstructorDistancePreference());
            if (m.getProperties().getPropertyBoolean("General.UseDepartmentSpreadConstraints",true)) {
                w.println("Dept. spread penalty;"+sDoubleFormat.format(m.getDepartmentSpreadPenalty()));
            }
            if (m.getProperties().getPropertyBoolean("General.MPP",false)) {
                Hashtable mppInfo = ((UniversalPerturbationsCounter)s.getPerturbationsCounter()).getCompactInfo(s, false, false);
                for (Enumeration e=ToolBox.sortEnumeration(mppInfo.keys());e.hasMoreElements();) {
                    String key = (String)e.nextElement();
                    w.println(key+";"+mppInfo.get(key));
                }
            }
            w.flush();w.close();
        } catch (java.io.IOException io) {
            sLogger.error(io.getMessage(),io);
        }
    }
}
