/*
 * 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 java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;

import javax.swing.UIManager;

import ipipan.poliqarp.util.MRUCollection;
import ipipan.poliqarp.logic.IConfiguration;
import ipipan.poliqarp.connection.PoliqarpConnection;

/** 
 * An instance of this class represents Poliqarp's configuration -- that is,
 * series of pairs (key, value). 
 */
public class Configuration implements Cloneable, IConfiguration
{
   private Map<String, String> config;
   private MRUCollection<String> mru;
   private Locale currentLocale;
   private ResourceBundle currentBundle;

   public static final String OPT_LC = "left-context";
   public static final String OPT_RC = "right-context";
   public static final String OPT_WC = "wide-context";
   public static final String OPT_SORTBY = "sort-by";
   public static final String OPT_DISPORTH = "display-orth";
   public static final String OPT_DISPBASE = "display-base";
   public static final String OPT_DISPTAG = "display-tag";
   public static final String OPT_DISPORTHCONTEXT = "display-orth-context";
   public static final String OPT_DISPBASECONTEXT = "display-base-context";
   public static final String OPT_DISPTAGCONTEXT = "display-tag-context";
   public static final String OPT_DISPDISAMB = "display-disambiguated-tags";
   public static final String OPT_QUERYI = "query-i";
   public static final String OPT_QUERYX = "query-x";
   public static final String OPT_METAI = "meta-i";
   public static final String OPT_METAX = "meta-x";
   public static final String OPT_BOLD = "bold-lemmas";
   public static final String OPT_FONTSIZE = "gui-font-size";
   public static final String OPT_SERVER = "server";
   public static final String OPT_PORT = "port";
   
   /** 
    * Constructs a new, empty <code>Configuration</code>. 
    */
   public Configuration() 
   {
      config = new HashMap<String, String>();
      mru = new MRUCollection<String>();
      currentBundle = null;
   }

   /** 
    * Constructs a configuration by loading its description from the given 
    * file and sets the language bundle as needed. 
    * @throws FileNotFoundException if the file cannot be open for reading.
    * @throws IOException if there was an error reading the file.
    */
   public Configuration(String fileName) 
      throws FileNotFoundException, IOException
   {
      this();
      FileReader reader = new FileReader(fileName);
      StreamTokenizer st = new StreamTokenizer(reader);
      st.resetSyntax();
      st.eolIsSignificant(false);
      st.whitespaceChars(0, 0x20);
      st.wordChars(33, (int)'\u0400');
      st.quoteChar((int)'"');
      
      int token = st.TT_WORD;
      
      while ((token = st.nextToken()) != st.TT_EOF) {
         if (token == st.TT_WORD) {
            String command = st.sval;
            if (command.equals("set")) {
               token = st.nextToken();
               if (token != st.TT_WORD && token != (int)'"') {
                  System.err.println("Warning: Incorrect token detected: " + 
                     token);
                  break;
               }
               String key = st.sval;
               token = st.nextToken();
               if (token != st.TT_WORD && token != (int)'"') {
                  System.err.println("Warning: Incorrect token detected: " + 
                     token);
                  break;
               }
               String value = st.sval;
               addConfigItem(key, value);
            } else {
               System.err.println("Warning: Incorrect command: " + command);
            }
         }
      }

      try {
         setLanguage(getConfigItem("language"));
      } catch (NoSuchElementException e) {
         setLanguage("English");
      }

      reader.close();
      setMissingToDefault();
      makeMRU();
   }

   /**
    * Sets a value for an option if it isn't already set.
    * @param key name of the option
    * @param value value of the option
    */
   public void setDefault(String key, String value) 
   {
      try {
         getConfigItem(key);
      } catch (NoSuchElementException e) {
         addConfigItem(key, value);
      }
   }
   
   /**
    * Set options not mentioned in the configuration file to their default
    * values.
    */
   public void setMissingToDefault() 
   {
      setDefault(OPT_LC, "5");
      setDefault(OPT_RC, "5");
      setDefault(OPT_WC, "50");
      setDefault(OPT_DISPORTH, "yes");
      setDefault(OPT_DISPORTHCONTEXT, "yes");
      setDefault(OPT_DISPBASE, "yes");
      setDefault(OPT_DISPBASECONTEXT, "no");
      setDefault(OPT_DISPTAG, "yes");
      setDefault(OPT_DISPTAGCONTEXT, "no");
      setDefault(OPT_DISPDISAMB, "yes");
      setDefault(OPT_QUERYI, "no");
      setDefault(OPT_QUERYX, "no");
      setDefault(OPT_METAI, "yes");
      setDefault(OPT_METAX, "yes");
      setDefault(OPT_SORTBY, "");
      setDefault(OPT_SERVER, "127.0.0.1");
      setDefault(OPT_PORT, "4567");
      setDefault(OPT_FONTSIZE, "11");
   }

   /** 
    * Assigns the list of last opened corpora as a <code>MRUCollection</code>
    * of strings to mru. 
    */
   private void makeMRU()
   {
      mru = new MRUCollection<String>();
      for (int i = 9; i >= 0; i--) {
         try {
            mru.add(getConfigItem("mru_" + i));
         } catch (NoSuchElementException e) {}
      }
   }

   /**
    * Returns the list of most recently used corpora.
    */
   public MRUCollection<String> getMRU() 
   {
      return mru;
   }

   /** 
    * Returns the name (in the current language) of the menu element bearing
    * the given ID.
    * @param componentId the identifier of the menu component
    * @return the component's name
    */
   public String getMenuComponentName(int componentId) {
      return getComponentName("MENU_" + componentId);
   }

   /** 
    * Returns the current content of a given resource string.
    * @param name the identifier of a resource string
    * @return the value of the string
    */
   public String getComponentName(String name) 
   {
      return currentBundle.getString(name);
   }

   /** 
    * Adds a configuration option to this configuration, or modifies it. 
    * @param key the name of the option
    * @param value the new value
    */
   public void addConfigItem(String key, String value) 
   {
      config.put(key, value);
   }

   /** 
    * Retrieves the value associated with the given option name.
    * @param key the name of the option
    * @return the value of the option
    * @throws NoSuchElementException if there is no value associated with 
    * the option
    */
   public String getConfigItem(String key) throws NoSuchElementException 
   {
      String res = config.get(key);
      if (res == null)
         throw new NoSuchElementException();
      return res;
   }

   /** 
    * Retrieves the numeric value associated with the given option name.
    * @param key the name of the option
    * @return the value of the option
    * @throws NoSuchElementException if there is no value associated with 
    * the option
    */
   public int getConfigItemNum(String key) throws NoSuchElementException 
   {
      int result = 0;
      try {
         result = Integer.parseInt(getConfigItem(key));
      } catch (NumberFormatException exn) {}
      return result;
   }
   
   /** 
    * Sets the current language.
    * @param language the name of the language to set.
    */
   public void setLanguage(String language) 
   {
      addConfigItem("language", language);
      String localeName;
      if (language.toLowerCase().equals("english")) 
         localeName = "en";
      else if (language.toLowerCase().equals("polish")) 
         localeName = "pl";
      else
         localeName = "en";
      currentLocale = new Locale(localeName);
      Locale.setDefault(currentLocale);
      try {
         UIManager.setLookAndFeel(UIManager.getLookAndFeel());
         // force the UI manager to reset its internal locale settings,
         // so that the built-in dialogs will use the new language
      } catch (javax.swing.UnsupportedLookAndFeelException e) {}
      currentBundle = ResourceBundle.getBundle("poliqarp", currentLocale);
   }

   /** 
    * Saves this configuration to a file.
    * @throws IOException if there was an error saving the file
    */
   public void writeConfig(String fileName) throws IOException 
   {
      updateMRU();
      FileWriter f = new FileWriter(fileName);
      for (Map.Entry<String, String> entry : config.entrySet()) {
         String key = entry.getKey();
         String value = entry.getValue();
         value = value.replaceAll("\"", "\\\\\"");
         value = value.replaceAll("\n", "\\\\n");
         value = value.replaceAll("\t", "\\\\t");
         value = value.replaceAll("\\\\", "\\\\\\\\");
         f.write("set " + key + " \"" + value + "\"\n");
      }
      f.close();
   }

   private void updateMRU() 
   {
      int i = 0;
      for (String s : mru) {
         addConfigItem("mru_" + i, s);
         i++;
         if (i == 10) break;
      }
   }

   /** 
    * Returns a copy of this configuration. 
    */
   public Object clone() 
   {
      Configuration res = new Configuration();
      copyTo(res);
      return (Object)res;
   }

   /**
    * Copies the contents of this configuration to another configuration 
    * object.
    */
   public void copyTo(Configuration cfg)
   {
      cfg.config = new HashMap<String, String>(config);
      cfg.currentBundle = currentBundle;
      cfg.mru = mru;
   }

   /** 
    * Returns the current locale. 
    */
   public Locale getCurrentLocale() 
   {
      return currentLocale;
   }

   private void safeSet(PoliqarpConnection conn, String message) 
      throws Exception
   {
      conn.send("SET " + message);
      if (!conn.getMessage().isOK())
         throw new Exception();
   }
   
   /**
    * Sends the server-pertaining elements of this Configuration
    * to the server.
    * @param connection connection to the server
    */
   public void updateConfig(PoliqarpConnection connection)
   {
      try {
         safeSet(connection, "left-context-width " + getConfigItem(OPT_LC));
         safeSet(connection, "right-context-width " + getConfigItem(OPT_RC));
         safeSet(connection, "wide-context-width " + getConfigItem(OPT_WC));
         safeSet(connection, "retrieve-lemmata 1111");
         safeSet(connection, "retrieve-tags 1111");
         boolean
            disamb = getConfigItem(OPT_DISPDISAMB).equals("yes"),
            queryi = getConfigItem(OPT_QUERYI).equals("yes"),
            queryx = getConfigItem(OPT_QUERYX).equals("yes"),
            metai = getConfigItem(OPT_METAI).equals("yes"),
            metax = getConfigItem(OPT_METAX).equals("yes");
         connection.getOptions().setQueryFlags(queryi, queryx, metai, metax);
         connection.getOptions().setAmbiguationDisplay(disamb);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
