/*
 * @(#)KSDBStartup.java	0.1 2001/08/27
 *
 * Copyright 2001 by Carnegie Mellon, All rights reserved.
 */

package ksdb;
import java.awt.*;
import java.awt.event.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import javax.swing.*;
import javax.swing.text.JTextComponent;

import edu.cmu.lti.kantoo.analyzer.DisambigDialog;
import edu.cmu.lti.kantoo.network.SQLConnection;
import edu.cmu.lti.kantoo.shared.*;


/**
 * KSDBStartup authenticates the user to the KSDB
 *
 * @author  David Svoboda
 * @version 0.1 2001/08/27
 */
public class KSDBStartup extends JFrame {

  private PropertyManager properties = new PropertyManager(KSDBStartup.class);

  /**
   * @return properties
   */
  public PropertyManager getProperties() {return properties;}


  /**
   * Keep track of all testers
   */
  java.util.List testers = new ArrayList();

  /**
   * ...but only one rewriter is possible
   */
  Rewriter rewriter = null;

  /**
   * The current KSDB. Passed to rewriters & testers
   */
  Ksdb ksdb = null;


  JTextField userField;

  public KSDBStartup(String title) {
    super(title);

    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.setBorder(BorderFactory.createTitledBorder("User Name"));
    getContentPane().add( panel);

    userField = new JTextField( System.getProperty("user.name"));
    panel.add(userField);

    // install the command table
    commands = PropertyActionMap.createActionMap( defaultActions);

    // Create menubar and toolbar 
    JMenuBar mb = new JMenuBar();
    new PropertyActionMap(getProperties(), "menubar", commands).addtoMenubar( mb);
    setJMenuBar(mb);
  }


  /**
   * Main procedure
   */
  public static void main(String[] args) {
    PropertyManager.getArgs( args);

    final KSDBStartup td = new KSDBStartup("KSDB Startup");
    new PropertyActor( td.properties, "startup").loadDimensions( td);
    td.addWindowListener(new WindowAdapter() {
	public void windowClosing(WindowEvent w) {td.close();}
      });
    td.setVisible(true);
  }

  /**
   * the set of commands we support
   */
  private ActionMap commands;

  private Action relinquishAction = new RelinquishAction();

  /**
   * Actions defined by the Notepad class
   */
  private Action[] defaultActions = {
    new ExitAction(),
    relinquishAction,
    new DocumentAction(),
    new QueryAction(),
    new HelpAction(),
  };

  /**
   * Relinquish-lock action
   */
  class RelinquishAction extends AbstractAction {

    RelinquishAction() {super("relinquish");}

    public void actionPerformed(ActionEvent e) {
      try {
	Ksdb ksdb = new Ksdb( userField.getText());
	ksdb.unlockSentences( false);
	ksdb.unlockSentences( true);
      } catch (Exception x) {ReportError.warning(x);}
    }
  }

  private TextDialog helpDialog = new TextDialog("Help", getProperties());

  class HelpAction extends AbstractAction {
    public HelpAction() {super("help");}

    public void actionPerformed(ActionEvent e) {
      helpDialog.setURL( KSDBStartup.class.getResource( KSDBStartup.class.getName() + ".html"));
      helpDialog.setState( Frame.NORMAL);
      helpDialog.setVisible( true);
    }
  }


  private java.sql.ResultSet documents = null;

  private Vector documentNames = null;

  /**
   * @return list of all documents in KSDB
   */
  private Vector getDocList(Ksdb ksdb) {
    if (documentNames != null) return documentNames;
    documentNames = new Vector();
    if (documents == null)
      documents = ksdb.select("title, filename, id FROM Document ORDER BY filename");
    SQLConnection.beforeFirst( documents);
    while (SQLConnection.next( documents)) {
      String title = SQLConnection.getString( documents, 1);
      if (title == null)
	title = SQLConnection.getString( documents, 2); // use filename if no title given
      documentNames.addElement( title);
    }
    return documentNames;
  }


  /**
   * Action to browse a document
   */
  class DocumentAction extends AbstractAction {

    DocumentAction() {super("document");}

    public void actionPerformed(ActionEvent e) {
      try {
	if (ksdb == null) {
	  ksdb = new Ksdb( userField.getText());
	  userField.setEnabled( false); // don't let user change KSDB anymore
	}

	final int d = DisambigDialog.showListDialog( ReportError.getAppParent(),
					       "Select a document to browse:",
					       "Document Chooser",
					       getDocList(ksdb),
					       -1);
	if (d == -1) return;
	documents.absolute(d+1);
	final int document = documents.getInt(3);
	Thread worker = new Thread() {
	    public void run() {
	      try {
		ksdb.lockSentences( false);
		ksdb.lockSentences( true);
		rewriter = Rewriter.createFrame( ksdb, document, null);
		relinquishAction.setEnabled( false);
		rewriter.getFrame( rewriter.getText()).setTitle("Rewriting document " + documentNames.elementAt(d));
		rewriter.getFrame( rewriter.getText()).setVisible( true);
		rewriter.setKSDBStartup( KSDBStartup.this);
	      } catch (Exception x) {ReportError.warning(x);}
	    }};
	worker.start();
      } catch (Exception x) {ReportError.warning(x);}
    }
  }



  // Construct a wizard using a component
  static private JPanel makeWizard(JComponent component, String title,
				   ActionListener actionListener) {
    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.setBorder( BorderFactory.createTitledBorder( title));
    panel.add( component);

    // sigh
    if (component instanceof JTextField)
      ((JTextField) component).addActionListener( actionListener);
    if (component instanceof JComboBox)
      ((JComboBox) component).addActionListener( actionListener);

    return panel;
  }

  // Construct a JComboBox consisting of the results of a selection
  static private JComboBox makeComboBox(Ksdb ksdb, String selection) {
    JComboBox box = new JComboBox();
    box.setEditable( true);
    box.addItem("");
    ResultSet rs = ksdb.select( selection);
    while (SQLConnection.next(rs)) box.addItem( SQLConnection.getString(rs,1));
    return box;
  }

  static private boolean editFlag = false;

  /**
   * Show an OK-RESET-CANCEL dialog, providing an editable multiline 
   * text widget. Returns NULL if or user closes window, or hits Cancel.
   * The current contents of the text widget are remembered, and reset
   * if the user presses RESET.
   *
   * @param parentComponent Determines the Frame in which the dialog is
   *			    displayed. If null, or if the parentComponent
   *                        has no Frame, a default Frame is used.
   * @param message         the Object to display; may be null
   * @param title	    the title string for the dialog; may be null
   * @param text            Text widget
   * @return	            String contents of widget, or null if cancelled.
   */
  public static String showTextDialog(Component parentComponent,
				      Object message, String title,
				      JTextComponent text, Ksdb ksdb) {
    if (parentComponent == null)
      parentComponent = new Frame();
    if (message == null)
      message = new String("Please provide some text:");
    if (title == null)
      title = new String("Text Dialog");

    JScrollPane scrollPane = new JScrollPane( text);
    scrollPane.setPreferredSize(new Dimension(250, 70));

    final String initialText = text.getText();
    final JTextComponent t = text;
    JButton reset = new JButton(new AbstractAction("Reset") {
	public void actionPerformed(ActionEvent e) {t.setText( initialText);
	}});

    // Wizards!

    // This wizard selects sentences that match a given regex
    final JTextField regexpText = new JTextField();
    JPanel regexp = makeWizard( regexpText,"Sentences that match this regexp:",
      new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  String oldText = t.getText();
	  int where = oldText.indexOf("WHERE ");
	  String clause = "text REGEXP '" + regexpText.getText() + "'";
	  StringBuffer newText = new StringBuffer( oldText);
	  if (where == -1)
	    newText.append(" WHERE " + clause);
	  else
	    newText.insert( where + "WHERE ".length(), clause + " AND ");
	  t.setText( newText.toString());
	  regexpText.setText("");
	}});

    // This wizard selects sentences from a certain document
    final JComboBox docChoice = makeComboBox( ksdb, "filename FROM Document ORDER BY filename");
    JPanel doc = makeWizard( docChoice, "Sentences from this document:",
			     new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  String choice = (String) docChoice.getSelectedItem();
	  if (choice.length() == 0) return;
	  String oldText = t.getText();
	  int where = oldText.indexOf("WHERE ");
	  String clause = "Context.document=Document.id"
	    + " AND Context.sentence=Sentence.id"
	    + " AND Document.filename=" + SQLConnection.quote( choice);
	  StringBuffer newText = new StringBuffer( oldText);
	  if (where == -1)
	    newText.append(" WHERE " + clause);
	  else
	    newText.insert( where + "WHERE ".length(), clause + " AND ");

	  // Finally make sure we are selecting from a document
	  if (oldText.indexOf("Document, Context") == -1)
	    newText.insert( oldText.indexOf("FROM ") + "FROM ".length(),
			    "Document, Context, ");
	  t.setText( newText.toString());
	  docChoice.setSelectedItem( "");
	}});

    // This wizard selects sentences from documents from a certain suite
    final JComboBox suiteChoice = makeComboBox( ksdb, "name FROM Suite ORDER BY name");
    JPanel suite = makeWizard( suiteChoice, "Sentences from this suite:",
			       new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  String choice = (String) suiteChoice.getSelectedItem();
	  if (choice.length() == 0) return;

	  String oldText = t.getText();
	  int where = oldText.indexOf("WHERE ");
	  String clause = "Context.document=Document.id"
	    + " AND Context.sentence=Sentence.id"
	    + " AND Suite_Document.suite=Suite.id"
	    + " AND Suite_Document.document=Document.id"
	    + " AND Suite.name='" + choice + "'";
	  StringBuffer newText = new StringBuffer( oldText);
	  if (where == -1)
	    newText.append(" WHERE " + clause);
	  else
	    newText.insert( where + "WHERE ".length(), clause + " AND ");

	  // Finally make sure we are selecting from a document and suite
	  if (oldText.indexOf("Document, Context") == -1)
	    newText.insert( oldText.indexOf("FROM ") + "FROM ".length(),
			    "Document, Context, ");
	  if (oldText.indexOf("Suite, Suite_Document") == -1)
	    newText.insert( oldText.indexOf("FROM ") + "FROM ".length(),
			    "Suite, Suite_Document, ");
	  t.setText( newText.toString());
	  suiteChoice.setSelectedItem( "");
	}});


    while (true) {
      Object[] options = new Object[] { "Test", "Edit", "Cancel"};
      final JOptionPane pane
	= new JOptionPane( new Object[] { message, scrollPane,
					  reset, regexp, doc, suite },
			   JOptionPane.QUESTION_MESSAGE,
			   JOptionPane.DEFAULT_OPTION, null, options);

      pane.createDialog( parentComponent, title).show();

      Object selectedValue = pane.getValue();
      if (selectedValue == null) // user closed dialog
	return null;
      if (selectedValue == options[2])
	return null;

      editFlag = (selectedValue == options[1]);
      if (regexpText.getText().length() == 0) break;

      // User left stuff in the regex wizard. Get user confirmation
      if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( pane, "Your REGEXP field is non-empty. Are you sure you wish to ignore its contents?", "Confirmation Required", JOptionPane.YES_NO_OPTION)) break;
    }

    return text.getText();
  }


  class QueryAction extends AbstractAction {

    QueryAction() {super("query");}

    public void actionPerformed(ActionEvent e) {
      try {
	if (ksdb == null) {
	  ksdb = new Ksdb( userField.getText());
	  userField.setEnabled( false); // don't let user change KSDB anymore
	}

	JTextArea text = new JTextArea("SELECT Sentence.id, Sentence.text, Sentence.previous FROM Sentence");
	text.setLineWrap( true);
	text.setCaretPosition( text.getText().length());
	String query = showTextDialog(ReportError.getAppParent(),
				      "Construct a query for the KSDB.",
				      "Query Constructor",
				      text, ksdb);
	if (query == null) return;

	final String finalQuery
	  = query.substring( "SELECT ".length(), query.length());
	int where = finalQuery.indexOf("WHERE");
	final String title = ((where == -1) ? "All Sentences" : "Browsing sentences: " + finalQuery.substring( where, finalQuery.length() - 1));
	relinquishAction.setEnabled( false);
	Thread worker = new Thread() {
	    public void run() {
	      try {
		if (editFlag) {
		  ksdb.lockSentences( false);
		  ksdb.lockSentences( true);
		  rewriter = Rewriter.createFrame( ksdb, 0, finalQuery);
		  rewriter.getFrame( rewriter.getText()).setTitle("Rewriting " + title);
		  rewriter.getFrame( rewriter.getText()).setVisible( true);
		  rewriter.setKSDBStartup( KSDBStartup.this);
		} else {
		  ksdb.lockSentences( false);
		  Tester tester = Tester.createFrame( "Testing " + title,
						      ksdb, 0, finalQuery);
		  tester.getFrame().setVisible( true);
		  tester.setKSDBStartup( KSDBStartup.this);
		  testers.add( tester);
		}
	      } catch (Exception x) {ReportError.warning(x);}
	    }};
	worker.start();
      } catch (Exception x) {ReportError.warning(x);}
    }
  }

  class ExitAction extends AbstractAction {

    ExitAction() {super("exit");}

    public void actionPerformed(ActionEvent e) {
      Thread worker = new Thread() {
	  public void run() {
	    for (Iterator iterator = testers.iterator(); iterator.hasNext();) {
	      Tester tester = (Tester) iterator.next();
	      tester.close();
	    }
	    if (rewriter != null)
	      rewriter.close( false);
	    properties.store();
	    System.exit(0);
	  }};
      worker.start();
    }
  }

  public void close() {new ExitAction().actionPerformed(null);}

  public void close(Tester tester) {
    if (this.testers.remove( tester) == false)
      ReportError.bug(new Error(), "Closed Tester not listed!");
    if (ksdb != null && this.testers.isEmpty())
      try {ksdb.unlockSentences( false);} catch (SQLException x) {ReportError.warning(x);}
    if (this.testers.isEmpty() && this.rewriter == null)
      relinquishAction.setEnabled( true);
  }

  public void close(Rewriter rewriter) {
    if (this.rewriter != rewriter)
      ReportError.bug(new Error(), "Wrong rewriter closed!");
    this.rewriter = null;
    if (ksdb != null) {
      try {ksdb.unlockSentences( true);} catch (SQLException x) {ReportError.warning(x);}
      if (this.testers.isEmpty())
	try {ksdb.unlockSentences( false);} catch (SQLException x) {ReportError.warning(x);}
    }
    if (this.testers.isEmpty() && this.rewriter == null)
      relinquishAction.setEnabled( true);
  }
}

