/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2004-2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation and appearing in the file gpl.txt included in the packaging
 * of this file.  (See http://www.gnu.org/licenses/translations.html for
 * unofficial translations.)
 * 
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

package ipipan.poliqarp.gui;

import ipipan.poliqarp.stat.Tagset;
import java.io.*;
import java.awt.event.*;
import java.util.Set;
import java.util.HashSet;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.table.*;

import com.jgoodies.forms.layout.*;
import com.jgoodies.looks.plastic.*;

import ipipan.poliqarp.connection.PoliqarpConnection;
import ipipan.poliqarp.logic.Corpus;
import ipipan.poliqarp.logic.CorpusStatistics;
import ipipan.poliqarp.logic.CorpusNotOpenedException;
import ipipan.poliqarp.logic.Query;
import ipipan.poliqarp.logic.QueryException;
import ipipan.poliqarp.stat.StatQueryException;

/** 
 * An instance of this class represents a working Poliqarp application. It is
 * responsible for creating and maintaining all the components and listening
 * to window events.
 */
public final class Application extends WindowAdapter
{
   private Configuration config;
   private JFrame mainFrame;
   private Set<MultilingualEntity> multilingual;
   private PoliqarpMenuBar menuBar;
   private StatusPanel statusPanel;
   private JComboBox queryField;
   private MultifunctionButton executeButton;
   private ResultTable resultTable;
   private MetaDataTable metaTable;
   private ContextArea contextArea;
   private JScrollPane bottomPane;
   private ErrorTranslator errorTranslator;
   private boolean contextAreaVisible;

   private ButtonActionListener bal;
   private PoliqarpConnection connection;
   private Corpus corpus;
   private MetaDataConstraintSet constraints;
   private Query query;
   private Timer timer;
   private JPopupMenu sortMenu;
   private int popupSortColumn;
   private File lastSaveDir = null;
   
   private Tagset tagset;
   
   private static final int MAX_HISTORY_SIZE = 100;

   private static final String configFileName = ".poliqarp.cfg";
   private static final String historyFileName = ".poliqarp.his";

   private static final String[] availableEncodings = 
   { "utf-8", "iso-8859-2", "windows-1250" };

   /**
    * An ActionListener for the 'Execute' button and the sorting popup menu.
    */
   private class ButtonActionListener implements ActionListener
   {
      public void actionPerformed(ActionEvent e) 
      {
         if (e.getSource() == executeButton) {
            switch (executeButton.getFunction()) {
               case 0: launchQuery(); break;
               case 1: cancelQuery(); break;
            }
         } else {
            int what = Integer.parseInt(e.getActionCommand());
            sortResults(popupSortColumn, what == 0 || what == 1, 
               what == 0 || what == 2);
         }
      }
   }

   private void sortResults(int column, boolean alpha, boolean ascending)
   {
      if (resultTable.getModel().isStatistical()) {
         resultTable.getModel().sortStat(column, ascending);
         return;
      }
      if (query.getHeader().length == 3 && column >= 1) // ugly! FIXME: abstract out the sorting
         column++;
      resultTable.setContentChangingEnabled(false);
      statusPanel.setButtonsEnabled(false);
      queryField.setEnabled(false);
      executeButton.setEnabled(false);
      statusPanel.setState(StatusLine.State.SORTING, null, null, config);
      query.getMatchList().sort(column, alpha, ascending, new Runnable()
      {
         public void run()
         {
            resultTable.setContentChangingEnabled(true);
            statusPanel.setButtonsEnabled(true);
            queryField.setEnabled(true);
            executeButton.setEnabled(true);
            statusPanel.setState(StatusLine.State.RESULTS, null, null, config);
            resultTable.getModel().refreshResults();
         }
      });
   }

   private void sortLetter(char letter)
   {
      resultTable.setContentChangingEnabled(false);
      statusPanel.setButtonsEnabled(false);
      queryField.setEnabled(false);
      executeButton.setEnabled(false);
      statusPanel.setState(StatusLine.State.SORTING, null, null, config);
      query.getMatchList().sortLetter(letter, new Runnable()
      {
         public void run()
         {
            resultTable.setContentChangingEnabled(true);
            statusPanel.setButtonsEnabled(true);
            queryField.setEnabled(true);
            executeButton.setEnabled(true);
            statusPanel.setState(StatusLine.State.RESULTS, null, null, config);
            resultTable.getModel().refreshResults();
         }
      });
   }

   /**
    * Constructs an Application object.
    */
   public Application()
   {
      multilingual = new HashSet<MultilingualEntity>();
   }
   
   /** 
    * Reads this application's configuration from .poliqarp.cfg. If this fails,
    * creates a configuration with default settings.
    */
   public void initConfig() 
   {
      try {
         config = new Configuration(configFileName);
      } catch (Exception e) {
         config = new Configuration();
         config.setLanguage("English");
         config.setMissingToDefault();
      }
   }

   /**
    * Updates the language of all multilingual widgets in this application
    * according to current settings.
    */
   public void updateLanguage()
   {
      for (MultilingualEntity e : multilingual)
         e.updateLanguage(config);
   }

   /**
    * Returns the configuration of this application.
    */
   public Configuration getConfig()
   {
      return config;
   }

   /**
    * Returns the corpus currently opened by this application.
    */
   public Corpus getCorpus()
   {
      return corpus;
   }

   /**
    * Toggles between context area and metadata table in the bottom pane. 
    */
   public void toggleContextVisible()
   {
      contextAreaVisible = !contextAreaVisible;
      bottomPane.setViewportView(contextAreaVisible ? contextArea :
         metaTable);
      statusPanel.updateLanguage(config);
   }
   
   /**
    * Displays a dialog box containing information about the program.
    */
   public void showAbout()
   {
      StringBuilder text = new StringBuilder(config.getComponentName(
         "TEXT_ABOUT"));
      if (connection != null) {
         text.append(config.getComponentName("TEXT_SERVER_VERSION"));
         text.append(connection.getServerVersion());
      }
      JOptionPane.showMessageDialog(mainFrame, text.toString(), 
         config.getComponentName("TEXT_ABOUTTITLE"), 
         JOptionPane.INFORMATION_MESSAGE);
   }

   /**
    * Answers whether the application is currently connected to the server.
    */
   public boolean isConnected()
   {
      return (connection != null);
   }

   /**
    * Displays a dialog box containing error information.
    */
   public void showError(String errName)
   {
      JOptionPane.showMessageDialog(mainFrame, 
         config.getComponentName("ERR_" + errName), 
         config.getComponentName("TEXT_ERROR"), 
         JOptionPane.ERROR_MESSAGE);
   }

   /**
    * Displays a custom error dialog box.
    */
   public void showCustomError(String error)
   {
      JOptionPane.showMessageDialog(mainFrame, error,
         config.getComponentName("TEXT_ERROR"), 
         JOptionPane.ERROR_MESSAGE);
   }

   private void openCorpus(String name)
   {
      final String corpusName = name;
      config.getMRU().add(corpusName);
      menuBar.updateMRU();
      tagset = new Tagset(corpusName);
      try {
         if (corpus != null)
            corpus.close();
         corpus = new Corpus(connection, corpusName, new Runnable() {
            public void run() {
               statusPanel.disableProgress();
               if (corpus.getState() == Corpus.State.OPENED)
               {
                  statusPanel.setState(StatusLine.State.CURRENT, corpusName,
                     null, config);
                  menuBar.activateCorpusOptions(true);
                  activateCorpusWidgets(true);
                  constraints = new MetaDataConstraintSet(corpus);
               }
               else
               {
                  statusPanel.setState(StatusLine.State.NOCURRENT, null, null,
                     config);
                  resultTable.getModel().clear();
                  menuBar.activateCorpusOptions(false);
                  activateCorpusWidgets(false);
                  if (new File(corpusName + ".poliqarp.corpus.image").exists())
                  {
                     String err = config.getComponentName("ERR_OPEN");
                     try {
                        err = corpus.getLastError();
                     } catch (CorpusNotOpenedException exn) { /* leave default msg */ }
                     showCustomError(errorTranslator.translate(err));
                  }
                  else
                     showError("NOCORPUS");
                  corpus = null;
               }
            }
         });
         statusPanel.setState(StatusLine.State.LOADING, corpusName, null,
            config);
         statusPanel.enableProgress(corpus);
      } catch (CorpusNotOpenedException e) {
         // FIXME: do it as it should be done
         if (new File(name + ".poliqarp.corpus.image").exists())
            showError("OPEN");
         else
            showError("NOCORPUS");
      }
   }

   /**
    * Creates the corpus opening dialog.
    */
   public void createOpenDialog() 
   {
      if (connection == null) {
         showError("NOTCONNECTED");
         return;
      }
      JFileChooser fc = new JFileChooser();
      CorpusFileFilter ff = new CorpusFileFilter(config);
      fc.addChoosableFileFilter(ff);
      fc.setAcceptAllFileFilterUsed(false);
      fc.setFileFilter(ff);
      fc.setFileHidingEnabled(false);
      if (fc.showOpenDialog(mainFrame) == fc.APPROVE_OPTION) {
         File f = fc.getSelectedFile();
         if (f.exists()) {
            String cn = f.getAbsolutePath();
            final String corpusName = cn.substring(0, cn.length() - 
               CorpusFileFilter.extensionLength);
            openCorpus(corpusName);
         } else {
            showError("NOCORPUS");
         }
      }
   }

   /**
    * Creates the result saving dialog.
    */
   public void createSaveDialog() 
   {
      if (connection == null || query == null) {
         showError("NOTCONNECTED");
         return;
      }
      SaveFileFilter htmlFF, txtFF, csvFF;
      JFileChooser fc = new JFileChooser();
      JComboBox encodingBox = new JComboBox(availableEncodings);
      htmlFF = new SaveFileFilter(config, ".*\\.html?", ".html", "TEXT_HTML");
      txtFF  = new SaveFileFilter(config, ".*\\.txt",   ".txt",  "TEXT_TXT");
      csvFF  = new SaveFileFilter(config, ".*\\.csv",   ".csv",  "TEXT_CSV");
      fc.addChoosableFileFilter(txtFF);
      fc.addChoosableFileFilter(csvFF);
      fc.addChoosableFileFilter(htmlFF);
      fc.setAcceptAllFileFilterUsed(false);
      fc.setFileFilter(txtFF);
      JPanel accessory = new JPanel();
      accessory.setLayout(new FormLayout("2dlu, default", "default:grow, default, default:grow"));
      accessory.add(encodingBox, new CellConstraints(2, 2));
      fc.setAccessory(accessory);
      if (lastSaveDir != null) {
         fc.setCurrentDirectory(lastSaveDir);
      }
      if (fc.showSaveDialog(mainFrame) == fc.APPROVE_OPTION) {
         File f = fc.getSelectedFile();
         String cn = f.getAbsolutePath();
         SaveFileFilter ff = (SaveFileFilter) fc.getFileFilter();
         ResultWriter writer = null;

         if (f.isDirectory() || !ff.accept(f)) {
            cn += ff.getExtension();
            f = new File(cn);
         }

         String encoding = (String) encodingBox.getSelectedItem();

         if (htmlFF.accept(f)) 
            writer = new HTMLResultWriter(config);
         if (txtFF.accept(f)) 
            writer = new PlainTxtResultWriter(config);
         if (csvFF.accept(f)) 
            writer = new CSVResultWriter(config); 

         if (writer == null) 
            return;
         
         /*
         JDialog dialog = new JDialog(mainFrame, "Info", true);
         JLabel l = new JLabel(config.getComponentName("TEXT_SAVING"));
         l.setBorder(BorderFactory.createEmptyBorder(10, 50, 10, 50));
         dialog.add(l);
         dialog.setDefaultCloseOperation(dialog.DO_NOTHING_ON_CLOSE);
         dialog.pack();
         */
         
         final ResultWriter fwriter = writer;
         final String fcn = cn;
         final Query fquery = query;
         final String fenc = encoding;
         // final JDialog fdlg = dialog;
         
         new Thread() {
            public void run() {
               executeButton.setEnabled(false);
               statusPanel.setState(StatusLine.State.WRITERESULTS,
                                    null, null, config);
               try {
                  fwriter.writeResults(fcn, fquery, fenc, null);
               } catch (IOException e) {
                  e.printStackTrace();
               }
               statusPanel.setState(StatusLine.State.RESULTS, null, null,
                  config);
               executeButton.setEnabled(true);
            }
         }.start();
         // dialog.setVisible(true);
         lastSaveDir = f.getParentFile();
      }
   }

   /**
    * Opens a corpus from the list of most recently used corpora.
    * 
    * @param i index of the corpus to open
    */
   public void openMRU(int i)
   {
      openCorpus(config.getMRU().get(i));
   }

   /**
    * Constructs and displays a configuration dialog.
    */
   public void createConfigDialog()
   {
      new ConfigDialog(mainFrame, this).launch();
   }

   private void launchQuery()
   {
      int index;
      
      if (query != null && query.isRunning())
         return;
      try {
         String queryText = (String)queryField.getEditor().getItem();
         queryText = queryText.trim();
         addHistory(queryText);
         // [AB] oddzielamy grupowanie od zapytania
         String groupText;
         index = queryText.indexOf(" group by ");
         if(index != -1) {
            groupText = queryText.substring(index + 10);
            queryText = queryText.substring(0, index);
         } else {
            groupText = null;
         }
 
         // [AB] - zapytanie juz obliczone, inne grupowanie
         if(query != null && groupText != null &&
            queryText.equals(query.toString())) {
            try{
               query.getMatchList().createCounter(groupText, tagset);
            } catch(StatQueryException s) {
               showError("STATQUERY");
               return;
            }
            resultTable.getModel().setData(query, query.getMatchList(), config);
            resultTable.getModel().clear();
            runMatchCounter();
            return;
         }
         // [AB] - koniec
         
         String meta = constraints.getQueryConstraint();
         if (meta != null)
         {
            index = queryText.indexOf("meta");
            if (index == -1)
               queryText = queryText + " meta " + meta;
         }
         timer = new Timer(200, new ActionListener() {
            public void actionPerformed(ActionEvent e)
            {
               query.getMatchList().updateCount();
               resultTable.getModel().updateContents();
            }
         });
         query = new Query(connection, queryText);
         try {
            query.getMatchList().createCounter(groupText, tagset);
         } catch(StatQueryException s) {
            showError("STATQUERY");
            return;
         }
         if(groupText != null) { // [AB]
            statusPanel.setState(StatusLine.State.MATCHSEARCHING, null, null,
               config);
            // config.statConfig(connection);
         } else {
            statusPanel.setState(StatusLine.State.EXECUTING, null, null,
               config);
         }
         resultTable.getModel().setData(query, query.getMatchList(), config);
         resultTable.getModel().clear();
         query.launch(new Runnable() {
            public void run()
            {
               timer.stop();
               if(query.getMatchList().getCounter() != null)
                  runMatchCounter(); // [AB]
               statusPanel.disableProgress();
               statusPanel.setState(StatusLine.State.RESULTS,
                                    null, null, config);
               resultTable.getModel().updateContents();
               menuBar.activateQueryOptions(true);
               executeButton.setFunction(0);
               executeButton.updateLanguage(config);
               String sortby = config.getConfigItem(config.OPT_SORTBY);
               if (!sortby.equals(""))
                  sortLetter(sortby.charAt(0));
            }
         });
         executeButton.setFunction(1);
         executeButton.updateLanguage(config);
         statusPanel.enableProgress(query);
         timer.start();
      } catch (QueryException e) {
         query = null;
         resultTable.getModel().clear();
         String err = null;
         try {
            err = corpus.getLastError();
         } catch (CorpusNotOpenedException exn) { /* leave default msg */ }
         if (err == null)
            err = "Incorrect or incomplete query.";
         showCustomError(errorTranslator.translate(err));
         menuBar.activateQueryOptions(false);
      }
   }
   
   private void runMatchCounter() {
      new Thread() {
         public void run() {
            try {
               executeButton.setEnabled(false);
               statusPanel.setState(StatusLine.State.MATCHCOUNTING, null, null,
                  config);
               statusPanel.enableProgress(query.getMatchList().getCounter());
               query.getMatchList().runCounter();
               statusPanel.disableProgress();
               statusPanel.setState(StatusLine.State.RESULTS, null, null,
                  config);
               executeButton.setEnabled(true);
               resultTable.getModel().updateContents();
            } catch (Exception e) {}
         }
      }.start();
   }

   private void cancelQuery()
   {
      if (query == null || !query.isRunning())
         return;
      try {
         query.abort();
         timer.stop();
         query = null;
         statusPanel.disableProgress();
         statusPanel.setState(StatusLine.State.CURRENT, corpus.getName(), null,
            config);
         resultTable.getModel().clear();
         menuBar.activateQueryOptions(false);
         executeButton.setFunction(0);
         executeButton.updateLanguage(config);
      } catch (QueryException e) {
         e.printStackTrace();
      }
   }
   
   private void writeHistory(String fileName)
   {
      try {
         PrintWriter pw = new PrintWriter(new BufferedWriter(
            new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8")));
         for (int i = 0; i < queryField.getItemCount(); i++)
            pw.println(queryField.getItemAt(i).toString());
         pw.close();
      } catch (Exception e) {}
   }

   private void readHistory(String fileName)
   {
      try {
         BufferedReader br = new BufferedReader(new InputStreamReader(
            new FileInputStream(fileName), "UTF-8"));
         String line;
         for (;;) {
            line = br.readLine();
            if (line == null)
               break;
            queryField.addItem(line);
         }
      } catch (Exception e) {}
   }

   private String getStatsDescription(int num)
   {
      return config.getComponentName("TEXT_CORPUS_STATS_" + num);
   }
   
   /**
    * Shows a dialog box with rudimentary corpus statistics.
    */
   public void showCorpusStatistics()
   {
      try {
         CorpusStatistics stats = corpus.getStatistics();
         String text = 
            getStatsDescription(1) + " " + corpus.getName() + "\n\n" + 
            getStatsDescription(2) + " " + stats.getSegments() + "\n" + 
            getStatsDescription(3) + " " + stats.getTypes() + "\n" + 
            getStatsDescription(4) + " " + stats.getLemmata() + "\n" + 
            getStatsDescription(5) + " " + stats.getTags() + "\n";
         JOptionPane.showMessageDialog(mainFrame, text,
            config.getComponentName("TEXT_CORPUS_STATS_TITLE"), 
            JOptionPane.INFORMATION_MESSAGE);
      } catch (Exception e) {}
   }

   private void addEditorListeners() 
   {
      final JTextField queryEditor = (JTextField)queryField.getEditor().
         getEditorComponent();
      queryEditor.addFocusListener(new FocusListener() {
         public void focusGained(FocusEvent e)
         {
            queryEditor.selectAll();
         }
         
         public void focusLost(FocusEvent e) {}
      });
      queryEditor.addKeyListener(new KeyAdapter() {
         public void keyTyped(KeyEvent e)
         {
            if (e.getKeyChar() == KeyEvent.VK_ENTER) {
               executeButton.doClick();
            }
         }
      });
   }

   private void createQueryPanel()
   {
      queryField = new JComboBox();
      queryField.setEditable(true);
      queryField.setEnabled(false);
      mainFrame.add(queryField, new CellConstraints(2, 2));

      executeButton = new MultifunctionButton("EXECUTE", 2);
      multilingual.add(executeButton);
      executeButton.setEnabled(false);
      executeButton.addActionListener(bal = new ButtonActionListener());
      mainFrame.add(executeButton, new CellConstraints(4, 2));
   }

   private void activateCorpusWidgets(boolean b)
   {
      queryField.setEnabled(b);
      executeButton.setEnabled(b);
      contextArea.setEnabled(b);
   }

   private void createSplitPane()
   {
      resultTable = new ResultTable(this);
      resultTable.getModel().setData(null, null, config);
      multilingual.add(resultTable);
      contextArea = new ContextArea();
      contextArea.setEnabled(false);
      metaTable = new MetaDataTable();
      multilingual.add(metaTable);
      bottomPane = new JScrollPane(contextArea);
      contextAreaVisible = true;
      resultTable.addContextSelectionListener(contextArea, metaTable);
      resultTable.getTableHeader().addMouseListener(new MouseAdapter() {
         public void mousePressed(MouseEvent e)
         {
            JTableHeader h = (JTableHeader)e.getSource();
            if (((ResultTable)h.getTable()).getModel().isStatistical())
               return;
            if (e.getButton() == e.BUTTON3 && e.isPopupTrigger() && 
               query != null && !query.isRunning())
            {
               TableColumnModel tcm = h.getColumnModel();
               int viewColumn = tcm.getColumnIndexAtX(e.getX());
               popupSortColumn = tcm.getColumn(viewColumn).getModelIndex(); 
               sortMenu.show(e.getComponent(), e.getX(), e.getY());
            }
         }
         
         public void mouseReleased(MouseEvent e)
         {
            mousePressed(e);
         }
      
         public void mouseClicked(MouseEvent e)
         {
            if (e.getButton() != e.BUTTON1 || query == null ||
               query.isRunning())
            {
               return;
            }
            JTableHeader h = (JTableHeader)e.getSource();
            TableColumnModel tcm = h.getColumnModel();
            int viewColumn = tcm.getColumnIndexAtX(e.getX()),
               column = tcm.getColumn(viewColumn).getModelIndex();
            sortResults(column, !e.isShiftDown(), true);
         }
      }); 
      JSplitPane result = new JSplitPane(JSplitPane.VERTICAL_SPLIT, 
         new JScrollPane(resultTable), bottomPane);
      result.setDividerLocation(250);
      mainFrame.add(result, new CellConstraints(2, 4, 3, 1));
   }

   private void createStatusPanel()
   {
      statusPanel = new StatusPanel(this);
      multilingual.add(statusPanel);
      resultTable.getModel().addResultEventListener(statusPanel);
      mainFrame.add(statusPanel, new CellConstraints(2, 6, 3, 1));
   }

   /**
    * Establishes a connection to the server. Shows an error message box
    * on failure.
    */

   public void connectToServer()
   {
      try { 
         connection = new PoliqarpConnection(
            config.getConfigItem(config.OPT_SERVER), 
            Integer.parseInt(config.getConfigItem(config.OPT_PORT)));
         config.updateConfig(connection);
         menuBar.disableConnectionCmd();
      } catch (Exception e) {
         showError("NOCONNECT");
      }
   }

   private void createMMI(String name, int action)
   {
      MultilingualMenuItem mi = new MultilingualMenuItem(name);
      mi.setActionCommand("" + action);
      mi.addActionListener(bal);
      multilingual.add(mi);
      sortMenu.add(mi);
   }

   private void createSortMenu()
   {
      sortMenu = new JPopupMenu();
      createMMI("SORT_ALPHA_ASC", 0);
      createMMI("SORT_ALPHA_DESC", 1);
      createMMI("SORT_ATERGO_ASC", 2);
      createMMI("SORT_ATERGO_DESC", 3);
   }

   private class ResizableFontTheme 
      extends com.jgoodies.looks.plastic.theme.ExperienceBlue
   {
      private float fontSize;

      public ResizableFontTheme(float fontSize)
      {
         this.fontSize = fontSize;
      }

      public FontUIResource getUserTextFont()
      {
         return new FontUIResource(super.getUserTextFont().deriveFont(fontSize));
      }
      
      public int getTableRowHeight()
      {
         return (int) (fontSize * 16 / 11);
      }
   }

   /**
    * Updates sizes of textual components and redraws the GUI.
    */
   public void updateSizes()
   {
      float size = Integer.parseInt(config.getConfigItem(config.OPT_FONTSIZE));
      ResizableFontTheme theme = new ResizableFontTheme(size);
      PlasticLookAndFeel.setPlasticTheme(theme);
      try {
         UIManager.setLookAndFeel(new PlasticXPLookAndFeel());
      } catch (Exception e) {}
      resultTable.setRowHeight(theme.getTableRowHeight());
      metaTable.setRowHeight(theme.getTableRowHeight());
      SwingUtilities.updateComponentTreeUI(mainFrame);
      metaTable.updateUI();
      SwingUtilities.updateComponentTreeUI(sortMenu);
      addEditorListeners();
      contextArea.setSize(size * 5 / 4);
   }
      
   /**
    * Creates the GUI and shows it.  For thread safety, this method should be 
    * invoked from the event-dispatching thread.
    */
   private void createAndShowGUI() 
   {
      PlasticLookAndFeel.setPlasticTheme(new ResizableFontTheme(10));
      try {
         UIManager.setLookAndFeel(new PlasticXPLookAndFeel());
      } catch (Exception e) {}
      mainFrame = new JFrame("Poliqarp");
      mainFrame.setIconImage(
         PoliqarpIconFactory.loadIcon("appicon").getImage());
      mainFrame.addWindowListener(this);
      mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      mainFrame.setLayout(new FormLayout("2dlu, d:grow, 2dlu, default, 2dlu",
         "2dlu, min, 2dlu, fill:default:grow, 2dlu, min, 2dlu"));

      initConfig(); 
      errorTranslator = new ErrorTranslator();
      multilingual.add(errorTranslator);
      menuBar = new PoliqarpMenuBar(this);
      multilingual.add(menuBar);
      mainFrame.setJMenuBar(menuBar.getBar());

      createQueryPanel();
      createSplitPane();
      createStatusPanel();
      createSortMenu();

      updateLanguage();
      updateSizes();
      readHistory(historyFileName);

      mainFrame.setSize(640, 480);
      mainFrame.setVisible(true);
      connectToServer();
   }

   /**
    * Voil&agrave;!
    */
   public static void main(String[] args) 
   {
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            new Application().createAndShowGUI();
         }
      });
   }

   private void addHistory(String line)
   {
      for (int i = 0; i < queryField.getItemCount(); i++)
         if (queryField.getItemAt(i).equals(line))
            queryField.removeItemAt(i);
      if (queryField.getItemCount() == MAX_HISTORY_SIZE)
         queryField.removeItemAt(MAX_HISTORY_SIZE - 1);
      queryField.insertItemAt(line, 0);
      queryField.setSelectedIndex(0);
   }

   public void windowClosing(WindowEvent e)
   {
      exitCleanup();
   }

   /**
    * Updates the server's session runtime options based on current 
    * configuration of the GUI. Refetches query results if needed. 
    */
   public void updateConfig()
   {
      if (isConnected()) {
         config.updateConfig(connection);
         resultTable.getModel().refreshResults();
      }
   }

   /**
    * Performs cleanup tasks that need to be done upon exiting. 
    */
   public void exitCleanup()
   {
      try {
         config.writeConfig(configFileName);
         writeHistory(historyFileName);
         if (corpus != null)
            corpus.close();
         if (connection != null)
            connection.close();
      } catch (Exception exn) {
      }
   }

   /**
    * Returns the set of metadata constraints.
    */
   public MetaDataConstraintSet getConstraints()
   {
      return constraints;
   }
}
