/*
 * 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 ecatalog.jaxb.ecatalogConfig.ECatalogConfigType;
import ecatalog.jaxb.ecatalogConfig.ConstraintType;
import org.w3c.dom.Element;

import utils.*;
import utils.OpaqueBox;

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

import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Arrays;
import java.util.Comparator;

import java.sql.SQLException;

import info.clearthought.layout.TableLayout;

import dpc.utils.ArrayConverter;
import dpc.utils.DebugUtil;


/* The Criteria Selection window. It creates a default ConstraintGui for each attribute 
 * TODO: recode to use a jtable or similar
 */
public class CriteriaSelectionGui {
    ECatalog ecatalog;
    boolean preferencesAllowed;
    MainGui mainGui;
    ECatalogConfigType config;
    Database db;

    TableLayout layout;
    int criteriaPanelWidth;
    Vector<ConstraintGui> constraintGuis;

    JPanel mainPanel;

    JPanel constraintsPanel;
    public JScrollPane constraintsScrollPanel;

    public JComponent getComponent() {
	return mainPanel;
    }

    public void reset() {
	reset(false);
    }

    public void reset(boolean update) {
	for (ConstraintGui c : constraintGuis)
	    c.reset();
	if (update)
	    ecatalog.update();
    }

    public void update() throws SQLException {
	for (ConstraintGui c : constraintGuis)
	    c.update();
	mainPanel.revalidate();
    }

    public void setEnabled(boolean enabled) {
	for (ConstraintGui c : constraintGuis)
	    c.setEnabled(enabled);
    }

    public JPanel init(MainGui mainGui, ECatalog ecatalog) {
	this.ecatalog = ecatalog;
	this.mainGui = mainGui;
	this.config = ecatalog.getConfig();
	this.preferencesAllowed = config.isPreferencesAllowed();
	this.db = ecatalog.getDatabase();

	Dimension dimension;

	//Main panel
	mainPanel = new JPanel();
	GridBagLayout mgbl = new GridBagLayout(); GridBagConstraints mc = new GridBagConstraints();
	mainPanel.setLayout(mgbl);
	
	//mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
	//mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
	mainPanel.setBackground(Color.WHITE);

	//TOP PANEL: title + reset button
	JPanel topPanel = new JPanel();
	topPanel.setLayout(new TableLayout(new double[][] {{TableLayout.PREFERRED, TableLayout.FILL, TableLayout.PREFERRED},
							   {TableLayout.PREFERRED}}));
	topPanel.setBackground(Color.BLUE);

	JLabel aLabel = new JLabel("Selection criteria");
	aLabel.setFont(new Font(mainGui.fontName, Font.BOLD, 18));
	aLabel.setForeground(Color.WHITE);
	topPanel.add(aLabel, "0,0");

	JButton resetButton = new JButton("Reset");
	resetButton.setFont(new Font(mainGui.fontName, Font.BOLD, 10));
	//resetButton.setPreferredSize(resetButton.getMinimumSize());
	resetButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { reset(true); } });
	topPanel.add(resetButton, "2,0");

	mc.anchor = GridBagConstraints.NORTHWEST; mc.gridx=0; mc.gridy=0; mc.gridwidth = 1; mc.weightx = 1.0D; mc.weighty = 0.0D; mc.fill = GridBagConstraints.HORIZONTAL;
	mgbl.setConstraints(topPanel, mc);
	mainPanel.add(topPanel);

	/*
	JLabel aLabel = new JLabel("Selection criteria");
	aLabel.setFont(new Font(mainGui.fontName, Font.BOLD, 18));
	aLabel.setBackground(Color.BLUE);
	aLabel.setOpaque(true);
	aLabel.setForeground(Color.WHITE);
	mc.anchor = GridBagConstraints.NORTHWEST; mc.gridx=0; mc.gridy=0; mc.gridwidth = 1; mc.weightx = 1.0D; mc.weighty = 0.0D; mc.fill = GridBagConstraints.HORIZONTAL;
	mgbl.setConstraints(aLabel, mc);
	mainPanel.add(aLabel);
	*/

	Component box = Box.createVerticalStrut(10);
	mc.anchor = GridBagConstraints.NORTHWEST; mc.gridx=0; mc.gridy=1; mc.gridwidth = 1; mc.weightx = 1.0D; mc.weighty = 0.0D; mc.fill = GridBagConstraints.HORIZONTAL;
	mgbl.setConstraints(box, mc);
	mainPanel.add(box);

	createConstraintsPanel();

	constraintsScrollPanel = new JScrollPane(constraintsPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	constraintsScrollPanel.setBackground(Color.WHITE);

	mc.anchor = GridBagConstraints.NORTHWEST; mc.gridx=0; mc.gridy=2; mc.gridwidth = 1; mc.weightx = 1.0D; mc.weighty = 1.0D; mc.fill = GridBagConstraints.BOTH;
	mgbl.setConstraints(constraintsScrollPanel, mc);
	mainPanel.add(constraintsScrollPanel);
	
	return mainPanel;
    }



    void createConstraintsPanel() {
	criteriaPanelWidth = (config.getCriteriaPanelWidth() == null) ? -1 :  config.getCriteriaPanelWidth().intValue();

	constraintsPanel = new JPanel() {
		public Dimension getPreferredSize() {
		    if (criteriaPanelWidth == -1)
			return super.getPreferredSize();
		    return new Dimension(super.getMinimumSize().width, super.getPreferredSize().height);
		}
	    };

	constraintsPanel.setBackground(Color.white);
	//gbl = new GridBagLayout(); c = new GridBagConstraints();
	//constraintsPanel.setLayout(gbl);

	layout = new TableLayout(new double[][] {{TableLayout.PREFERRED, 5, TableLayout.FILL},{}});
	constraintsPanel.setLayout(layout);

	constraintGuis = new Vector<ConstraintGui>();

	addConstraints();
    }

    void addConstraints() {
	int constraintIndex = 0;
	for(Attribute at : db.getAttributes()) 
	    if (at.getConfig().getDefaultConstraintIndex().intValue() != -1)
		addConstraint(at, constraintIndex++);
    }

    void removeConstraints() {
	for (ConstraintGui constraintGui : constraintGuis) {
	    constraintGui.detach();
	    layout.deleteRow(0);
	}
	constraintGuis = new Vector<ConstraintGui>();
	constraintsPanel.removeAll();
    }

    /* Adds a constraint gui to the panel, based on an attribute.
     */
    void addConstraint(Attribute at, int constraintIndex) {
	WeightSelector weightSelector = null;
	if (preferencesAllowed)
	    weightSelector = new WeightSelector(ecatalog);
	JPanel labelAndWeightPanel = createLabelAndWeightPanel(at, weightSelector);
	ConstraintGui constraintGui = createConstraintGui(at, weightSelector);

	addConstraint(constraintIndex, labelAndWeightPanel, constraintGui);
    }

    /* Adds a constraint gui to the panel, based on an attribute.
     */
    void addConstraint(int insertAtConstraintIndex, JPanel labelAndWeightPanel, ConstraintGui constraintGui) {
	constraintGuis.add(insertAtConstraintIndex, constraintGui);

	layout.insertRow(insertAtConstraintIndex*2, TableLayout.PREFERRED);

	// Label and Weigth
	labelAndWeightPanel.setOpaque(false);
	constraintsPanel.add(labelAndWeightPanel, "0, " + (insertAtConstraintIndex*2) + ", l, t");

	// Constraint Gui panel
	constraintGui.mainPanel.setOpaque(false);
	constraintsPanel.add(constraintGui.mainPanel, "2, " + (insertAtConstraintIndex*2));

	Color bgColor = (insertAtConstraintIndex % 2 == 0) ? MainGui.colorBgEvenItems : MainGui.colorBgOddItems;
	JLabel bgBox = new JLabel();
	bgBox.setBackground(bgColor);
	bgBox.setOpaque(true);
	constraintsPanel.add(bgBox, "0, " + (insertAtConstraintIndex*2) + ", 2, " + (insertAtConstraintIndex*2));

	layout.insertRow(insertAtConstraintIndex*2+1, 4);
    }

    ConstraintGui createConstraintGui(Attribute at, WeightSelector weightSelector) {
	ConstraintGui constraintGui;

	try {
	    List<ConstraintType> listConstraints = at.getConfig().getConstraint();
	    int size = listConstraints.size();
	    if (size == 0) {
		if (at instanceof BooleanAttribute) {
		    constraintGui = new BooleanAttributeConstraintGui();
		    ((BooleanAttributeConstraintGui)constraintGui).configure((BooleanAttribute)at);
		} else if (at instanceof TextAttribute) {
		    Vector<ConstraintGui> constraintGuis = new Vector<ConstraintGui>();
		    
		    SimpleAttributeConstraintGui simple = new SimpleAttributeConstraintGui();
		    simple.configure(SimpleAttributeConstraintGui.ConstraintType.EQUAL, at, null);
		    constraintGuis.add(simple);

		    MultipleValuesConstraintGui multiple = new MultipleValuesConstraintGui();
		    multiple.configure(MultipleValuesConstraint.Type.INCLUDE, at);
		    constraintGuis.add(multiple);

		    multiple = new MultipleValuesConstraintGui();
		    multiple.configure(MultipleValuesConstraint.Type.EXCLUDE, at);
		    constraintGuis.add(multiple);

		    constraintGui = new ConstraintGuiSelector();
		    ((ConstraintGuiSelector)constraintGui).configure((ConstraintGui[])constraintGuis.toArray(new ConstraintGui[0]), 0);
		} else if (at instanceof NumberAttribute) {
		    Vector<ConstraintGui> constraintGuis = new Vector<ConstraintGui>();
		    
		    SimpleAttributeConstraintGui simple = new SimpleAttributeConstraintGui();
		    simple.configure(SimpleAttributeConstraintGui.ConstraintType.EQUAL, at, null);
		    constraintGuis.add(simple);

		    simple = new SimpleAttributeConstraintGui();
		    simple.configure(SimpleAttributeConstraintGui.ConstraintType.LESS_THAN_OR_EQUAL, at, null);
		    constraintGuis.add(simple);

		    simple = new SimpleAttributeConstraintGui();
		    simple.configure(SimpleAttributeConstraintGui.ConstraintType.MORE_THAN_OR_EQUAL, at, null);
		    constraintGuis.add(simple);
		    
		    constraintGui = new ConstraintGuiSelector();
		    ((ConstraintGuiSelector)constraintGui).configure((ConstraintGui[])constraintGuis.toArray(new ConstraintGui[0]), 0);
		} else {
		    ECatalog.error(new Error("unknown type of attribute"));
		    throw new Error(); //it is already done, but to avoid the java compiling error
		}
	    } else if (size ==1) {
		ConstraintType ctype = listConstraints.get(0);
		AttributeConstraintGui acGui = (AttributeConstraintGui) Class.forName(ctype.getClassName()).newInstance();
		acGui.configure((Element)ctype.getAny(), at);

		constraintGui = new ConstraintGuiSelector();
		((ConstraintGuiSelector)constraintGui).configure(new ConstraintGui[] {acGui}, 0);
	    } else {
		ConstraintGui[] constraintGuis = new ConstraintGui[size];
		for (int i = 0; i < size; i++) {
		    ConstraintType ctype = listConstraints.get(i);
		    constraintGuis[i] = (AttributeConstraintGui) Class.forName(ctype.getClassName()).newInstance();
		    ((AttributeConstraintGui)constraintGuis[i]).configure((Element)ctype.getAny(), at);
		}
		constraintGui = new ConstraintGuiSelector();
		((ConstraintGuiSelector)constraintGui).configure(constraintGuis, at.getConfig().getDefaultConstraintIndex().intValue());
	    }
	}
	catch (Exception e) { 
	    ECatalog.error(new Error(e)); 
	    throw new Error(); //it is already done, but to avoid the java compiling error
	}

	constraintGui.attach(mainGui, db, ecatalog, weightSelector);
	return constraintGui;
    }
    

    JPanel createLabelAndWeightPanel(Attribute at, WeightSelector weightSelector) {
	JPanel panel = new JPanel();
	panel.setBackground(Color.WHITE);

	GridBagLayout gbl = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints();
	panel.setLayout(gbl);

	String helpPrompt = at.getConfig().getHelpPrompt();
	JLabel constraintLabel = new JLabel(at.getConfig().getLabel()) {
		public JToolTip createToolTip() { return MainGui.createToolTip(); }
	    };
	constraintLabel.setToolTipText(helpPrompt);
	//constraintLabel.setFont(mainGui.font);  //TODO minor: use same font, adding the bold property
	c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 0.0D; c.fill = GridBagConstraints.NONE;
	gbl.setConstraints(constraintLabel, c);
	panel.add(constraintLabel);

	if (preferencesAllowed) {
	    c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 0.0D; c.fill = GridBagConstraints.NONE;
	    gbl.setConstraints(weightSelector.getComponent(), c);
	    panel.add(weightSelector.getComponent());
	}

	//TODO: remove all these things, make a proper jtable
	JComponent box = new JLabel();
	box.setOpaque(false);
	c.anchor = GridBagConstraints.NORTHWEST; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 1.0D; c.weighty = 1.0D; c.fill = GridBagConstraints.BOTH;
	gbl.setConstraints(box, c);
	panel.add(box);

	return panel;
    }
}

