/*
 * 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.gui.criteriaSelection;

import ecatalog.ECatalog;
import ecatalog.gui.MainGui;
import ecatalog.ECatalog;
import ecatalog.db.*;

import org.w3c.dom.Element;
import javax.xml.bind.JAXBException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import ecatalog.jaxb.simpleAttributeConstraintConfig.SimpleAttributeConstraintConfigType;

import utils.AnotherLinkButton;
import info.clearthought.layout.TableLayout;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

import java.util.LinkedList;
import java.util.Iterator;
import java.util.LinkedHashMap;

import java.sql.SQLException;
import dpc.utils.DebugUtil;

class SimpleAttributeConstraintGui extends AttributeConstraintGui implements ItemComboBoxModel {
    Attribute attribute;
    ConstraintType type;
    NumberAttributePenaltyFunction penaltyFunction;
    SimpleAttributeConstraint constraint;

    enum ConstraintType {
	EQUAL("="), LESS_THAN_OR_EQUAL("<="), MORE_THAN_OR_EQUAL(">=");
	String label;
	ConstraintType(String label) { this.label = label; }
	public String getLabel() { return label; }
    };

    ComboBoxFilterProperties filterProperties;
    int[] sortedIndex;

    ItemComboBox itemComboBox;
    JButton clearButton;
   
    public void configure(Element configElement, Attribute attribute) throws Exception {
	Unmarshaller u = JAXBContext.newInstance("ecatalog.jaxb.simpleAttributeConstraintConfig").createUnmarshaller();
        SimpleAttributeConstraintConfigType config = (SimpleAttributeConstraintConfigType) ((JAXBElement<?>)u.unmarshal(configElement)).getValue();
	ConstraintType type = ConstraintType.valueOf(config.getType());

	NumberAttributePenaltyFunction penaltyFunction = null;
	if (config.getPenaltyClassName() != null) {
	    penaltyFunction = (NumberAttributePenaltyFunction) Class.forName(config.getPenaltyClassName()).newInstance();
	    penaltyFunction.configure((NumberAttribute)attribute, (Element)config.getAny());
	}
	configure(type, attribute, penaltyFunction);
    }

    public void configure(ConstraintType type, Attribute attribute, NumberAttributePenaltyFunction penaltyFunction) {
	this.attribute = attribute;
	this.type = type;
	this.penaltyFunction = penaltyFunction;
    }

    public void attach(MainGui mainGui, Database db, ECatalog ecatalog, WeightSelector weightSelector) {
	super.attach(mainGui, db, ecatalog, weightSelector);

	if (type == ConstraintType.EQUAL)
	    constraint = new EqualConstraint(attribute, db, ecatalog.getDefaultWeight(), penaltyFunction);
	else if (type == ConstraintType.LESS_THAN_OR_EQUAL)
	    constraint = new LessThanOrEqualConstraint(attribute, db, ecatalog.getDefaultWeight(), penaltyFunction);
	else if (type == ConstraintType.MORE_THAN_OR_EQUAL)
	    constraint = new MoreThanOrEqualConstraint(attribute, db, ecatalog.getDefaultWeight(), penaltyFunction);
	else {
	    ECatalog.error(new Error("Unexpected simple attribute constraint type"));
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}

	db.addConstraint(constraint);
	
	constraint.update();  //TODO: Fix. Swing calls the JComboBox model and rendered before everything is initialise.
	initGui();
    }

    public void setEnabled(boolean enabled) {
	itemComboBox.setEnabled(enabled);
	if (weightSelector != null)
	    weightSelector.setEnabled(enabled);
	clearButton.setEnabled((enabled && constraint.areDetailsDefined() == true));
    }

    public Constraint getConstraint() {
	return constraint;
    }

    public Attribute getAttribute() {
	return attribute;
    }

    public String getConstraintTypeLabel() {
	return type.getLabel();
    }

    public void reset() {
	constraint.removeDetails();

	//constraint.setWeightToDefault();
	constraint.setWeight(ecatalog.getDefaultWeight());
    }

    public void detach() {
	db.removeConstraint(constraint);
    }

    public void requestFocus() {
	itemComboBox.requestFocus();
    }


    public void update() throws SQLException {
	if (weightSelector != null)
	    weightSelector.update();

	sortedIndex = null;
	itemComboBox.update();
	clearButton.setEnabled(constraint.areDetailsDefined() == true);
    }

    public void valueClear() {
	if (ecatalog.getLog() != null)
	    ecatalog.getLog().updateConstraint(this, "REMOVE", -1);

	constraint.removeDetails();
	ecatalog.update();
    }

    void computeSortedIndex() {
	if (sortedIndex == null)
	    sortedIndex = Util.getSortedIndex(constraint, filterProperties.textFilter, filterProperties.filterIncompatibleValues, filterProperties.sortDirection);
    }

    public void selectedItemChanged(Object selectedItem) {
	if (selectedItem == null) {
	    constraint.removeDetails();
	    if (ecatalog.getLog() != null)
		ecatalog.getLog().updateConstraint(this, "REMOVE", -1);
	} else {
	    int valueIndex = (int)(Integer)selectedItem;
	    if (ecatalog.getLog() != null)
		ecatalog.getLog().updateConstraint(this, constraint.areDetailsDefined() ? "MODIFY" : "ADD", valueIndex);
	    constraint.setValueIndex(valueIndex);
	}
	ecatalog.update();
    }

    public int getNbrComboBoxItems() {
	if (filterProperties == null)
	    return attribute.getNbrDistinctValues();

	computeSortedIndex();
	return sortedIndex.length;
    }

    public void popupMenuWillBecomeVisible() {
	if (ecatalog.getLog() != null)
	    ecatalog.getLog().popupMenu(this);
    }

    public ComboBoxItem getComboBoxItem(int popupItemIndex, boolean computeText, boolean computeColor, boolean computeToolTipText) {
	try {
	    if (!ecatalog.getConfig().isContextualToolTipDisplayed())
		computeToolTipText = false;

	    int valueIndex = -1;
	    int nbrItems = -1;
	    Color color = null;
	    String toolTipText = (!computeToolTipText) ? "" : (attribute.getConfig().getHelpPrompt() + ".\n");

	    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
		computeText = false;
	    }

	    if (constraint.areDetailsDefined() == false && popupItemIndex == -1) {                                         //example value
		if (!ecatalog.getConfig().isExampleDisplayed()) {
		    valueIndex = -1;
		} else {
		    if (db.getCurrentNbrSolutions() == 0) {
			valueIndex = -1;
			toolTipText += "You can still add some more criteria here.\n";
			if (ecatalog.getConfig().isProspectiveAnalysisDisplayed())
			    toolTipText += "You will see the TRADEOFFS in the analysis window.";
		    } else {
			valueIndex = attribute.getCurrentExampleValueIndex();         //example value
			if (computeText || computeToolTipText && ecatalog.getConfig().isProspectiveAnalysisDisplayed())
			    nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueSelected(valueIndex);
			if (computeColor)
			    color = (!ecatalog.getConfig().isProspectiveAnalysisDisplayed() || attribute.getCurrentNbrCompatibleDistinctValues() != 1) 
				? MainGui.colorExample : MainGui.colorInferred;
			
			if (computeToolTipText) {
			    String withConstraintString = constraint.getWithConstraintString(attribute.getCurrentExampleValueIndex());
			    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
				toolTipText += "You would select this criteria (" + withConstraintString + ").";
			    } else {
				nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueSelected(attribute.getCurrentExampleValueIndex());
				toolTipText += "If you select this criteria (" + withConstraintString + "), " +
				    "there would be " + nbrItems + " compatible items.";
			    }
			}
		    }
		}
	    } else if (popupItemIndex == -1) {
		valueIndex = constraint.getValueIndex();       //already user selected value
		nbrItems = -1;
		color = (db.getCurrentNbrSolutions() > 0) ? MainGui.colorConstraintGiven : MainGui.colorOverconstrained;
		
		if (computeToolTipText) 
		    toolTipText += "You have defined the criteria \"" + constraint.getWithConstraintString() + "\".\n";
		if (db.getCurrentNbrSolutions() == 0) {
		    toolTipText += "With all the selected criteria, there are not compatible items.\n";
		    if (ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
			toolTipText +=
			    "You can see the current tradeoffs in the analysis windows.";
		    } else {
			toolTipText += "With all the selected criteria, there are " + db.getCurrentNbrSolutions() + " compatible items.\n";
			if (ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
			    toolTipText +=
				"If you remove this criteria (" + attribute.getValueString(valueIndex) + "), " +
				"there would be " + constraint.getCurrentNbrCompatibleItemsExcludingThisConstraint() + " compatible items.";
			}
		    }
		}
	    } else {
		if (filterProperties == null) {
		    valueIndex = popupItemIndex;
		} else {
		    computeSortedIndex();
		    valueIndex = sortedIndex[popupItemIndex];                               //popup list value
		}

		if (computeText || computeColor || computeToolTipText) {
		    if (constraint.areDetailsDefined() && valueIndex == constraint.getValueIndex()) {
			nbrItems = -1;
			color = MainGui.colorConstraintGiven;
			toolTipText = "You have already specified this value (" + attribute.getValueString(valueIndex)  + ").\n" +
			    "By selecting this, you are not modifying your criteria.";
		    } else {
			String actionText = (constraint.areDetailsDefined()) ? "change to" : "select";
			String widthConstraintString = constraint.getWithConstraintString(valueIndex);

			if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
			    toolTipText = "You would " + actionText + " this criteria (" + widthConstraintString + "),\n";
			} else {
			    nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueSelected(valueIndex);
			    color = (nbrItems > 0) ? MainGui.colorExample : MainGui.colorOverconstrained;

			    if (nbrItems > 0)
				toolTipText += "there would be " + nbrItems + " compatible items.";
			    else
				toolTipText += "there won't be compatible items.\n" +
				    "You will see the TRADEOFFS in the analysis window";
			}
		    }
		}
	    }

	    String text = (valueIndex == -1) ? null : attribute.getValueString(valueIndex);
	    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
		if (computeColor && popupItemIndex != -1) 
		    color = MainGui.colorConstraintGiven;
	    } else {
		if (computeText) {
		    if (nbrItems != -1)
			text += " (" + nbrItems + ")";
		}
	    }
	    if (computeToolTipText == false)
		toolTipText = null;

	    return new ComboBoxItem(valueIndex, text, color, toolTipText);
	} catch (SQLException e) { 
	    ECatalog.error(e); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}
    }

    void initGui() {
	mainPanel = new JPanel();
	TableLayout layout = new TableLayout(new double[][] {{TableLayout.FILL, 2, TableLayout.PREFERRED},{TableLayout.PREFERRED}});
	mainPanel.setLayout(layout);

	if (attribute instanceof NumberAttribute) {
	    filterProperties = null;
	} else {
	    filterProperties = (ecatalog.getConfig().isProspectiveAnalysisDisplayed())
		? new ComboBoxFilterProperties(true, true, true, true)
		: new ComboBoxFilterProperties(true, false, false, false);
	}

	boolean setMinimumPreferredWidth = (ecatalog.getConfig().getCriteriaPanelWidth() != null);

	itemComboBox = new ItemComboBox();
	itemComboBox.init(this, filterProperties, setMinimumPreferredWidth);
	mainPanel.add(itemComboBox.getComponent(), "0, 0");

	clearButton = new AnotherLinkButton("clear");
	clearButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { valueClear(); } });
	mainPanel.add(clearButton, "2, 0, l, t");
    }

    public void textFilterChanged(String text) {
	filterProperties.textFilter = text;
	sortedIndex = null;

	if (ecatalog.getLog() != null)
	    ecatalog.getLog().comboboxFilterChanged(this, "TEXT_FILTER", text);
    }

    public void filterIncompatibleValuesChanged(boolean filter) {
	filterProperties.filterIncompatibleValues = filter;
	sortedIndex = null;

	if (ecatalog.getLog() != null)
	    ecatalog.getLog().comboboxFilterChanged(this, "FILTER_INCOMPATIBLE_VALUES", String.valueOf(filter));
    }

    public void sortDirectionChanged(int direction) {
	filterProperties.sortDirection = direction;
	sortedIndex = null;

	if (ecatalog.getLog() != null)
	    ecatalog.getLog().comboboxFilterChanged(this, "SORT_DIRECTION", (filterProperties.sortDirection == 1) ? "ASC" : "DES");
    }
}


