/*
 * 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.logic;

import java.io.*;
import java.util.List;
import java.util.Map;

import ipipan.poliqarp.connection.AsyncHandler;
import ipipan.poliqarp.connection.Message;
import ipipan.poliqarp.connection.PoliqarpConnection;

import ipipan.poliqarp.util.Pair;

/**
 * A <code>Corpus</code> object represents a request to the server that a
 * corpus be opened. If the request succeeds, it becomes a full-blown corpus
 * object which can be queried about number of segments, etc.
 */
public final class Corpus extends ServerJob implements AsyncHandler
{
   /**
    * A <code>Corpus</code> can be in one of three states.
    */
   public enum State {
      /**
       * The corpus is being opened. 
       */
      OPENING,

      /**
       * The corpus has been opened successfully.
       */
      OPENED,

      /**
       * The corpus has not been opened.
       */
      NOT_OPENED
   };

   private String name;
   private State state;
   private List<Pair<MetaData.Type, String> > metaTypes = null;
   
   /**
    * Handles the <code>OPENED</code> and <code>OPENFAIL</code>
    * asynchronous messages from the server.
    *
    * @param message the asynchronous message.
    */
   public void handle(String message)
   {
      if (message.equals("OPENED")) {
         state = State.OPENED;
         connection.setAsyncHandler(null);
         launchNotifier();
      } else if (message.startsWith("OPENFAIL")) {
         state = State.NOT_OPENED;
         connection.setAsyncHandler(null);
         launchNotifier();
      } else 
         System.err.println("Unknown async message: " + message);
   }

   /**
    * Sends a request to open a corpus to the server and constructs the
    * <code>Corpus</code> object representing that request.
    *
    * @param conn an open connection to the server
    * @param corpusName base name of the corpus
    * @param notifier a <code>Runnable</code> executes when the process of 
    * corpus opening is finished; can also be null to indicate a no-op
    * @throws CorpusNotOpenedException if the server refused to create the
    * request
    */
   public Corpus(PoliqarpConnection conn, String corpusName, Runnable notifier)
      throws CorpusNotOpenedException
   {
      super(conn);
      name = corpusName;
      state = State.OPENING;
      setNotifier(notifier);
      conn.setAsyncHandler(this);
      try {
         synchronized (this) {
            conn.send("OPEN " + corpusName);
            Message reply = conn.getMessage();
            if (!reply.isOK())
               throw new CorpusNotOpenedException();
         }
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
   }

   /**
    * Returns the statistics of this corpus.
    * @throws CorpusNotOpenedException if this object doesn't represent an
    * open corpus
    */
   public CorpusStatistics getStatistics() throws CorpusNotOpenedException
   {
      if (state != State.OPENED)
         throw new CorpusNotOpenedException();
      try {
         connection.send("CORPUS-STATS");
         Message reply = connection.getMessage();
         if (!reply.isOK())
            throw new CorpusNotOpenedException();
         return new CorpusStatistics(
            reply.getOKIntInfo(0), reply.getOKIntInfo(1),
            reply.getOKIntInfo(2), reply.getOKIntInfo(3));
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
   }

   /** 
    * Returns the last error that occurred while compiling a query
    * on this corpus, or null if there were no error.
    */
   public String getLastError() throws CorpusNotOpenedException
   {
      try {
         connection.send("GET-LAST-ERROR");
         String reply = connection.getMessage().toString();
         if (reply.startsWith("ERROR ")) {
            return reply.substring("ERROR ".length());
         } else
            return null;
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
   }

   /**
    * Returns the list of kinds of metadata that can be defined for this corpus
    */
   public List<Pair<MetaData.Type, String> > getMetaTypes()
      throws CorpusNotOpenedException
   {
      if (metaTypes != null)
         return metaTypes;
      if (state != State.OPENED)
         throw new CorpusNotOpenedException();
      List<Pair<MetaData.Type, String>> result = 
         new java.util.LinkedList<Pair<MetaData.Type, String>>();
      try {
         connection.send("METADATA-TYPES");
         Message reply = connection.getMessage();
         if (!reply.isOK())
            throw new CorpusNotOpenedException();
         int num = reply.getOKIntInfo(0);
         for (int i = 0; i < num; i++) {
            String value = connection.getMessage().toString();
            String name = value.substring(2);
            MetaData.Type type = null;
            switch (value.charAt(0)) {
               case 'T':
                  type = MetaData.Type.TEXT;
                  break;
               case 'D':
                  type = MetaData.Type.DATE;
                  break;
               default:
                  throw new CorpusNotOpenedException();
            }
            result.add(new Pair<MetaData.Type, String>(type, name));
         }
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
      metaTypes = result;
      return result;
   }

   /**
    * Returns the set of aliases currently defined for this corpus
    * as a <code>Map</code> from strings to strings.
    */
   public Map<String, String> getAliases() throws CorpusNotOpenedException
   {
      if (state != State.OPENED)
         throw new CorpusNotOpenedException();
      Map<String, String> result = new java.util.HashMap<String, String>();
      try {
         connection.send("GET-ALIASES");
         Message reply = connection.getMessage();
         if (!reply.isOK())
            throw new CorpusNotOpenedException();
         int num = reply.getOKIntInfo(0);
         for (int i = 0; i < num; i++) {
            String name = connection.getMessage().toString();
            String value = connection.getMessage().toString();
            result.put(name, value);
         }
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
      return result;
   }

   /**
    * Defines an alias for this corpus.
    * @param name Name of the alias.
    * @param value Value of the alias.
    * @throws CorpusNotOpenedException if the operation failed.
    */
   public void defineAlias(String name, String value) 
      throws CorpusNotOpenedException
   {
      if (state != State.OPENED)
         throw new CorpusNotOpenedException();
      try {
         connection.send("CREATE-ALIAS " + name + " " + value);
         Message reply = connection.getMessage();
         if (!reply.isOK())
            throw new CorpusNotOpenedException();
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
   }

   /**
    * Deletes an alias that is currently defined for this corpus.
    * @param name Name of the alias.
    * @throws CorpusNotOpenedException if the operation failed.
    */
   public void deleteAlias(String name) 
      throws CorpusNotOpenedException
   {
      if (state != State.OPENED)
         throw new CorpusNotOpenedException();
      try {
         connection.send("DELETE-ALIAS " + name);
         Message reply = connection.getMessage();
         if (!reply.isOK())
            throw new CorpusNotOpenedException();
      } catch (IOException e) {
         throw new CorpusNotOpenedException();
      }
   }

   /**
    * Returns the current state of this corpus.
    */
   public State getState() 
   {
      return state;
   }

   /**
    * Closes this corpus, thrusting it into <code>NOT_OPENED</code> state.
    *
    * @throws CorpusNotOpenedException if the corpus is not opened.
    */
   public void close() throws CorpusNotOpenedException
   {
      try {
         if (state == State.OPENED) {
            connection.send("CLOSE");
            Message msg = connection.getMessage();
            if (!msg.isOK()) {
               System.err.println(msg.toString());
               throw new CorpusNotOpenedException();
            }
         } else {
            throw new CorpusNotOpenedException();
         }
      } catch (Exception e) {
         throw new CorpusNotOpenedException();
      }
      state = State.NOT_OPENED;
   }

   /**
    * Returns the name of this corpus.
    */
   public String getName()
   {
      return name;
   }
/*
   public static void main(String[] args)
   {
      try {
         PoliqarpConnection conn = new PoliqarpConnection("localhost", 4567);
         Corpus c = new Corpus(conn, args[0]);
         while (c.getState() == State.OPENING) {
            System.out.print(".");
            Thread.sleep(100);
         }
         switch (c.getState()) {
            case OPENED: 
               System.out.println(":-)"); 
               Query q = new Query(conn, "[base=sztuka]");
               System.out.println("query created, searching...");
               q.launch(1000);
               while (q.isRunning()) {
                  System.out.print(".");
                  Thread.sleep(100);
               }
               MatchList ml = q.getMatchList();
               System.out.println("done, " + ml.count() + " results found.");
               System.out.println("Match 0: ");
               System.out.println(ml.getMatch(0).toString());
               break;
            case NOT_OPENED: 
               System.out.println(":-("); 
               break;
         }
         conn.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
*/
}
