package ttsolver;

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

import java.io.*;
import java.util.*;
import java.text.*;
import org.dom4j.*;
import org.dom4j.io.*;
import java.sql.Statement;

import ttsolver.constraint.*;
import ttsolver.model.*;
import edu.purdue.smas.timetable.data.*;
import edu.purdue.smas.timetable.data.pattern.*;
import edu.purdue.smas.timetable.util.Constants;
import edu.purdue.smas.timetable.util.Database;

/**
 * This class saves the resultant solution in the XML format.
 * <br><br>
 * Parameters:
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>General.Output</td><td>{@link String}</td><td>Folder with the output solution in XML format (solution.xml)</td></tr>
 * <tr><td>General.ConvertIds</td><td>{@link Boolean}</td><td>If true, ids are converted (to be able to make input data public)</td></tr>
 * </table>
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class TimetableXMLSaver extends TimetableSaver {
    private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableXMLSaver.class);
    private static DecimalFormat[] sDF = {new DecimalFormat(""),new DecimalFormat("0"),new DecimalFormat("00"),new DecimalFormat("000"),new DecimalFormat("0000"),new DecimalFormat("00000"),new DecimalFormat("000000"),new DecimalFormat("0000000")};
    public static boolean DEBUG = false;
    
    private TimetableModel iModel = null;
    private Solution iSolution = null;
    private boolean iConvertIds = false;
    private File iOutputFolder = null;
    
    public TimetableXMLSaver(Solution solution) {
        super(solution);
        iConvertIds = getModel().getProperties().getPropertyBoolean("General.ConvertIds",false);
        iOutputFolder = new File(getModel().getProperties().getProperty("General.Output"));
        iSolution = solution; iModel = (TimetableModel)iSolution.getModel();
    }
    
    public void setSolution(Solution solution) { iSolution = solution; iModel = (TimetableModel)iSolution.getModel(); }
    
    private static String getId(Hashtable conversion, boolean convert, Object id) {
        String newId = (String)conversion.get(id);
        if (newId==null) {
            newId = String.valueOf(conversion.size()+1);
            conversion.put(id, newId);
        }
        return (convert?newId:id.toString());
    }
    
    public void save(PrintWriter out) throws Exception {
        iOutputFolder.mkdirs();
        File outFile = new File(iOutputFolder,"solution.xml");
        sLogger.debug("Writting XML data to:"+outFile);
        
        Document document = DocumentHelper.createDocument();
        document.addComment("Large Lecture Room Timetabling");
        
        if (!iModel.assignedVariables().isEmpty()) {
            StringBuffer comments = new StringBuffer("Solution Info:\n");
            Dictionary solutionInfo=(iSolution==null?iModel.getInfo():iSolution.getInfo());
            for (Enumeration e=edu.purdue.smas.timetable.util.ToolBox.sortEnumeration(solutionInfo.keys());e.hasMoreElements();) {
                String key = (String)e.nextElement();
                Object value = solutionInfo.get(key);
                comments.append("    "+key+": "+value+"\n");
            }
            document.addComment(comments.toString());
        }
        
        Element root = document.addElement("llrt");
        root.addAttribute("semester", iModel.getProperties().getProperty("Data.Semester"));
        root.addAttribute("year", iModel.getProperties().getProperty("Data.Year"));
        root.addAttribute("version", iModel.getProperties().getProperty("Data.Version"));
        root.addAttribute("created", String.valueOf(new Date()));
        root.addAttribute("nrDays", String.valueOf(Constants.DAY_CODES.length));
        root.addAttribute("halfHoursPerDay", String.valueOf(Constants.SLOTS_PER_DAY));
            
        Element instructorsEl = root.addElement("instructors").addAttribute("count", String.valueOf(iModel.getInstructorConstraints().size()));
        Element departmentsEl = root.addElement("departments");
        HashSet depts = new HashSet();
        
        Element roomsEl = root.addElement("rooms").addAttribute("count", String.valueOf(iModel.getRoomConstraints().size()));
        Hashtable roomConv = new Hashtable();
        Hashtable roomElements = new Hashtable();
        for (Enumeration e=iModel.getRoomConstraints().elements();e.hasMoreElements();) {
            RoomConstraint rc = (RoomConstraint)e.nextElement();
            Element roomEl = roomsEl.addElement("room").addAttribute("id", getId(roomConv, iConvertIds, rc.getResourceId()));
            roomElements.put(getId(roomConv, iConvertIds, rc.getResourceId()), roomEl);
        }
        
        Element classesEl = root.addElement("classes").addAttribute("count", String.valueOf(iModel.variables().size()));
        Hashtable classConv = new Hashtable();
        Hashtable courseConv = new Hashtable();
        Hashtable deptConv = new Hashtable();
        Hashtable instructorConv = new Hashtable();
        HashSet filledRooms = new HashSet();
        iModel.variables().addAll(iModel.ignoredClasses());
        for (Enumeration e1=iModel.variables().elements();e1.hasMoreElements();) {
            Lecture lecture = (Lecture)e1.nextElement();
            Placement placement = (Placement)lecture.getAssignment();
            Element classEl = classesEl.addElement("class").addAttribute("id", getId(classConv, iConvertIds, String.valueOf(lecture.getClassId())));
            classEl.addAttribute("course", getId(courseConv, true, lecture.sameLectures()));
            classEl.addAttribute("expectedCapacity", String.valueOf(lecture.countStudents()));
            if (lecture.getDepartment()!=null) {
                classEl.addAttribute("department", getId(deptConv, iConvertIds, lecture.getDepartment()));
                depts.add(lecture.getDepartment());
            }
            if (lecture.getInstructorConstraint()!=null) {
                Element instrEl = classEl.addElement("instructor").addAttribute("id", getId(instructorConv, iConvertIds, lecture.getInstructorConstraint().getResourceId()));
                if (placement!=null) instrEl.addAttribute("solution","true");
            }
            for (Enumeration e2=lecture.roomLocations().elements();e2.hasMoreElements();) {
                RoomLocation rl = (RoomLocation)e2.nextElement();
                Element roomLocationEl = (Element)classEl.addElement("room");
                roomLocationEl.addAttribute("id",  getId(roomConv, iConvertIds, rl.getId()));
                roomLocationEl.addAttribute("pref", String.valueOf(rl.getPreference()));
                if (placement!=null && placement.getRoomLocation().equals(rl)) roomLocationEl.addAttribute("solution", "true");
                if (filledRooms.add(rl)) {
                    Element roomEl = (Element)roomElements.get(rl.getId());
                    roomEl.addAttribute("capacity", String.valueOf(rl.getRoomSize()));
                    if (rl.getPosX()>0 || rl.getPosY()>0)
                        roomEl.addAttribute("location", rl.getPosX()+","+rl.getPosY());
                }
            }
            for (Enumeration e2=lecture.timeLocations().elements();e2.hasMoreElements();) {
                TimeLocation tl = (TimeLocation)e2.nextElement();
                Element timeLocationEl = (Element)classEl.addElement("time");
                timeLocationEl.addAttribute("days", sDF[7].format(Long.parseLong(Integer.toBinaryString(tl.getDayCode()))));
                timeLocationEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
                timeLocationEl.addAttribute("length", String.valueOf(tl.getLength()));
                timeLocationEl.addAttribute("pref", String.valueOf((int)tl.getNormalizedPreference()));
                if (placement!=null && placement.getTimeLocation().equals(tl)) timeLocationEl.addAttribute("solution", "true");
            }
        }
        classesEl.addAttribute("courses",String.valueOf(courseConv.size()));
        
        Element grConstraintsEl = root.addElement("groupConstraints").addAttribute("count", String.valueOf(iModel.getGroupConstraints().size()));
        Hashtable grConv = new Hashtable();
        for (Enumeration e1=iModel.getGroupConstraints().elements();e1.hasMoreElements();) {
            ttsolver.constraint.GroupConstraint gc = (ttsolver.constraint.GroupConstraint)e1.nextElement();
            Element grEl = grConstraintsEl.addElement("constraint").addAttribute("id", getId(grConv, iConvertIds, String.valueOf(gc.getId())));
            grEl.addAttribute("type", gc.getTypeStr());
            grEl.addAttribute("pref", gc.getPrologPreference());
            for (Enumeration e2=gc.variables().elements(); e2.hasMoreElements();) {
                Lecture l = (Lecture)e2.nextElement();
                grEl.addElement("class").addAttribute("id",getId(classConv, iConvertIds, String.valueOf(l.getId())));
            }
        }
        
        Hashtable students = new Hashtable();
        for (Enumeration e1=iModel.variables().elements();e1.hasMoreElements();) {
            Lecture lecture = (Lecture)e1.nextElement();
            for (Enumeration e2=lecture.students().elements();e2.hasMoreElements();) {
                String student = (String)e2.nextElement();
                Vector enrls = (Vector)students.get(student);
                if (enrls==null) {
                    enrls = new Vector();
                    students.put(student, enrls);
                }
                enrls.add(getId(classConv, iConvertIds, String.valueOf(lecture.getId())));
            }
        }
        
        Element studentsEl = root.addElement("students").addAttribute("count", String.valueOf(students.size()));
        Hashtable studentConv = new Hashtable();
        for (Enumeration e1=ToolBox.sortEnumeration(students.keys(), new IdComparator());e1.hasMoreElements();) {
            String student = (String)e1.nextElement();
            Element stEl = studentsEl.addElement("student").addAttribute("id", getId(studentConv, iConvertIds, student));
            Vector lectures = (Vector)students.get(student);
            Collections.sort(lectures);
            for (Enumeration e2=lectures.elements();e2.hasMoreElements();) {
                stEl.addElement("class").addAttribute("id", (String)e2.nextElement());
            }
        }
        
        departmentsEl.addAttribute("count", String.valueOf(depts.size()));

        FileOutputStream fos = new FileOutputStream(outFile);
        (new XMLWriter(fos,OutputFormat.createPrettyPrint())).write(document);
        fos.flush();fos.close();
    }
    
    private static class IdComparator implements Comparator {
        public int compare(Object a, Object b) {
            return Long.valueOf((String)a).compareTo(Long.valueOf((String)b));
        }
    }
}
