package ttsolver;

import ifs.model.*;
import ifs.util.*;

import java.io.*;
import java.util.*;

import org.dom4j.*;
import org.dom4j.io.*;

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

/**
 * This class loads the input model from XML file.
 * <br><br>
 * Parameters:
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>General.Input</td><td>{@link String}</td><td>Input XML file</td></tr>
 * <tr><td>General.InitialAssignment</td><td>{@link Boolean}</td><td>Use the solution as initial assignment</td></tr>
 * <tr><td>General.UseDepartmentSpreadConstraints</td><td>{@link Boolean}</td><td>Use {@link DepartmentSpreadConstraint}</td></tr>
 * <tr><td>General.MPP</td><td>{@link Boolean}</td><td>Minimal perturbation problem (if true, initial assignments are set from solution)</td></tr>
 * <tr><td>General.ForcedPerturbances</td><td>{@link Integer}</td><td>For testing of MPP: number of input perturbations, i.e., classes with prohibited intial assignment</td></tr>
 * <tr><td>General.UseDistanceConstraints</td><td>{@link Boolean}</td><td>Consider distances between buildings</td></tr>
 * </table>
 * 
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class TimetableXMLLoader extends TimetableLoader {
    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableXMLLoader.class);

    private boolean iUseDepartmentSpreadConstraints = true;
    private int iForcedPerturbances = 0;
    
    private boolean iInitialAssignment;
    private boolean iMppAssignment;
    private boolean iUseDistanceConstraints;
    private File iInputFile;
    
    public TimetableXMLLoader(TimetableModel model) {
        super(model);
        iInputFile                 = new File(getModel().getProperties().getProperty("General.Input"));
        iForcedPerturbances        = getModel().getProperties().getPropertyInt("General.ForcedPerturbances",0);
        iMppAssignment             = getModel().getProperties().getPropertyBoolean("General.MPP",false);
        iInitialAssignment         = !iMppAssignment && getModel().getProperties().getPropertyBoolean("General.InitialAssignment",false);
        iUseDepartmentSpreadConstraints = getModel().getProperties().getPropertyBoolean("General.UseDepartmentSpreadConstraints",true);
        iUseDistanceConstraints = getModel().getProperties().getPropertyBoolean("General.UseDistanceConstraints", true);
    }

    public void load(PrintWriter out) throws Exception {
        sLogger.debug("Reading XML data from "+iInputFile);
        Progress.getInstance().setPhase("Reading "+iInputFile.getName()+" ...");
        
        Document document = (new SAXReader()).read(iInputFile);
        Element root = document.getRootElement();
        sLogger.debug("Root element: "+root.getName());
        if (!"llrt".equals(root.getName())) {
            sLogger.error("Given XML file is not large lecture room timetabling problem.");
            return;
        }
        
        if (root.element("input")!=null) root = root.element("input");
        
        sLogger.info("Going to load data "+root.attributeValue("semester")+root.attributeValue("year")+"v"+root.attributeValue("version")+" ...");
        getModel().getProperties().setProperty("Data.Semester", root.attributeValue("semester"));
        getModel().getProperties().setProperty("Data.Year", root.attributeValue("year"));
        getModel().getProperties().setProperty("Data.Version", root.attributeValue("version"));
        
        Progress.getInstance().setPhase("Creating rooms ...",root.element("rooms").elements("room").size());
        Hashtable roomElements = new Hashtable();
        Hashtable roomConstraints = new Hashtable();
        Hashtable sameLectures = new Hashtable();
        for (Iterator i=root.element("rooms").elementIterator("room");i.hasNext();) {
            Element roomEl = (Element)i.next();
            RoomConstraint constraint = new RoomConstraint(roomEl.attributeValue("id"), "r"+roomEl.attributeValue("id"), edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY,edu.purdue.smas.timetable.util.Constants.DAY_CODES.length, roomEl.attributeValue("capacity")+" seats");
            getModel().addConstraint(constraint);
            Progress.getInstance().incProgress();
            roomElements.put(roomEl.attributeValue("id"), roomEl);
            roomConstraints.put(roomEl.attributeValue("id"), constraint);
        }
        
        Progress.getInstance().setPhase("Creating variables ...",root.element("classes").elements("class").size());
        
        int nrClasses = root.element("classes").elements("class").size();
        
        Hashtable classElements = new Hashtable();
        Hashtable lectures = new Hashtable();
        Hashtable initialPlacements = new Hashtable();
        Hashtable instructorConstraints = new Hashtable();
        for (Iterator i1=root.element("classes").elementIterator("class");i1.hasNext();) {
            Element classEl = (Element)i1.next();
            
            classElements.put(classEl.attributeValue("id"),classEl);
            Element instructorEl = (Element)classEl.element("instructor");
            InstructorConstraint instructorConstraint = null;
            if (instructorEl!=null) {
                instructorConstraint = (InstructorConstraint)instructorConstraints.get(instructorEl.attributeValue("id"));
                if (instructorConstraint==null) {
                    instructorConstraint = new InstructorConstraint(instructorEl.attributeValue("id"), "i"+instructorEl.attributeValue("id"), edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY,edu.purdue.smas.timetable.util.Constants.DAY_CODES.length);
                    instructorConstraints.put(instructorEl.attributeValue("id"), instructorConstraint);
                    getModel().addConstraint(instructorConstraint);
                }
            }
            Vector roomLocations = new FastVector();
            Vector roomConstraintsThisClass = new FastVector();
            RoomLocation initialRoomLocation = null;
            int bestRoomPref = Integer.MAX_VALUE;
            for (Iterator i2=classEl.elementIterator("room");i2.hasNext();) {
                Element roomLocationEl = (Element)i2.next();
                Element roomEl = (Element)roomElements.get(roomLocationEl.attributeValue("id"));
                RoomConstraint roomConstraint = (RoomConstraint)roomConstraints.get(roomLocationEl.attributeValue("id"));
                roomConstraintsThisClass.add(roomConstraint);
                RoomLocation rl = new RoomLocation(roomConstraint.getResourceId(),roomConstraint.getName(),null,Integer.parseInt(roomLocationEl.attributeValue("pref")),Integer.parseInt(roomEl.attributeValue("capacity")));
                if (roomEl.attributeValue("location")!=null) {
                    String loc = roomEl.attributeValue("location");
                    int posX = Integer.parseInt(loc.substring(0,loc.indexOf(',')));
                    int posY = Integer.parseInt(loc.substring(loc.indexOf(',')+1));
                    rl.setCoordinates(posX, posY);
                }
                bestRoomPref = Math.min(bestRoomPref, Integer.parseInt(roomLocationEl.attributeValue("pref")));
                if ("true".equals(roomLocationEl.attributeValue("solution")))
                    initialRoomLocation = rl;
                roomLocations.add(rl);
            }
            if (roomLocations.isEmpty()) {
                sLogger.error("  ERROR: No room.");
                continue;
            }
            double bestTimePref = Double.MAX_VALUE;
            Vector timeLocations = new FastVector();
            TimeLocation initialTimeLocation = null;
            for (Iterator i2=classEl.elementIterator("time");i2.hasNext();) {
                Element timeLocationEl = (Element)i2.next();
                TimeLocation tl = new TimeLocation(
                    Integer.parseInt(timeLocationEl.attributeValue("days"),2),
                    Integer.parseInt(timeLocationEl.attributeValue("start")),
                    Integer.parseInt(timeLocationEl.attributeValue("length")),
                    Integer.parseInt(timeLocationEl.attributeValue("pref"))
                    );
                bestTimePref = Math.min(bestTimePref, Integer.parseInt(timeLocationEl.attributeValue("pref")));
                if ("true".equals(timeLocationEl.attributeValue("solution")))
                    initialTimeLocation = tl;
                timeLocations.add(tl);
            }
            if (timeLocations.isEmpty()) {
                sLogger.error("  ERROR: No time.");
                continue;
            }
            
            Lecture lecture = new Lecture(Long.parseLong(classEl.attributeValue("id")),  "c"+classEl.attributeValue("id"),  timeLocations,  bestTimePref,  (instructorEl==null?null:instructorEl.attributeValue("id")), roomLocations, bestRoomPref,  null, Integer.parseInt(classEl.attributeValue("expectedCapacity")),  (instructorEl==null?null:"i"+instructorEl.attributeValue("id")), "?");
            if (initialTimeLocation!=null && initialRoomLocation!=null) {
                initialPlacements.put(lecture, new Placement(null,initialTimeLocation, initialRoomLocation));
            } else {
                sLogger.debug("Lecture "+lecture+" has no initial placement (time="+initialTimeLocation+", room="+initialRoomLocation+")");
            }
            lectures.put(classEl.attributeValue("id"), lecture);
            lecture.setDepartment(classEl.attributeValue("department"));
            Vector sames = (Vector)sameLectures.get(classEl.attributeValue("course"));
            if (sames==null) {
                sames = new FastVector();
                sameLectures.put(classEl.attributeValue("course"), sames);
            }
            sames.addElement(lecture);

            getModel().setBestRoomPreference(getModel().getBestRoomPreference() + bestRoomPref);
            getModel().setBestTimePreference(getModel().getBestTimePreference() + bestTimePref);
            getModel().addVariable(lecture);
            if (instructorConstraint!=null) instructorConstraint.addVariable(lecture);
            
            for (Enumeration e2=roomConstraintsThisClass.elements(); e2.hasMoreElements();)
                ((Constraint)e2.nextElement()).addVariable(lecture);
            
            if (lecture.getInitialAssignment()!=null && getModel().conflictValues(lecture.getInitialAssignment()).isEmpty()) {
                lecture.assign(0,lecture.getInitialAssignment());
            }
            Progress.getInstance().incProgress();
        }
        
        Progress.getInstance().setPhase("Creating constraints ...",root.element("groupConstraints").elements("constraint").size());
        Hashtable grConstraintElements = new Hashtable();
        Hashtable groupConstraints = new Hashtable();
        for (Iterator i1=root.element("groupConstraints").elementIterator("constraint");i1.hasNext();) {
            Element grConstraintEl = (Element)i1.next();
            GroupConstraint gc = new GroupConstraint(Long.parseLong(grConstraintEl.attributeValue("id")), grConstraintEl.attributeValue("type"), grConstraintEl.attributeValue("pref"));
            gc.setGlobalPreference(getModel().getGlobalGroupConstraintPreferenceCounter());
            getModel().addConstraint(gc);
            for (Iterator i2=grConstraintEl.elementIterator("class");i2.hasNext();) {
                String classId = ((Element)i2.next()).attributeValue("id");
                gc.addVariable((Lecture)lectures.get(classId));
            }
            grConstraintElements.put(grConstraintEl.attributeValue("id"),grConstraintEl);
            groupConstraints.put(grConstraintEl.attributeValue("id"),gc);
            Progress.getInstance().incProgress();
        }
        
        Progress.getInstance().setPhase("Loading students ...",root.element("students").elements("student").size());
        Hashtable jenrlConstraints = new Hashtable();
        for (Iterator i1=root.element("students").elementIterator("student");i1.hasNext();) {
            Element studentEl = (Element)i1.next();
            Vector lecturesThisStudent = new Vector();
            for (Iterator i2=studentEl.elementIterator("class");i2.hasNext();) {
                String classId = ((Element)i2.next()).attributeValue("id");
                Lecture lecture = (Lecture)lectures.get(classId);
                lecture.addStudent(studentEl.attributeValue("id"));
                lecturesThisStudent.add(lecture);
            }
            for (Enumeration e1=lecturesThisStudent.elements();e1.hasMoreElements();) {
                Lecture lect1 = (Lecture)e1.nextElement();
                for (Enumeration e2=lecturesThisStudent.elements();e2.hasMoreElements();) {
                    Lecture lect2 = (Lecture)e2.nextElement();
                    if (lect1.getClassId()<lect2.getClassId()) {
                        JenrlConstraint jenrl =  (JenrlConstraint)jenrlConstraints.get(lect1.getClassId()+","+lect2.getClassId());
                        if (jenrl==null) {
                            jenrl = new JenrlConstraint(1, getModel().getViolatedStudentConflictsCounter());
                            getModel().addConstraint(jenrl);
                            jenrl.addVariable(lect1);
                            jenrl.addVariable(lect2);
                            jenrlConstraints.put(lect1.getClassId()+","+lect2.getClassId(), jenrl);
                        } else {
                            jenrl.incJenrl();
                        }
                    }
                }
            }
            Progress.getInstance().incProgress();
        }
        
        for (Enumeration e1=sameLectures.elements();e1.hasMoreElements();) {
            Vector sames = (Vector)e1.nextElement();
            for (Enumeration e2=sames.elements(); e2.hasMoreElements();) {
                Lecture lect = (Lecture)e2.nextElement();
                lect.setSameLectures(sames);
            }
        }

        if (iMppAssignment || iInitialAssignment) {
            Progress.getInstance().setPhase("Creating initial assignment ...",initialPlacements.size());
            for (Iterator ip=initialPlacements.entrySet().iterator();ip.hasNext();) {
                Map.Entry entry = (Map.Entry)ip.next();
                Lecture lecture = (Lecture)entry.getKey();
                Placement placement = (Placement)entry.getValue();
                placement.setVariable(lecture);
                if (iMppAssignment) lecture.setInitialAssignment(placement);
                Hashtable conflictConstraints = getModel().conflictConstraints(placement);
                if (conflictConstraints.isEmpty()) {
                    lecture.assign(0,placement);
                } else {
                    sLogger.warn("WARNING: Unable to assign "+lecture.getName()+" := "+placement.getName());
                    sLogger.debug("  Reason:");
                    for (Enumeration ex=conflictConstraints.keys();ex.hasMoreElements();) {
                        Constraint c = (Constraint)ex.nextElement();
                        Collection vals = (Collection)conflictConstraints.get(c);
                        for (Iterator i=vals.iterator();i.hasNext();) {
                            Value v = (Value) i.next();
                            sLogger.debug("    "+v.variable().getName()+" = "+v.getName());
                        }
                        sLogger.debug("    in constraint "+c);
                    }
                }
                Progress.getInstance().incProgress();
            }
        }

        if (iForcedPerturbances>0) {
            Progress.getInstance().setPhase("Forcing perturbances",iForcedPerturbances);
            for (int i=0;i<iForcedPerturbances;i++) {
                Progress.getInstance().setProgress(i);
                Variable var = null;
                do {
                    var = (Variable)ToolBox.random(getModel().variables());
                } while (var.getInitialAssignment()==null || var.values().size()<=1);
                var.removeInitialValue();
            }
        }

        if (iUseDepartmentSpreadConstraints) {
            Progress.getInstance().setPhase("Creating dept. spread constraints ...",getModel().variables().size());
            Hashtable depSpreadConstraints = new Hashtable();
            for (Enumeration e=getModel().variables().elements();e.hasMoreElements();) {
                Lecture lecture = (Lecture)e.nextElement();
                if (lecture.getDepartment()==null) continue;
                DepartmentSpreadConstraint deptConstr = (DepartmentSpreadConstraint)depSpreadConstraints.get(lecture.getDepartment());
                if (deptConstr==null) {
                    deptConstr = new DepartmentSpreadConstraint(getModel().getProperties(),lecture.getDepartment());
                    depSpreadConstraints.put(lecture.getDepartment(),deptConstr);
                    getModel().addConstraint(deptConstr);
                }
                deptConstr.addVariable(lecture);
                Progress.getInstance().incProgress();
            }
            for (Enumeration e=depSpreadConstraints.elements();e.hasMoreElements();)
                ((DepartmentSpreadConstraint)e.nextElement()).init();
        }
        Progress.getInstance().setPhase("Done",1);Progress.getInstance().incProgress();

        sLogger.debug("Model successfully loaded.");
        out.println("Model successfully loaded.");
    }    
}
