/*
 * 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.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.multipleValuesConstraintConfig.MultipleValuesConstraintConfigType;

import utils.HeaderedComboBox;
import utils.AnotherLinkButton;

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

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

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

class MultipleValuesConstraintGui extends AttributeConstraintGui implements MultipleSelectionListModel {
    MultipleValuesConstraint.Type type;
    MultipleValuesConstraint constraint;
    Attribute attribute;
    String[] label = {"none of", "one of"};

    MultipleSelectionList multipleSelectionList;
    JButton clearButton;

    ComboBoxFilterProperties filterProperties;
    int[] sortedIndex;

    boolean systemChangingFields = false;
   
    public void configure(Element configElement, Attribute attribute) throws javax.xml.bind.JAXBException {
	Unmarshaller u = JAXBContext.newInstance("ecatalog.jaxb.multipleValuesConstraintConfig").createUnmarshaller();
        MultipleValuesConstraintConfigType config = (MultipleValuesConstraintConfigType) ((JAXBElement<?>)u.unmarshal(configElement)).getValue();

	MultipleValuesConstraint.Type type = MultipleValuesConstraint.Type.valueOf(config.getType());
	configure(type, attribute);
    }

    public void configure(MultipleValuesConstraint.Type type, Attribute attribute) {
	this.attribute = attribute;
	this.type = type;
    }
    
    public void attach(MainGui mainGui, Database db, ECatalog ecatalog, WeightSelector weightSelector) {
	super.attach(mainGui, db, ecatalog, weightSelector);

	constraint = new MultipleValuesConstraint(type, attribute, db, ecatalog.getDefaultWeight());
	db.addConstraint(constraint);
	
	constraint.update();  //TODO: Fix. Swing calls the JComboBox model and rendered before everything is initialise.
	initGui();
    }

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

    public String getConstraintTypeLabel() {
	return label[(type == MultipleValuesConstraint.Type.INCLUDE) ? 1 : 0];
    }

    public Constraint getConstraint() {
	return constraint;
    }

    public Attribute getAttribute() {
	return attribute;
    }

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

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

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

    public void requestFocus() {
	//valueComboBox.requestFocus();
    }

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

	systemChangingFields = true;
	sortedIndex = null;
	multipleSelectionList.update();
	clearButton.setEnabled(constraint.areDetailsDefined());
	systemChangingFields = false;
    }

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

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

    public int getNbrComboBoxes() {
	return constraint.getNbrOfvalues();
    }

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

    public int getNbrComboBoxItems(int comboBoxIndex) {
	//return attribute.getNbrDistinctValues();
	computeSortedIndex();
	return sortedIndex.length;
    }

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

    public ComboBoxItem getComboBoxItem(int comboBoxIndex, int popupItemIndex, boolean computeText, boolean computeColor, boolean computeToolTipText) {
	try {
	    //System.out.println("c[" + attribute.getLabel() + "].getComboBoxItem(comboBoxIndex = " + comboBoxIndex + ", popupItemIndex = " + popupItemIndex+", computeText="+computeText+", computeColor="+computeColor+", computeToolTipText="+computeToolTipText+")");

	    if (!ecatalog.getConfig().isContextualToolTipDisplayed())
		computeToolTipText = false;

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

	    boolean exampleComboBox = (comboBoxIndex == -1 || (comboBoxIndex == 0 && constraint.getNbrOfvalues() == 0));
	    boolean newComboBox = (comboBoxIndex > 0 && comboBoxIndex == constraint.getNbrOfvalues());

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

	    if (exampleComboBox && popupItemIndex == -1) {                                         //example value
		if (!ecatalog.getConfig().isExampleDisplayed()) {
		    valueIndex = -1;
		} else if (constraint.getType() == MultipleValuesConstraint.Type.EXCLUDE) {
		    valueIndex = -1;
		    toolTipText += "You can still add some more criteria here.\n";
		} 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.getCurrentNbrCompatibleItemsIfValueAdded(valueIndex);
		    if (computeColor)
			color = (!ecatalog.getConfig().isProspectiveAnalysisDisplayed() || attribute.getCurrentNbrCompatibleDistinctValues() != 1) 
			    ? MainGui.colorExample : MainGui.colorInferred;

		    if (computeToolTipText) {
			MultipleValuesConstraint tmpC = (MultipleValuesConstraint)constraint.clone();
			tmpC.addValueIndex(valueIndex);
			String widthString = tmpC.getWithConstraintString();

			if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
			    toolTipText += "You would select this criteria (" + widthString + ").";
			} else {
			    toolTipText += "If you select this criteria (" + widthString + "), " +
				"there would be " + nbrItems + " compatible items.";
			    if (nbrItems == 0) {
				ECatalog.error(new Error("exampleComboBox && popupItemIndex == -1, db.getCurrentNbrSolutions() != 0, nbrItems==0"));
				throw new Error(); //it is already done, but to avoid the java compiling error
				//toolTipText += ", there won't be compatible items.\nYou will see the TRADEOFFS in the analysis window";
			    }
			}
		    }
		}
	    } else if (popupItemIndex == -1 && !newComboBox) {
		valueIndex = constraint.getValueIndexAt(comboBoxIndex);       //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 value (" + attribute.getValueString(valueIndex) + "), " +
				"there would be " + constraint.getCurrentNbrCompatibleItemsIfValueRemoved(valueIndex) + " compatible items.";
		    }
		}
	    } else if (popupItemIndex == -1 && newComboBox) {
		valueIndex = -1;
		nbrItems = -1;
		color = MainGui.colorConstraintGiven; //just to set something
		String op = (constraint.getType() == MultipleValuesConstraint.Type.INCLUDE) ? "include" : "exclude";
		toolTipText += "You can " + op + " another value for " + attribute.getLabel();
	    } else {
		computeSortedIndex();

		valueIndex = sortedIndex[popupItemIndex];                               //popup list value
		if (computeText || computeColor || computeToolTipText) {
		    if (!exampleComboBox && !newComboBox) {        //replace a value
			if (valueIndex == constraint.getValueIndexAt(comboBoxIndex)) {  //nochange...
			    nbrItems = -1;
			    color = MainGui.colorConstraintGiven;
			    if (computeToolTipText)
				toolTipText += "You have already specified this value (" + attribute.getValueString(valueIndex)  + ").\n" +
				    "By selecting this, you are not modifying your criteria.";
			} else if (constraint.containtsValueIndex(valueIndex)) {        //removing the value for this combobox
			    nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueRemoved(constraint.getValueIndexAt(comboBoxIndex));
			    color = MainGui.colorConstraintGiven;
			    if (computeToolTipText)
				toolTipText += "You have already specified this value (" + attribute.getValueString(valueIndex)  + ").\n" +
				    " By selecting this, you are in fact removing the value \"" + attribute.getValueString(constraint.getValueIndexAt(comboBoxIndex)) + "\".";
			} else {                                                        //replacing the value for this combobox to the selected one
			    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
				toolTipText +=
				    "You would replace \"" + attribute.getValueString(constraint.getValueIndexAt(comboBoxIndex)) + "\" by " +
				    "\"" + attribute.getValueString(valueIndex)  + "\".";
			    } else {
				nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueChanged(constraint.getValueIndexAt(comboBoxIndex), valueIndex);
				color = (nbrItems > 0 ) ? MainGui.colorExample : MainGui.colorOverconstrained;
				if (computeToolTipText) {
				    toolTipText += "If you replace \"" + attribute.getValueString(constraint.getValueIndexAt(comboBoxIndex)) + "\" by " +
					"\"" + attribute.getValueString(valueIndex)  + "\", ";
				    if (nbrItems == 0)
					toolTipText += "there are not compatible items.\nYou can see the current tradeoffs in the analysis windows.";
				    else
					toolTipText += "there are " + db.getCurrentNbrSolutions() + " compatible items.";
				}
			    }
			} 
		    } else {                                                   //add a new value
			if (constraint.containtsValueIndex(valueIndex)) {
			    nbrItems = -1;
			    color = MainGui.colorConstraintGiven;
			    if (computeToolTipText) 
				toolTipText += "You have already specified this value (" + attribute.getValueString(valueIndex)  + ").\n" +
				    "By selecting this, you are not modifying your criteria.";
			} else {
			    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
				String op = (constraint.getType() == MultipleValuesConstraint.Type.INCLUDE) ? "include" : "exclude";
				toolTipText += "You would " + op + " \"" + attribute.getValueString(valueIndex)  + "\".";
			    } else {
				nbrItems = constraint.getCurrentNbrCompatibleItemsIfValueAdded(valueIndex);
				color = (nbrItems > 0 ) ? MainGui.colorExample : MainGui.colorOverconstrained;
				if (computeToolTipText) {
				    String op = (constraint.getType() == MultipleValuesConstraint.Type.INCLUDE) ? "include" : "exclude";
				    toolTipText += "If you " + op + " \"" + attribute.getValueString(valueIndex)  + "\", ";
				    if (nbrItems == 0)
					toolTipText += "there are yet not compatible items.\nYou can see the current tradeoffs in the analysis windows.";
				    else
					toolTipText += "there are " + nbrItems + " compatible items.";
				}
			    }
			}	    
		    }
		}
	    }

	    if (valueIndex == -1)
		color = Color.black;
	    String text = (valueIndex == -1) ? null : attribute.getValueString(valueIndex);
	    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
		if (computeColor && popupItemIndex != -1) 
		    color = MainGui.colorConstraintGiven;
	    } else {
		if (computeText && valueIndex != -1) {
		    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
	}
	catch (ArrayIndexOutOfBoundsException e) { 
	    //TODO minor: when removing an item, there is a time before updating where MultipleSelectionList thinks there is still +1 comboboxes
	    return new ComboBoxItem(-1, "", Color.black, "");
	}
    }

    public String getRemoveItemToolTipText(int comboBoxIndex) {
	try {
	    String toolTipText = attribute.getConfig().getHelpPrompt() + ".\n";
	    int valueIndex = constraint.getValueIndexAt(comboBoxIndex);
	    if (!ecatalog.getConfig().isProspectiveAnalysisDisplayed()) {
		toolTipText += 
		    "You would remove this value (" + attribute.getValueString(valueIndex) + ").";
	    } else {
		toolTipText += 
		    "If you remove this value (" + attribute.getValueString(valueIndex) + "), " +
		    "there would be " + constraint.getCurrentNbrCompatibleItemsIfValueRemoved(valueIndex) + " compatible items.";
	    }
	    return toolTipText;
	} catch (SQLException e) { 
	    ECatalog.error(e); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}
    }
   
    public void itemAdded(Object newItemIndex) {
	/*
	DebugUtil.printTrace("newItemIndex: " + newItemIndex);
	constraint.addValueIndex((Integer)newItemIndex);
	ecatalog.update();
	*/
	selectedItemChanged(constraint.getNbrOfvalues(), newItemIndex);
    }

    public void selectedItemChanged(int itemPosition, Object newItemIndex) {
	//DebugUtil.printTrace("itemPosition: " + itemPosition + ", newItemIndex: " + newItemIndex);
	int valueIndex = (Integer)newItemIndex;

	if (ecatalog.getLog() != null) {
	    boolean mvOpAdd = (constraint.getNbrOfvalues() == itemPosition && !constraint.containtsValueIndex(valueIndex));
	    String mvOp2 = null;
	    if (!mvOpAdd)
		mvOp2 = (!constraint.containtsValueIndex(valueIndex))          ? "NEW" 
		    : (constraint.getNbrOfvalues() == itemPosition || constraint.getValueIndexAt(itemPosition) != valueIndex) ? "INLIST" : "CURRENT";
	    
	    ecatalog.getLog().updateConstraint(this, constraint.areDetailsDefined() ? "MODIFY" : "ADD", valueIndex,
					       mvOpAdd ? "ADD" : "REPLACE", mvOp2, itemPosition);
	}

	constraint.setValueIndex(valueIndex, itemPosition);
	ecatalog.update();
    }

    public void selectedItemRemoved(int itemPosition) {
	//DebugUtil.printTrace("itemPosition: " + itemPosition);
	int itemIndex = constraint.getValueIndexAt(itemPosition);

	if (ecatalog.getLog() != null)
	    ecatalog.getLog().updateConstraint(this, (constraint.getNbrOfvalues() == 1) ? "MODIFY" : "REMOVE", -1,
					       "REMOVE", null, itemPosition);

	constraint.removeValueIndex(itemIndex);
	ecatalog.update();
    }

    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");
    }

    void initGui() {
	mainPanel = new JPanel();
	GridBagLayout gbl = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints();
	mainPanel.setLayout(gbl);
	//mainPanel.setBackground(Color.WHITE);

	filterProperties = (ecatalog.getConfig().isProspectiveAnalysisDisplayed())
	    ? new ComboBoxFilterProperties(true, true, true, true)
	    : new ComboBoxFilterProperties(true, false, false, false);

	String rowLabel = (constraint.getType() == MultipleValuesConstraint.Type.INCLUDE) ? "or" : "nor";
	boolean setMinimumPreferredWidth = (ecatalog.getConfig().getCriteriaPanelWidth() != null);

        multipleSelectionList = new MultipleSelectionList();
	multipleSelectionList.init(this, rowLabel, filterProperties, setMinimumPreferredWidth);
	multipleSelectionList.getComponent().setOpaque(false);

	c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = 1; c.weightx = 1.0D; c.fill = GridBagConstraints.HORIZONTAL;
	gbl.setConstraints(multipleSelectionList.getComponent(), c);
	mainPanel.add(multipleSelectionList.getComponent());


	// Create the "clear" button
	Component box = Box.createHorizontalStrut(2); 
	c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = 1; c.weightx = 0.0D; c.fill = GridBagConstraints.NONE;
	gbl.setConstraints(box, c);
	mainPanel.add(box);

	clearButton = new AnotherLinkButton("clear");
	Dimension dim = clearButton.getPreferredSize(); clearButton.setMaximumSize(dim);
	clearButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { valueClear(); } });
	c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 0.0D; c.fill = GridBagConstraints.NONE;
	gbl.setConstraints(clearButton, c);
	mainPanel.add(clearButton);
    }
}

