/*
 * 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/
 */

import ecatalog.ECatalog;
import ecatalog.ECatalogListener;
import ecatalog.ECatalogLog;
import ecatalog.db.Attribute;
import ecatalog.db.NumberAttribute;
import ecatalog.db.Item;
import ecatalog.db.DefaultItem;

import ecatalog.gui.visualization.VisualizationGui;
import ecatalog.gui.visualization.SelectedItem;
import ecatalog.gui.visualization.ECatalogItemTableRenderer;
import ecatalog.gui.visualization.DefaultItemTableRenderer;
import ecatalog.gui.visualization.ItemActionProxyItemTableRendererListener;

import utils.LinkedHashMap2;
import utils.Log;
import utils.InputRecordAndPlaybackEngine;
import survey.*;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import ecatalog.jaxb.ecatalogConfig.ECatalogConfigType;

import java.io.*;
import java.sql.SQLException;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.SwingUtilities;
import info.clearthought.layout.TableLayout;
import java.util.*;
import java.util.Timer;
import java.util.regex.Pattern;
import java.text.SimpleDateFormat;


class UserStudy implements ECatalogListener {
    String logPath = "./logs/";
    final String surveysConfigPath = "surveysConfig/";
    final String ecatalogConfigPath = "configs/";

    final String carsConfigFileName = "config-cars.xml";
    final String apartmentsConfigFileName = "config-apartments.xml";

    final String carsDatabaseName = "Second-hand Cars";
    final String apartmentsDatabaseName = "Apartments to rent in Lausanne or nearby";

    final String timeLeftText = "Time left for this task: ";
    final String timeFinishText = "Time has expired, please take a choice soon!";
    final int taskTimeInMinutes = 5;

    final String usageInformation = 
	"Usage:\n" +
	"java -cp \"build:${CLASSPATH}\" UserStudy <participantId> <interfaceId>\n";

    /*
      final String validationMessage =
      "Now we ask you to validate your choice.<br>" + 
      "Using the " + validationSystemName +", look if you can find a better one, according to the task.<br>"+
      "If you cannot, take the one you selected in the task (which you will see it placed in the Consideration Window).<br>";
    */

    String userId;
    boolean preferencesAllowed;
    boolean conflictAnalysisDisplayed;


    JFrame frame;
    JPanel mainPanel;

    ExperimentLog experimentLog;
    //String experimentTimeString;
    Survey survey;

    ECatalog ecatalog;
    Attribute idAttribute;
    LinkedHashMap2<String, DefaultItem> preSelectedItems = new LinkedHashMap2<String, DefaultItem>();

    ECatalogLog log;
    ECatalogItemTableRenderer currentRenderer;
    SimpleDateFormat sdf;
    InputRecordAndPlaybackEngine inputRecordEngine;

    public static void main(String[] argv) {
	try{
	    new UserStudy().run(argv);
	} catch (Exception e) {
	    e.printStackTrace();
	    JOptionPane.showMessageDialog(null, "Ask David. Error: " + e.toString(), "ERROR. Ask David", JOptionPane.ERROR_MESSAGE);
	}
    }


    void init() throws Exception {
	String DATE_FORMAT = "yyyyMMddHHmmssSSS";
	sdf = new SimpleDateFormat(DATE_FORMAT);
	sdf.setTimeZone(TimeZone.getDefault());          

	Calendar cal = Calendar.getInstance(TimeZone.getDefault());
	String experimentTimeString = sdf.format(cal.getTime());
	logPath += userId + "_" + experimentTimeString + "/";
	if (! (new File(logPath).mkdir()))
	    throw new Exception("Cannot create the path " + logPath);
    }


    void run(String[] argv) throws Exception {
	System.out.println("Application: START");
	if (argv.length != 2 || !("1".equals(argv[1]) || "2".equals(argv[1]) || "3".equals(argv[1]))) {
	    System.out.println(usageInformation);
	    return;
	}

	userId = argv[0];
	preferencesAllowed = (argv[1].equals("1") || argv[1].equals("3"));
	conflictAnalysisDisplayed = (argv[1].equals("2") || argv[1].equals("3"));
	

	init();

	experimentLog = new ExperimentLog(getLogFileName("userstudy"));
	experimentLog.genericInfo("config", new String[][] {{"preferencesAllowed", (preferencesAllowed ? "yes" : "no")}, {"conflictAnalysisDisplayed", (conflictAnalysisDisplayed ? " yes": "no")}});
	experimentLog.genericInfo("step", new String[][] {{"id","start"}});

	initGui();

	String introductionMessage =
	    "Dear User, you are participating in an experiment on electronic catalogs.<br><br>" +
	    "David will give you now a printed timeline, a consent form and two tutorials.<br><br>" +
	    "This experiment will take around 1h - 1h30.<br><br>" +
	    "Please click \"Start Experiment\" when you are ready.<br>";
	messageScreen("Welcome", introductionMessage, "Start Experiment >>");

	String consentFormMessage =
	    "Please, sign now the printed \"Consent Form\" sheet.<br><br>" +
	    "Click Continue when done.<br>";
	messageScreen("Consent Form", consentFormMessage, "Continue >>");
	
	questionnaireScreen("preSurveyConfig.xml", "preSurvey");

	//Baseline
	task(false);

	//Improved
	task(true);

	//Validation
	validation();

	//Specfic Survey
	String surveyConfig = "generated/specificSurveyConfig_" + (preferencesAllowed ? "y" : "n") +"w_" + (conflictAnalysisDisplayed ? "y" : "n") + "ta.xml";
	questionnaireScreen(surveyConfig, "specificSurvey");
	
	//FINISH
	String endMessage =
	    "The experiment has finished. Many thanks for your help!<br>" +
	    "Ask for the reward!<br>";

	messageScreen("Thanks", endMessage, "Finish");

	experimentLog.genericInfo("step", new String[][] {{"id", "end"}});
	experimentLog.close();
	inputRecordEngine.stopRecording();
	System.out.println("Application: END");
	System.exit(0);
    }

    String getImprovedSystemName() {
	String name = "ECatalog+";
	if (preferencesAllowed)
	    name += "W";
	if (conflictAnalysisDisplayed)
	    name += "T";
	return name;
    }

    void task(boolean improvedSystem) throws Exception {
	String title = ((!improvedSystem) ? "Baseline" : "Improved") + " System";
	String message = (!improvedSystem) 
	    ? "First you are going to use a baseline system for electronic catalogs (we call it <b>ECatalog</b>).<br>"
	    : "Now you are going to use the improved version of the system called <b>" + getImprovedSystemName() + "</b>).<br>";
	message +=
	    "<br>" +
	    "You will learn to use it with the help of a tutorial, and then you will be asked to accomplish a task.<br>" +
	    "Click Next to continue";
	messageScreen(title, message, "Next >>");

	//TUTORIAL
	subTask(true, improvedSystem);


	if (!improvedSystem) {
	    message =
		"You have learnt to use the system. Now you will be asked to use the system to search for a second-hand car.<br><br><br>" +
		"First, please, <b>take a look at the printed document</b> titled \"Second-hand cars database\".<br>" +
		"It presents the list of attributes of the database, and explains the representation used in the \"Matching Items\" window.<br><br><br>" + 
		"Once you have read the document, click \"Next\" to continue.<br>";
	    messageScreen("Second-hand cars", message, "Next >>");
	}

	//PAUSE
	String pauseMessage =
	    "You have learnt to use the system. Now you will be asked to use the system to search for a second-hand car.<br>" +
	    "(The details of the task will be presented in the next screen)<br><br>" +
	    "After the task, you will be asked to fill a questionnaire to messure the subjective workload while using the system.<br>" +
	    "Remember that we are not evaluating you; we are comparing three distinct systems.<br><br><br>" +

	    "<b>Please, take one minute to relax and click \"Next\" to continue.</b>";
	messageScreen("Pause", pauseMessage, "Next >>");

	//REAL TASK
	DefaultItem item = subTask(false, improvedSystem);

	//Save the selected item
	String itemId = idAttribute.getDbValueString(item, false);
	preSelectedItems.put(itemId, item);

	//QUESTIONAIRE
	questionnaire(improvedSystem);
    }

    DefaultItem subTask(boolean tutorial, boolean improvedSystem) throws Exception {
	String systemId = improvedSystem ? "Improved" : "Baseline";
	String id    = systemId + "_" + (tutorial ? "tutorial" : "task");
	String title = systemId + " System. " + (tutorial ? "Tutorial" : "Task");
	String databaseName = tutorial ? apartmentsDatabaseName : carsDatabaseName;
	String configFileName = tutorial ? apartmentsConfigFileName : carsConfigFileName;

	boolean preferencesAllowed = improvedSystem ? UserStudy.this.preferencesAllowed : false;
	boolean conflictAnalysisDisplayed = improvedSystem ? UserStudy.this.conflictAnalysisDisplayed : false;

	
	String context = "You will search in a database of <b>" + databaseName + "</b>, using the <b>" + systemId + " system</b>.";
	String task;
	if (tutorial) {
	    String tutorialName = systemId + " System: " + ((!improvedSystem) ? "ECatalog" : getImprovedSystemName()) + ". Tutorial";
	    task =
		"Follow the <p>printed</b> tutorial called \"<b>" + tutorialName + "</b>\".<br><br>" + 
		"<b>Note:</b> Please, follow strictly the tutorial; otherwise we won't be able to use the results of this user study.<br>" +
		"At the end of the tutorial, you will have some time to try the system by yourself.";
	} else {
	    task =
		"<b>Your assignment is to select your optimal second-hand car.</b><br><br>" +

		"<b>Note:</b> There are only 200 second-hand cars in the database, so it may happen that you don't find your perfect choice.<br>" +
		"You still need to make a choice (your optimal second-hand car from the ones in the database).<br><br>" +
		
		"<b>Note:</b> There is not a strict time restriction, but try to limit yourself to 5 minutes.<br>";

	    if (improvedSystem)
		task += "<br><br><br><br><br>" + 
		    "<b>Note: It is the same list of second-hand cars as before.<br>" +
		    "Using the Improved System you may find a better choice.</b><br>";
	}

	task += "<br><br><br>" + 
	    "Click \"Start\" when you are ready to start the task.<br>";


	String message = "<h2>Context</h2>" + context + "<br><br><br><h2>Task</h2>" + task + "<br>";
	
	String messageShort = (tutorial) 
	    ?
	    "<b>Task:</b> Follow the tutorial"
	    :
	    "<b>Task:</b> Find your optimal second-hand car.<br>" +
	    "Once you have decided, select it and click the button at the botton right corner.";

	messageScreen(title, message, null);
	prepareECatalog(configFileName, null, id, preferencesAllowed, conflictAnalysisDisplayed);
	messageScreenWait("Start >>>");

	DefaultItem itemCopy = ecatalogScreen(id, messageShort, ((tutorial == true) ? -1 : taskTimeInMinutes));

	log.close();
	removeECatalog();
	clearScreen();

	return itemCopy;
    }

    void questionnaire(boolean improvedSystem) throws Exception {
	String id    = improvedSystem ? "improved" : "baseline";
	String configFileName = "generated/generalSurveyConfig_" + id + ".xml";

	questionnaireScreen(configFileName, "generalSurvey_" + id);
    }

    
    void validation() throws Exception {
	String message =
	    "Thanks. Now we need to verify whether the Baseline and/or the Improved system were really useful or not.<br>" +
	    "For this, we are going to show you the list of all the second-hand cars,<br>" +
	    "and we ask you to <b>carefully inspect all the cars to find your best option</b>.<br><br>" +

	    "<b>Note:</b> You can sort the cars by an attribute by clicking on its column header.<br>" +
	    "(by clicking again on the same column header, you change the sorting order).<br><br>" +

	    "<b>Note:</b> In case that you don't find a better car, you can select the one you chosed in the Baseline or in the Improved system<br>" +
	    "(which you will find placed in the \"Considered Items\" window).<br><br>" +

	    "Click Next to continue";

	String messageShort =
	    "Please carefully inspect all the cars to find your best option.<br>" +
	    "Once you have decided, select it and click the button at the botton right corner.<br><br>" +

	    "<b>Note:</b> You can sort the cars by an attribute by clicking on its column header.<br>" +
	    "(by clicking again on the same column header, you change the sorting order).<br><br>" +

	    "<b>Note:</b> In case that you don't find a better one, you can select the one you chosed in the Baseline or in the Improved system<br>" +
	    "(which you can see placed in the \"Considered Items\" window).";

	messageScreen("Validation", message, null);

	prepareValidation(carsConfigFileName, null, "validation");

	messageScreenWait("Validation >>>");

	validationScreen("validation", messageShort, null);

	questionnaireScreen("validationSurveyConfig.xml", "validationSurvey");
    }


    void initGui() throws Exception {
	UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
	frame = new JFrame("ECatalog");
	mainPanel = new JPanel();
	mainPanel.setBackground(Color.white);
	
	frame.getContentPane().add(mainPanel);
	frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {} }); //DO NOT ALLOW TO EXIT.
	                                                                                              //TODO: how to hide the "close" button?
	//frame.pack();
	
	frame.setSize(1200, 900);
	frame.setVisible(true);
	frame.setExtendedState(Frame.MAXIMIZED_BOTH);
	frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);


	inputRecordEngine = new InputRecordAndPlaybackEngine();
	inputRecordEngine.startRecording(getLogFileName("inputRecordEngine"), false);
	initEventQueue();
    }

    void prepareECatalog(String configFileName, String fromClause, String taskId, boolean preferencesAllowed, boolean conflictAnalysisDisplayed) throws Exception {
	//get config
	JAXBContext jc = JAXBContext.newInstance("ecatalog.jaxb.ecatalogConfig");
	Unmarshaller u = jc.createUnmarshaller();
	ECatalogConfigType config = (ECatalogConfigType) ((JAXBElement<?>)u.unmarshal(new FileInputStream(ecatalogConfigPath + configFileName))).getValue();

	config.setExampleDisplayed(true);
	config.setPreferencesAllowed(preferencesAllowed);
	config.setProspectiveAnalysisDisplayed(false);
	config.setConflictAnalysisDisplayed(conflictAnalysisDisplayed);
	config.setContextualToolTipDisplayed(false);
	config.setConstraintStringFunctionsEnabled(false);

	if (fromClause != null)
	    config.setFromClause(fromClause);
	
	/*
	//take the ItemTableRenderer from the config file and install the proxy
	String className = config.getItemTableRendererClassName();
	currentRenderer = (className != null) 
	? (ECatalogItemTableRenderer) Class.forName(className).newInstance()
	: new DefaultItemTableRenderer();
	config.setItemTableRendererClassName(BuyProxyItemTableRenderer.class.getName());
	*/

	//init ECatalog
	ecatalog = new ECatalog();
	ecatalog.configure(config);
	ecatalog.addECatalogListener(this);

	//set log
	String logFileName = (taskId != null)? getLogFileName(taskId) : "temp.xml";
	log = new MyECatalogLog(ecatalog, logFileName);
	ecatalog.setLog(log);
	log.genericInfo("experiment", new String[][] {{"userId", userId},
						      {"exampleDisplayed", String.valueOf(config.isExampleDisplayed())},
						      {"preferencesAllowed", String.valueOf(config.isPreferencesAllowed())},
						      {"prospectiveAnalysisDisplayed", String.valueOf(config.isProspectiveAnalysisDisplayed())},
						      {"conflictAnalysisDisplayed", String.valueOf(config.isConflictAnalysisDisplayed())},
						      {"contextualToolTipDisplayed", String.valueOf(config.isContextualToolTipDisplayed())}});

	ecatalog.init();
	idAttribute = ecatalog.getDatabase().getAttributeById("id");
    }

    void removeECatalog() {
	ecatalog = null;
	log = null;
    }

    void clearScreen() {
	mainPanel.removeAll();
    }

    JButton itemSelectButton;
    ClockTask clockTaskToStart = null;
    DefaultItem ecatalogScreen(String taskId, String taskMessage, int taskTimeInMinutes) throws Exception {
	return ecatalogScreen(taskId, taskMessage, taskTimeInMinutes, null);
    }

    DefaultItem ecatalogScreen(String taskId, String taskMessage, int taskTimeInMinutes, ItemConditionCheck itemConditionCheck) throws Exception {
	//System.out.println("ecatalogScreen: START, taskMessage: " + taskMessage + ", taskTimeInMinutes: " + taskTimeInMinutes);

	experimentLog.genericInfo("step", new String[][] {{"id", "ecatalogScreen"}, {"taskId", taskId}, {"state", "start"}});

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

	//TASK MESSAGE
	JEditorPane taskMessagePane = new JEditorPane("text/html", taskMessage);
	taskMessagePane.setEditable(false);
	taskMessagePane.setBackground(Color.lightGray);
	taskMessagePane.setBorder(BorderFactory.createLineBorder(Color.gray)); 
	mainPanel.add(taskMessagePane, "0, 0");

	//ECATALOG INTERFACE
	mainPanel.add(ecatalog.getComponent(), "0, 1");


	//BOTTOM PANE	////////////
	JPanel bottomPanel = new JPanel();
	bottomPanel.setBackground(Color.lightGray);
	bottomPanel.setBorder(BorderFactory.createLineBorder(Color.gray)); 
	mainPanel.add(bottomPanel, "0, 2");

	//TIME MESSAGE
	Timer timer = null;  //just to avoid the compilation error: variable timer might not have been initialized
	if (taskTimeInMinutes != -1) {
	    JEditorPane timeField = new JEditorPane("text/html", "");
	    timeField.setEditable(false);
	    timeField.setOpaque(false);
	    final ClockTask clockTask = new ClockTask(timeField, taskTimeInMinutes*60, timeLeftText, timeFinishText);
	    clockTask.run();   //Run once, so that it sets the initial text and we can get the preferred size.

	    timer = new Timer();
	    timer.schedule(clockTask, 0, 250); //250ms for the blinking effect. todo minor: put this inside the ClockStart itself?
	    clockTaskToStart = clockTask;

	    bottomPanel.setLayout(new TableLayout(new double[][] 
		{{10, TableLayout.FILL, TableLayout.PREFERRED, 30}, {timeField.getPreferredSize().height}}));

	    bottomPanel.add(timeField, "1, 0");
	} else {
	    bottomPanel.setLayout(new TableLayout(new double[][] 
		{{10, TableLayout.FILL, TableLayout.PREFERRED, 30}, {TableLayout.PREFERRED}}));
	}
	
	//SELECT BUTTON
	itemSelectButton = new JButton("Select item. End Task");
	itemSelectButton.setEnabled(ecatalog.getSelectedItem() != null);
	itemSelectButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { waitFinish(); } });
	bottomPanel.add(itemSelectButton, "2, 0");


	//READY
	ecatalog.gui.blockSplitPanes(true);
	ecatalog.gui.visualizationGui.blockSplitPane(true);
	mainPanel.revalidate();
	mainPanel.repaint();

	//START CLOCK
	log.genericInfo("task", new String[][]{{"taskId", taskId}, {"state", "start"}});

	//WAIT UNTIL FINISHED
	while (true) {
	    waitStart();

	    if (ecatalog.getSelectedItem() == null) {  //No item is selected!
		JOptionPane.showMessageDialog(frame, "Please select an item first");
		continue;
	    }

	    //CHECK THE CONDITION
	    if (itemConditionCheck == null)
		break;

	    String msg = itemConditionCheck.isItemValid(ecatalog.getSelectedItem().item);
	    if (msg == null)
		break;
	    JOptionPane.showMessageDialog(frame, msg);
	}

	//END
	if (taskTimeInMinutes != -1)
	    timer.cancel();

	itemSelectButton.setText("Wait please");
	itemSelectButton = null;

	if (taskTimeInMinutes != -1)
	    clockTaskToStart = null; //Most probably it will already be null, but just to make sure

	SelectedItem selectedItem = ecatalog.getSelectedItem();
	log.itemActionSelected(selectedItem, "task", new String[][] {{"taskId", taskId}});
	log.genericInfo("task", new String[][]{{"taskId", taskId}, {"state", "end"}});

	String itemId = getItemId(selectedItem);
	//tasksSelection.put(taskId, itemId);
	log.genericInfo("task", new String[][]{{"taskId", taskId}, {"selectedItemId", itemId}});

	experimentLog.genericInfo("step", new String[][] {{"id", "ecatalogScreen"}, {"taskId", taskId}, {"state", "end"}, {"selectedItemId", itemId}});
	//System.out.println("ecatalogScreen: END");

	DefaultItem itemCopy = DefaultItem.createFromItem(ecatalog.getDatabase(), selectedItem.item);
	return itemCopy;
    }


    public void itemSelected(SelectedItem selectedItem) {
	if (itemSelectButton != null)
	    itemSelectButton.setEnabled(selectedItem != null);
    }


    /*
    void taskValidationStep(String taskId, String taskMessage, ItemConditionCheck itemConditionCheck, String selectionId) throws Exception {
	taskMessage = "<b>" + taskId + ":</b> " + taskMessage;
	String message = "<h2>Validation</h2>" + validationMessage + "<br><br><br><h2>Task</h2>" + taskMessage + "<br>";
	messageScreen("Validation", message, null);
	ecatalog.restart();
	messageScreenWait("Start validation >>>");

	validationScreen(taskId, taskMessage, itemConditionCheck, selectionId);
    }
    */

    void prepareValidation(String configFileName, String fromClause, String taskId) throws Exception {
	prepareECatalog(configFileName, fromClause, taskId, false, false);
    }

    void validationScreen(String taskId, String taskMessage, ItemConditionCheck itemConditionCheck) throws Exception {
	experimentLog.genericInfo("step", new String[][] {{"id", "validationScreen"}, {"taskId", taskId}, {"state", "start"}});

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

	//TASK MESSAGE
	JEditorPane taskMessagePane = new JEditorPane("text/html", taskMessage);
	taskMessagePane.setEditable(false);
	taskMessagePane.setBackground(Color.lightGray);
	taskMessagePane.setBorder(BorderFactory.createLineBorder(Color.gray)); 
	mainPanel.add(taskMessagePane, "0, 0");

	//VISUALIZATION GUI
	VisualizationGui visualizationGui = new VisualizationGui();
	visualizationGui.init(ecatalog.gui, ecatalog.getDatabase(), this, "Items",
			      false, ecatalog.getConfig().getItemTableRendererClassName(), log);

	visualizationGui.resetSorting();
	visualizationGui.resetConsiderationWindow();

	for (int i = 0; i < preSelectedItems.size(); i++)
	    visualizationGui.consider(preSelectedItems.get(i));

	visualizationGui.update();

	JScrollPane scrollPane = new JScrollPane(visualizationGui.getComponent(), JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
	mainPanel.add(scrollPane, "0, 1");

	//BOTTOM PANE	
	JPanel bottomPanel = new JPanel();
	bottomPanel.setBackground(Color.lightGray);
	bottomPanel.setBorder(BorderFactory.createLineBorder(Color.gray)); 
	bottomPanel.setLayout(new TableLayout(new double[][] 
	    {{TableLayout.FILL, TableLayout.PREFERRED, 30}, {TableLayout.PREFERRED}}));
	
	//SELECT BUTTON
	itemSelectButton = new JButton("Select item. End Task");
	itemSelectButton.setEnabled(ecatalog.getSelectedItem() != null);
	itemSelectButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { waitFinish(); } });
	bottomPanel.add(itemSelectButton, "1, 0");
	mainPanel.add(bottomPanel, "0, 2");

	//READY
	mainPanel.revalidate();
	mainPanel.repaint();

	log.genericInfo("validation", new String[][]{{"taskId", taskId}, {"state", "start"}});
	
	//WAIT UNTIL FINISHED
	while (true) {
	    waitStart();

	    //CHECK THE CONDITION
	    if (itemConditionCheck == null)
		break;

	    String msg = itemConditionCheck.isItemValid(visualizationGui.getSelectedItem().item);
	    if (msg == null)
		break;
	    JOptionPane.showMessageDialog(frame, msg);
	}

	//END
	itemSelectButton.setText("Wait please");
	itemSelectButton = null;

	SelectedItem selectedItem = visualizationGui.getSelectedItem();
	log.itemActionSelected(selectedItem, "validation", new String[][] {{"taskId", taskId}});
	log.genericInfo("validation", new String[][]{{"taskId", taskId}, {"state", "end"}});

 	String itemId = getItemId(selectedItem);
	log.genericInfo("validation", new String[][]{{"taskId", taskId}, {"selectedItemId", itemId}});

	experimentLog.genericInfo("step", new String[][] {{"id", "validationScreen"}, {"taskId", taskId}, {"state", "end"}, {"selectedItemId", itemId}});
    }


    /*
    void taskMessageScreen(String title, String databaseName, String details, boolean wait) {
	String context = "You will search in a database of <b>" + databaseName + "</b>, using the <b>" + ecatalogSystemName + "</b>.";
	String message = "<h2>Context</h2>" + context + "<br><br><br><h2>Task</h2>" + details + "<br>";
	messageScreen(title, message, wait ? "Start task >>>" :  null);
    }
    


    void taskMessageScreenWait() {
	messageScreenWait("Start task >>>");
    }
    */

    JButton nextButton;
    /* If buttonText is null, it prints the message, and returns immendiatly */
    void messageScreen(String title, String message, String buttonText) {
	//System.out.println("messageScreen, message: " + message + ", buttonText: " + buttonText);
	waitFinished = false;

	clearScreen();
	TableLayout layout = new TableLayout(new double[][] 
	    {{100, TableLayout.FILL, TableLayout.PREFERRED, 100},
	     {100, TableLayout.FILL, 100, TableLayout.PREFERRED, 70}});
	mainPanel.setLayout(layout);

	JEditorPane textPane = new JEditorPane("text/html", message);
	textPane.setEditable(false);
	textPane.setBorder(BorderFactory.createTitledBorder(title)); 
	mainPanel.add(textPane, "1, 1, 2, 1");

	nextButton = new JButton(buttonText);
	nextButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { waitFinish(); } });

	mainPanel.add(nextButton, "2, 3");
	mainPanel.revalidate();
	mainPanel.repaint();

	experimentLog.genericInfo("step", new String[][] {{"id","messageScreen"}, {"title", title}});

	if (buttonText != null)
	    messageScreenWait(buttonText);
	nextButton.setEnabled(false);
	nextButton.setText("Wait please");
    }

    void messageScreenWait(String buttonText) {
	nextButton.setText(buttonText);
	nextButton.setEnabled(true);

	waitStart();
	nextButton.setEnabled(false);
	nextButton.setText("Wait please");
    }



    String getItemId(SelectedItem selectedItem) {
	try {
	    String itemId = idAttribute.getDbValueString(selectedItem.item, false);
	    return itemId;
	} catch (Exception e) {
	    ECatalog.error(e);
	    return "<error>";
	}
    }

    boolean waitFinished;
    synchronized public void waitStart() {
	waitFinished = false;
	while (!waitFinished) {
	    try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
	} 
    }   
    synchronized public void waitFinish() {
	waitFinished = true;
	notifyAll();
    }


    

    String getLogFileName(String taskId) {
	return logPath + "log_" + taskId + ".xml";
    }

    String getSurveyFileName(String id) {
	return logPath + "questionnaire_" + id + ".xml";
    }


    void questionnaireScreen(String configFileName) throws Exception {
	waitFinished = false;
	clearScreen();

	survey = new Survey(surveysConfigPath + configFileName);
	survey.addListener(new SurveyListener() { public void surveyFinished() { waitFinish(); } });

	TableLayout layout = new TableLayout(new double[][] {{TableLayout.FILL}, {TableLayout.FILL}});
	mainPanel.setLayout(layout);
	mainPanel.add(survey, "0, 0");
	mainPanel.revalidate();
	mainPanel.repaint();

	waitStart();
    }

    void questionnaireScreen(String configFileName, String id) throws Exception {

	experimentLog.genericInfo("step", new String[][] {{"id", "questionnaire"}, {"questionnaireId", id}, {"state", "start"}});
	questionnaireScreen(configFileName);
	experimentLog.genericInfo("step", new String[][] {{"id", "questionnaire"}, {"questionnaireId", id}, {"state", "end"}});

	if (id != null) {
	    String fileName = getSurveyFileName(id);
	    survey.saveResult(fileName);
	    //experimentLog.genericInfo("step", new String[][] {{"id","survey"}, {"surveyId", id}, {"fileName", fileName}});
	}
    }


    static void fixWindowSize(JFrame frame)
    {
	Dimension dim = frame.getSize();

	Dimension maxDim = Toolkit.getDefaultToolkit().getScreenSize();
	//Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(...);
	Insets insets = new Insets(20,20,20,20);
	maxDim.width = (maxDim.width - (insets.left + insets.right))*3/4;
	maxDim.height = (maxDim.height - (insets.top + insets.bottom))*35/40;
		
	//dim.width = Math.min(dim.width, maxDim.width);
	//dim.height = Math.min(dim.height, maxDim.height);
	dim = maxDim;
	frame.setSize(dim);
    }



    static int getIndexSelectedButton(ButtonGroup group) {
	int index = 0;
	for (Enumeration<AbstractButton> e = group.getElements() ; e.hasMoreElements() ; index++) {
	    if (e.nextElement().isSelected())
		return index;
	}
	return -1;
    }

    static String replaceText(String text, String[][] valuePairs) {
	for (int i = 0; i < valuePairs.length; i++) {
	    Pattern pattern = Pattern.compile(valuePairs[i][0]);
	    text = pattern.matcher(text).replaceAll(valuePairs[i][1]);
	}
	return text;
    }


    class MyECatalogLog extends ECatalogLog { 
	MyECatalogLog(ECatalog ecatalog, String fileName) throws IOException {
	    super(ecatalog, fileName);
	}
	
	public void error(Throwable e) {
	    e.printStackTrace();
	    super.error(e);
	    JOptionPane.showMessageDialog(frame, "Ask David. Error: " + e.toString(), "ERROR. Ask David", JOptionPane.ERROR_MESSAGE);
	}
    }

    class ExperimentLog extends utils.Log { 
	ExperimentLog(String fileName) throws IOException {
	    super("experimentLog", fileName);
	}
	
	public void error(Throwable e) {
	    e.printStackTrace();
	    super.error(e);
	    JOptionPane.showMessageDialog(frame, "Ask David. Error: " + e.toString(), "ERROR. Ask David", JOptionPane.ERROR_MESSAGE);
	}
    }


    // inits the event queue for the ClockStartDetector and the InputRecordAndPlaybackEngine
    void initEventQueue() {
	//TODO: I can install this new EventQueue (using push), but I do not know how to remove it (pop does not work)
	//      and so, as a hack, I can only install one for all the lifetime of the application.

	class MyEventQueue extends EventQueue {
	    boolean clockStarted = false;
	    protected void dispatchEvent(AWTEvent event) {
		if (clockTaskToStart != null && event instanceof MouseEvent && event.getID() == MouseEvent.MOUSE_RELEASED) {
		    clockTaskToStart.clockStart();
		    clockTaskToStart = null;
		}

		if (event instanceof InputEvent)
		    inputRecordEngine.recordInputEvent((InputEvent) event);

		super.dispatchEvent(event);
	    }
	};
	
	MyEventQueue myEventQueue = new MyEventQueue();
	Toolkit.getDefaultToolkit().getSystemEventQueue().push(myEventQueue);
    }
}



interface ItemConditionCheck {
    /* Returns null if the item satisfies the condition, or otherwise a String explaining the problem */
    String isItemValid(Item item) throws SQLException;
}


class ClockTask extends TimerTask {
    JEditorPane editorPane;
    int timeInSeconds;
    String headText, finishText;
    long startedTime;
	
    ClockTask(JEditorPane editorPane, int timeInSeconds, String headText, String finishText) {
	this.editorPane = editorPane;
	this.timeInSeconds = timeInSeconds;
	this.headText = headText;
	this.finishText = finishText;
	startedTime = -1;
    }

    void clockStart() {
	startedTime = System.currentTimeMillis();
    }

    public void run() {
	String text = "<font size=\"+1\">" + headText + "<b>";

	long timeLeftInMs = timeInSeconds * 1000;

	if (startedTime != -1)
	    timeLeftInMs -= System.currentTimeMillis() - startedTime;

	long timeLeftInSeconds = timeLeftInMs / 1000;
	//boolean blink = (startedTime != -1 && timeLeftInMs >0 ) && (((timeLeftInMs / 1000) % 60) <= 6) && ((timeLeftInMs % 500) < 250);

	boolean blink = (startedTime != -1 && (((Math.abs(timeLeftInMs) / 1000) % 60) <= 6));
	boolean hide = blink && ((Math.abs(timeLeftInMs) % 500) < 250);

	if (!hide) {
	    if (timeLeftInSeconds < 0)
		text += "<font color='red'>-" + getClockText(-timeLeftInSeconds) + " " + finishText + "</font>";
	    else
		text += getClockText(timeLeftInSeconds);
	}

	text += "</b></font>";
	editorPane.setText(text);
    }

    String getClockText(long time) {  //TODO: is there a simple way to do this? maybe java.text.Format?
	String text = "";
	long min = time/60;
	if (min < 10) text += "0";
	text += String.valueOf(min);
	text += ":";
	long seconds = time - min*60;
	if (seconds < 10) text += "0";
	text += String.valueOf(seconds);

	return text;
    }
}

