/*
 * ECatalog is a database front-end, with two main features:
 * 1. Use of preferences
 *  A preference-based approach, where the user is allowed to define the importance of each criterion.
 *  Then the items are ranked accordingly to his criteria.
 * 2. Trade-off analysis
 *  A cooperative database approach, where the system "argues" with the user about his criteria.
 *  When there are no matching items, the system explains the minimal conflicting set and
 *  give some possible strong and weak relaxations about his criteria.
 * This package also containts the software and the set-up details used for our User Study,
 * comparing the use or not of the two previous features mentioned above.
 *
 * Copyright (C) 2006 David Portabella Clotet, Artificial Intelligence Laboratory, EPFL
 * 
 * This file is part of ecatalog-1.0.zip
 * 
 * ECatalog is free software and a free user study set-up;
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * ECatalog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ECatalog; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * @version 1.0
 * @author David Portabella
 * To contact the author:
 * email: david@portabella.name and david.portabella@epfl.ch
 * 
 * More information about ECatalog:
 *  http://sourceforge.net/projects/ecatalog/
 *  http://icwww.epfl.ch/~portabel/ecatalogs/
 */

package ecatalog;

import utils.Log;

import ecatalog.gui.visualization.SelectedItem;
import ecatalog.gui.criteriaSelection.ConstraintGui;
import ecatalog.gui.criteriaSelection.AttributeConstraintGui;
import ecatalog.db.*;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import ecatalog.jaxb.ecatalogConfig.ECatalogConfigType;

import java.io.*;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.util.Vector;

import dpc.utils.TextUtil;
import dpc.utils.OutputStreamStringBuffer;


//TODO: catch Exceptions and printStackTrace in the logFile with a custom <error> attribute

public class ECatalogLog extends utils.Log {
    ECatalog ecatalog;

    String getItemDescription(Item item) {
	try {
	    StringBuffer text = new StringBuffer();
	    text.append("<item>\n");
	    for (Attribute at : ecatalog.getDatabase().getAttributes()) {
		int valueIndex = at.getValueIndex(item);
		String value = at.getDbValueString(valueIndex, false);
		text.append("<attribute " + valuePairsToString(new String[][] {{"id", at.getId()}, {"value", value}}) + "/>\n");
	    }
	    text.append("</item>");
	    return text.toString();
	} catch (SQLException e) { 
	    e.printStackTrace();
	    return "<item>SQLException</item>";
	}
    }


    public ECatalogLog(ECatalog ecatalog, String fileName) throws IOException {
	this(ecatalog, true, new PrintWriter(new FileWriter(fileName, false), true));
    }

    public ECatalogLog(ECatalog ecatalog, boolean printHeader, PrintWriter out) {
	super("ecatalogLog",printHeader, out);
	this.ecatalog = ecatalog;
    }

    public void start() {
	/*
	try {
	    JAXBContext jc = JAXBContext.newInstance("ecatalog.jaxb.ecatalogConfig");
	    Marshaller m = jc.createMarshaller();
	    m.setProperty("jaxb.formatted.output", Boolean.TRUE);
	    m.setProperty("jaxb.fragment", Boolean.TRUE);
	    m.marshal(new JAXBElement(new QName("config"), ECatalogConfigType.class, ecatalog.getConfig()), out);
	} catch (javax.xml.bind.JAXBException e) {
	    error(e);
	}
	*/
	
	out.println("<start configTitle=" + escape(ecatalog.getConfig().getTitle()) + " " + getTimeTags() + "/>");
    }
    
    public void close() {
	closeIc();
	super.close();
    }

    boolean openedIc = false;
    long currentIc = 0;
    public void nextIc() {
	if (openedIc)
	    out.println("</ic>");
	else
	    openedIc = true;
	out.println("<ic id=\"" + currentIc + "\">");
	currentIc++;
	out.flush();
    }

    public void closeIc() {
	if (openedIc) {
	    out.println("</ic>");
	    openedIc = false;
	}
    }

    public void genericIc(String icOp, String[][] valuePairs, String attributeBody) {
	genericInfo("ic", merge(new String[][] {{"icOp", icOp}}, valuePairs), attributeBody);
    }

    public void genericIc(String icOp, String[][] valuePairs) {
	genericIc(icOp, valuePairs, null);
    }

    public void genericIc(String icOp) {
	genericIc(icOp, null, null);
    }


    /* updateConstraint 
     * op = {ADD, REMOVE, MODIFY}
     */
    public void updateConstraint(AttributeConstraintGui constraintGui, String op, int valueIndex) {
	updateConstraint(constraintGui, op, valueIndex, null, null, -1);
    }

    /* updateConstraint 
     * op = {ADD, REMOVE, MODIFY}
     * mvOp, for MultipleValuesConstraintGui, mvOp = {ADD, REMOVE, REPLACE}
     * mvOp2 in case mvOp=REPLACE, mvOp2 = {NEW, CURRENT, INLIST}
     */
    public void updateConstraint(AttributeConstraintGui constraintGui, String op, int valueIndex, String mvOp, String mvOp2, int mvOpIndex) {
	Attribute at = constraintGui.getAttribute();
	String value = (valueIndex == -1) ? null : at.getDbValueString(valueIndex, false);
	String mvOpIndexString = (mvOpIndex == -1) ? null : String.valueOf(mvOpIndex);
	genericIc("updateConstraint", 
		  new String[][] {{"type", constraintGui.getConstraint().getConstraintTypeDescription()},
				  {"attributeId", at.getId()},
				  {"op", op},
				  {"value", value}, 
				  {"mvOp", mvOp},
				  {"mvOp2", mvOp2}, 
				  {"mvOpIndex", mvOpIndexString}});
    }

    /* mvFilterChanged(constraint, op, value)
     * op = {TEXT_FILTER, FILTER_INCOMPATIBLE_VALUES, SORT_DIRECTION}
     */
    public void comboboxFilterChanged(AttributeConstraintGui constraintGui, String op, String value) {
	Attribute at = constraintGui.getAttribute();
	genericIc("comboboxFilterChanged",
		  new String[][] {{"attributeId", at.getId()},
				  {"op", op},
				  {"value", value}});
    }


    public void changeConstraintType(ConstraintGui newConstraintGui) {
	Attribute at = ((AttributeConstraintGui)newConstraintGui).getAttribute();
	genericIc("changeConstraintType",
		  new String[][] {{"attributeId", at.getId()},
				  {"newConstraintType", newConstraintGui.getConstraint().getConstraintTypeDescription()}});
    }

    public void changeConstraintWeigth(ConstraintGui constraintGui, Weight weight) {
	Attribute at = ((AttributeConstraintGui)constraintGui).getAttribute();
	genericIc("changeConstraintWeigth",
		  new String[][] {{"attributeId", at.getId()},
				  {"weight", String.valueOf(weight.getWeightValue())}});
    }

    public void relaxationSelected(int minimalUnsolvableSubproblemIndex, int relaxationSetIndex, DBAnalysis.ConstraintRelaxationSet relaxationSet) {
	String[][] valuePairs = new String[][] {{"minimalUnsolvableSubproblemIndex", String.valueOf(minimalUnsolvableSubproblemIndex)},
						{"relaxationSetIndex", String.valueOf(relaxationSetIndex)}};
	String text = getRelaxationDescription(relaxationSet);
	genericIc("relaxationSelected", valuePairs, text);
    }

    public void itemSelected(SelectedItem selectedItem) {
	itemSelected(selectedItem, "itemSelected", null, null);
    }

    /* This function is not call by the ECatalog package */
    public void itemActionSelected(SelectedItem selectedItem, String actionId, String[][] extraValuePairs) {
	itemSelected(selectedItem, "itemActionSelected", actionId, extraValuePairs);
    }

    public void itemSelected(SelectedItem selectedItem, String icOp, String actionId, String[][] extraValuePairs) {
	try {
	    boolean compatibleItem = ecatalog.getDatabase().isItemCompatible(selectedItem.item);
	    int currentNbrSolutions = ecatalog.getDatabase().getCurrentNbrSolutions();

	    String[][] valuePairs = new String[][] {{"actionId", actionId},
						    {"compatibleItem", String.valueOf(compatibleItem)},
						    {"tableItemIndex", String.valueOf(selectedItem.tableIndex)},
						    {"window", String.valueOf(selectedItem.window)},
						    {"currentNbrSolutions", String.valueOf(currentNbrSolutions)}};

	    valuePairs = merge(valuePairs, extraValuePairs);
	    String itemDescription = getItemDescription(selectedItem.item);
	    genericIc(icOp, valuePairs, itemDescription);
	} catch (SQLException e) { 
	    error(e);
	}
    }


    public void considerItem(SelectedItem selectedItem, boolean alreadyPresentInConsiderWindow) {
	try {
	    boolean compatibleItem = ecatalog.getDatabase().isItemCompatible(selectedItem.item);

	    String[][] valuePairs = new String[][] {{"compatibleItem", String.valueOf(compatibleItem)},
						    {"tableItemIndex", String.valueOf(selectedItem.tableIndex)},
						    {"window", String.valueOf(selectedItem.window)},
						    {"alreadyPresentInConsiderWindow", String.valueOf(alreadyPresentInConsiderWindow)}};

	    String itemDescription = getItemDescription(selectedItem.item);
	    genericIc("considerItem", valuePairs, itemDescription);
	} catch (SQLException e) { 
	    error(e);
	}
    }

    public void removeConsiderItem(SelectedItem selectedItem) {
	try {
	    boolean compatibleItem = ecatalog.getDatabase().isItemCompatible(selectedItem.item);

	    String[][] valuePairs = new String[][] {{"compatibleItem", String.valueOf(compatibleItem)},
						    {"tableItemIndex", String.valueOf(selectedItem.tableIndex)},
						    {"window", String.valueOf(selectedItem.window)}};

	    String itemDescription = getItemDescription(selectedItem.item);
	    genericIc("removeConsiderItem", valuePairs, itemDescription);
	} catch (SQLException e) { 
	    error(e);
	}
    }

    public void popupMenu(ConstraintGui constraintGui) {
	popupMenu(constraintGui, -1, false);
    }

    public void popupMenu(ConstraintGui constraintGui, int comboBoxIndex, boolean adding) {
	Attribute at = ((AttributeConstraintGui)constraintGui).getAttribute();
	genericIc("popupMenu", 
		  new String[][] {{"attributeId", at.getId()},
				  {"comboBoxIndex", (comboBoxIndex == -1) ? null : String.valueOf(comboBoxIndex)},
				  {"adding", (comboBoxIndex == -1) ? null : String.valueOf(adding)}});
    }

    public void writeState() {
	int currentNbrSolutions = ecatalog.getDatabase().getCurrentNbrSolutions();
	int currentNbrItemsInConsideration = ecatalog.gui.visualizationGui.getCurrentNbrItemsInConsideration();

	String[][] valuePairs = new String[][] {{"currentNbrSolutions", String.valueOf(currentNbrSolutions)},
						{"currentNbrItemsInConsideration", String.valueOf(currentNbrItemsInConsideration)}};
	StringBuffer body = new StringBuffer();
	body.append("<constraints>\n");
	for (Constraint c : ecatalog.getDatabase().getConstraints())
	    body.append(getConstraintDescription(c, "constraint") + "\n");
	body.append("</constraints>\n");

	genericInfo("state", valuePairs, body.toString());
    }

    public void possibleRelaxationsAnalysis(DBAnalysis.PossibleRelaxations possibleRelaxations, long duration) {
	StringBuffer text = new StringBuffer();
	text.append("<possibleRelaxations " 
		    + valuePairsToString(new String[][] {{"duration", String.valueOf(duration)},
							 {"text", possibleRelaxations.text}})
		    + ">\n");

	for (DBAnalysis.ConstraintRelaxationSet relaxationSet : possibleRelaxations.constraintRelaxationSetVector)
	    text.append(getRelaxationDescription(relaxationSet) + "\n");
	text.append("</possibleRelaxations>\n");
	out.print(text);
    }

    public void minimalUnsolvableSubproblemAnalysis(DBAnalysis.MinimalUnsolvableSubproblem minimalUnsolvableSubproblem, long duration) {
	StringBuffer text = new StringBuffer();
	text.append("<minimalUnsolvableSubproblem " 
		    + valuePairsToString(new String[][] {{"duration", String.valueOf(duration)},
							 {"text", minimalUnsolvableSubproblem.text}})
		    + ">\n");

	for (DBAnalysis.Problem problem : minimalUnsolvableSubproblem.problems) {
	    text.append("<problem>\n");

	    //the problem itself
	    text.append("<problem>\n");
	    for (int i = 0; i < problem.problem.length; i++) {
		if (problem.problem[i]) {
		    Constraint constraint = minimalUnsolvableSubproblem.constraints.get(i);
		    text.append(getConstraintDescription(constraint, "constraint") + "\n");
		}
	    }
	    text.append("</problem>\n");

	    //and the relaxations
	    out.print(text.toString());   //possibleRelaxationsAnalysis writes directly to the file, so first flush current info. TODO minor: make this nicer
	    text = new StringBuffer();
	    possibleRelaxationsAnalysis(problem.possibleRelaxations, -1);

	    text.append("</problem>\n");
	}
	text.append("</minimalUnsolvableSubproblem>\n");
	out.print(text.toString()); 
    }

    String getConstraintDescription(Constraint constraint, String attributeName) {
	Attribute at = ((AttributeConstraint)constraint).getAttribute();
	String[][] valuePairs = new String[][] {{"type", constraint.getConstraintTypeDescription()},
						{"attribute", at.getId()},
						{"detailsDefined", String.valueOf(constraint.areDetailsDefined())}};
	StringBuffer body = new StringBuffer();

	if (constraint.areDetailsDefined()) {
	    if (constraint instanceof SimpleAttributeConstraint) {
		String value = at.getDbValueString(((SimpleAttributeConstraint)constraint).getValueIndex(), false);
		valuePairs = merge(valuePairs, new String[][] {{"value", value}});
	    } else if (constraint instanceof MultipleValuesConstraint) {
		for (int i = 0; i < ((MultipleValuesConstraint)constraint).getNbrOfvalues(); i++) {
		    String value = at.getDbValueString(((MultipleValuesConstraint)constraint).getValueIndexAt(i), false);
		    body.append("<value " + valuePairsToString(new String[][] {{"id", value}}) + "/>");
		}
	    } else {
		System.err.println("ECatalogLog: constraint not recognized!: " + constraint.toString());
	    }
	}

	StringBuffer text = new StringBuffer();
	text.append("<" + attributeName + " " + valuePairsToString(valuePairs));
	if (body.length() == 0)
	    text.append("/>");
	else
	    text.append(">\n" + body + "\n</" + attributeName + ">\n");

	return text.toString();
    }

    public String getRelaxationDescription(DBAnalysis.ConstraintRelaxationSet relaxationSet) {
	StringBuffer text = new StringBuffer();
	text.append("<relaxationSet>\n");

	for (DBAnalysis.ConstraintRelaxation cr : relaxationSet.constraintRelaxations) {
	    text.append("<constraintRelaxation>\n" +
			getConstraintDescription(cr.oldConstraint, "oldConstraint") + "\n" +
			getConstraintDescription(cr.relaxedConstraint, "relaxedConstraint") + "\n" +
			"</constraintRelaxation>\n");
	}
	text.append("</relaxationSet>");

	return text.toString();
    }

    /*
    public void itemActionSelected(Item item, int tableItemIndex) {
    
    }
    */
}


