package Sight.util;

import Sight.Agents.util.Pind;

import Sight.io.Output;
import java.io.*;
import java.util.*;
/**
 * This class is used to process a list of certain
 * sequences or identifiers. It catches all
 * exceptions and outOfMemoryError and provide
 * hot restart. To get use of this, the program
 * must be executed from the batch shell which
 * simply restarts it if the exit code 55 is returned.
 * Example of the batch file (DOS):
 * :LOOP \n start "executor" /LOW /WAIT /MIN java -jar Sight_platform.jar \n if ERRORLEVEL 55 goto :LOOP
 * @author Audrius Meskauskas
 * @version 1.0
 */

public class ListProcessor implements Runnable {

  /** Only one such class can be instantiated in this Sight version. */
  public static ListProcessor instance = null;

  /** List of all items to process */
  public ArrayList list;

  /** Number of the item currently being processed. */
  public int cursor = 0;

  /** Creates new (empty) list processor. Used if the method
   *  next() is overriden. */
  public ListProcessor()
   {
     if (instance!=null)
       throw new java.lang.UnsupportedOperationException(
        "Only one instance of ListProcessor is allowed in this Sight version.");
     instance = this;
   };

  /** Read the task identifiers from the given reader. */
  public void setList(Reader r) throws java.io.IOException
   {
     setList(new BufferedReader(r));
   };

  /** Read the task identifiers from the given reader. */
  public void setList(File r) throws FileNotFoundException, java.io.IOException
   {
     setList(new BufferedReader(new FileReader(r)));
   };

  /** Read the task identifiers from the given BufferedReader. */
  public void setList(BufferedReader r)
   throws java.io.IOException
   {
    reader = r;
     // Try to perform hot restart first:

    BufferedReader rr = null;
    try {
    rr = Sight.Log.LogFileOpener.OpenRead(this.getBackupFileName());
    } catch (Exception exc) { rr = null; };
    if (rr==null)
     { System.out.println("Cold start.");
       this.setLastDone(null);
       do_on_start();
     };

    String skip = null;
    if (rr!=null)
     { skip = rr.readLine().trim();
       rr.close();
       if (skip.equalsIgnoreCase("task_finished"))
        { System.out.println("Task finished, delete the file "+
          this.getBackupFileName()+".log to re-caclulate.");
          finish();
        };
       if (!skip.equalsIgnoreCase("null"))
        {
          System.out.println("Hot restart from "+skip);
          do_on_hot_start();
        }
        else
         {
           System.out.println("Hot restart information was not saved, performing cold start.");
           skip = null;
         };

       this.setLastDone(skip);
       PrintStream restarts = Sight.Log.LogFileOpener.Append(this.getRestartsLogFile());
       restarts.println("after "+skip+" at "+new java.util.Date().toString());
       restarts.close();
     };
   };

     // load the ids to process:
   void loadList() throws java.io.IOException
    {
      if (reader==null)
       throw new Error("Call setList(...) before calling loadList()");
      String skip = this.getLastDone();
      String s;
      list = new java.util.ArrayList(2000);
      while ( (s=reader.readLine() ) != null) { //==null at eof
       s=transformTaskIdentifier(s); // ignore version number if present
       if (s.length()==0) continue;
       // by default, the repetetive elements are discarded.
       if (!list.contains(s)) list.add(s);
      };

     // set the cursor to the element that must be processed:
     if (skip==null) cursor = 0; else
      { cursor = -1;
        for (int i = 0; i < list.size(); i++) {
          if ( ((String)list.get(i)).equalsIgnoreCase(skip))
           {
             cursor = i;
             break;
           };
        }
         if (cursor<0)
          throw new Error("Id "+skip+" was not found in the task list of size "+list.size());
         cursor++;
      };
      Sight.Agents.util.Pind.setBatch(list);
    };

  private int totalFailExitCode   = 99;;
  private int allFinishedExitCode = 0;
  private int restartExitCode     = 55;
  private int totalStopExitCode   = 54;
  private String backupFileName   ="hot";
  private boolean terminating;
  private String LastDone;
  private String restartsLogFile = "Restarts";
  private boolean paused;

  private BufferedReader reader;

  /** Program exit with this code if the task is not finished, but cannot
   *  be continued or the user requested a termination of the process.
   *  The surrounding shell should not invoke the program again. Default value 99
   */
  public void setTotalFailExitCode(int newTotalFailExitCode) {
    totalFailExitCode = newTotalFailExitCode;
  }

  /** Program exit with this code if the task is not finished, but cannot
   *  be continued or the user requested a termination of the process.
   *  The surrounding shell should not invoke the program again. Default value 99
   */
  public int getTotalFailExitCode() {
    return totalFailExitCode;
  }

  /** Program exit with this code if the task is already completely finished.
   *  The surrounding shell should not invoke the program again. Default value 0.
   */
  public void setAllFinishedExitCode(int newAllFinishedExitCode) {
    allFinishedExitCode = newAllFinishedExitCode;
  }

  /** Program exit with this code if the task is already completely finished.
   *  The surrounding shell should not invoke the program again. Default value 777.
   */
  public int getAllFinishedExitCode() {
    return allFinishedExitCode;
  }

  /** Program exit with this code if the task is not finished,
   *  but the machine is running out of memory or something like that.
   *  The surrounding shell should restart the program after it
   *  exits with this code. Default value 55.
   */
  public void setRestartExitCode(int newRestartExitCode) {
    restartExitCode = newRestartExitCode;
  }

  /** Program exit with this code if the task is not finished,
   *  but the machine is running out of memory or something like that.
   *  The surrounding shell should restart the program after it
   *  exits with this code. Default value 55.
   */
  public int getRestartExitCode() {
    return restartExitCode;
  }

  /** Set the name for the file where to save information, required for the hot
   *  restart. The file is stored in the LastDone Log directory and has the
   *  exctension .log
   */
  public void setBackupFileName(String newBackupFileName) {
    backupFileName = newBackupFileName;
  }
  /** Get the name for the file where to save information, required for the hot
   *  restart. The file is stored in the LastDone Log directory and has the
   *  exctension .log
   */
  public String getBackupFileName() {
    return backupFileName;
  }

  /** setTerminating(true) will make program to save the LastDone state and
   *  exit under totalStopExitCode exit code after the LastDone item in the task
   *  list is finished.
   */
  public void setTerminating(boolean newTerminating) {
    terminating = newTerminating;
    Sight.Agents.util.Pind.setRegisterExit(newTerminating);
  }

  /** setTerminating(true) will make program to save the LastDone state and
   *  exit under totalStopExitCode exit code after the LastDone item in the task
   *  list is finished.
   */
  public boolean isTerminating() {
    return terminating || Sight.Agents.util.Pind.registerExit;
  }

  /** Program exits with this code if the user requested a premature
   *  termination (for example, during debugging). The immediate restart
   *  is not supposed. However the program can be restarted later manually if
   *  required. The default value is 54.
   */
  public void settotalStopExitCode(int newtotalStopExitCode) {
    totalStopExitCode = newtotalStopExitCode;
  }

  /** Program exits with this code if the user requested a premature
   *  termination (for example, during debugging). The immediate restart
   *  is not supposed. However the program can be restarted later manually if
   *  required. The default value is 54.
   */
  public int gettotalStopExitCode() {
    return totalStopExitCode;
  }

   /** Set the last process identifier and exit. */
  public void setLastDone(String newLastDone) {
      LastDone = newLastDone;
    }

   /** Get the last processed identifier .*/
  public String getLastDone() {
      return LastDone;
    }

   /** Save the current state and exit. Should never be called directly. */
  void finish()
    {    balastas = null; System.gc(); Runtime.getRuntime().gc();
         PrintStream r = Sight.Log.LogFileOpener.Open(this.getBackupFileName());
         r.println("task_finished");
         r.close();
         do_on_finish();
         System.exit(this.getAllFinishedExitCode()); // exit requesting hot restart
    }

   /** Save the current state and exit. Should not be called directly. */
  void restart(int exitCode, Object reason)
    {

         PrintStream restarts = Sight.Log.LogFileOpener.Append(this.getRestartsLogFile());
         restarts.println("Terminating at "+this.getLastDone()+" code "+
         this.getRestartExitCode()+" at "+new java.util.Date().toString());
         restarts.println("Reason:");
         if (reason!=null)
         {
         if (reason instanceof Throwable)
           ((Throwable) reason).printStackTrace(restarts);
          else restarts.println(reason.toString());
         };

         restarts.close();


         PrintStream hts = Sight.Log.LogFileOpener.Open(this.getBackupFileName());
         hts.println(this.getLastDone());
         hts.close();
         System.exit(exitCode); // exit requesting hot restart
    }

  /** Set the file name where the information about the all restarts is stored. */
  public void setRestartsLogFile(String newRestartsLogFile) {
    restartsLogFile = newRestartsLogFile;
  }
  /** Get the file name where the information about the all restarts is stored. */
  public String getRestartsLogFile() {
    return restartsLogFile;
  }

  /** Override this if you want to ignore the version number of some other part of the
   *  task identifier.
   */
  public String transformTaskIdentifier(String id)
   {
     return id;
   };

  /** Release this to have memory for exit operations
   *  under low memory conditions.
   */
  static byte []balastas = new byte[8000000];
  private String doneLogFileName = "done";
  private String bugFileName     = "not_done";

  /** Perform the actual processing */
  public void run()
   {  String current;
      if (reader==null)
       throw new Error("Call setList(...) before calling loadList()");
      try {
      loadList();
      for (cursor=cursor; cursor<list.size(); cursor++)
       {
       current = (String) list.get(cursor);
       current=transformTaskIdentifier(current); // ignore version number if present
       if (current.length()==0) continue;
         // check for low memory conditions and restart if required
         try {
         process(current, cursor);
         done(current);
         this.setLastDone(current);
         } catch (java.lang.OutOfMemoryError err)
            {
              balastas = null;
              list = null;
              System.gc();
              Runtime.getRuntime().gc();
              restart(this.getRestartExitCode(), err);
            }
           catch (Exception err)
            {
              balastas = null;
              System.gc();
              Runtime.getRuntime().gc();
              restart(this.getRestartExitCode(), err);
            };

         LastDone = current;

         if (this.isTerminating()) restart(this.gettotalStopExitCode(), null);

         while (isPaused())
         { try { Sleeper.Sleep(1000);
           if (this.isTerminating()) restart(this.gettotalStopExitCode(), null);
         } catch (InterruptedException ex) { } };
       }
      }
       catch (Exception io)
        { restart(this.getTotalFailExitCode(), io); };

      instance = null;
      reader = null;

      done("Task completed at "+new java.util.Date().toString());
      finish();
   };

  /** Suspends/resumes the executing. */
  public void setPaused(boolean newPaused) {
    paused = newPaused;
  }
  public boolean isPaused() {
    return paused;
  }

 public void process(String current, int cur) throws Exception, Error
  {
     try {
     Pind.Batch(cur, Pind.PROCESSING);
     process(current);
     Pind.Batch(cur, Pind.CHAIN_OK);
     } catch (Exception exc)
        {
          Output.e(exc);
          Pind.Batch(cur, Pind.BUG);
          bug(current, exc);
        };
  };

 /** Process this identifier. This method must be overriden. The default
  *  method pauses for 1 sec and then throws exception if
  *  if (Math.random()<0.12) or OutOfMemoryError if <0.05.
  *  It is implemented for demo and debugging purposes. */
 public void process(String id) throws Exception, Error
  {
    try {
      Sleeper.Sleep(1000);
    }
    catch (InterruptedException ex) {
    }

    System.out.println(id+" processed");
    Sight.io.Output.a(id+" processed");
    if (Math.random()<0.05)
     throw new java.lang.OutOfMemoryError("Demo out of memory error while processing "+id);
    if (Math.random()<0.12)
     throw new java.lang.Exception("Demo exception from the default method while processing "+id);
  };

 /**
  * Test it.
  */
 static public void main(String[] args) {
   try {
   StringBuffer b = new StringBuffer();
   for (int i = 0; i < 100; i++) {
     b.append("task_"+i+"\n");
   }
   StringReader s = new StringReader(b.toString());
   ListProcessor lp = new ListProcessor();
   lp.setList(s);
   lp.run();
   } catch (Exception exc)
      { if (exc!=null) System.out.println(exc.getMessage());
        exc.printStackTrace();
      };
 }

  void done(String id)
  {
         PrintStream lg = Sight.Log.LogFileOpener.Append(this.getdoneLogFileName());
         lg.println(id);
         lg.close();
  };

  void bug(String id, Throwable reason)
  {      PrintStream lg = Sight.Log.LogFileOpener.Append(this.getBugFileName());
         lg.println(id);
         lg.close();
  };

  /** This file contains list of all successfully processed identifiers. */
  public void setdoneLogFileName(String newdoneLogFileName) {
    doneLogFileName = newdoneLogFileName;
  }
  /** This file contains list of all successfully processed identifiers. */
  public String getdoneLogFileName() {
    return doneLogFileName;
  }
  public void setBugFileName(String newBugFileName) {
    bugFileName = newBugFileName;
  }
  public String getBugFileName() {
    return bugFileName;
  }

  /** Operations, required only when starting the task list.
   *  For example, write header of the log files. */
  public void do_on_start()
   {
   };

  /** Operations, required only when finishig the task list.
   *  For example, write footers of the log files. */
  public void do_on_finish()
   {
   };

  /** Operations, required on the hot start.
   *  For example, open log files appending. */
  public void do_on_hot_start()
   {
   };


}