package com.dbxml.db.server;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: Server.java,v 1.4 2004/08/22 04:18:42 bradford Exp $
 */

import com.dbxml.db.server.labrador.ServerProxy;
import com.dbxml.labrador.ID;
import com.dbxml.labrador.objects.ObjectResolver;
import com.dbxml.util.Configuration;
import com.dbxml.util.LoggingStream;
import com.dbxml.xml.dom.DOMHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Document;
import java.io.BufferedOutputStream;

/**
 * The Server class is the glue that holds the dbXML server together.
 * It is used to bootstrap the server as well as to instantiate and
 * start the various system services.
 */

public final class Server {
   // Configuration Defaults
   private static final String LOG_FILE = "logs/dbXML.out";
   private static final String CONFIG_FILE = "config/system.xml";

   private static final String SERVER = "server";
   private static final String SERVICES = "services";
   private static final String LOGGING = "logging";
   private static final String LOG = "logfile";

   private static Server instance;

   private Configuration config;
   private Task finalizer;
   private Thread scheduler;
   private boolean running;

   private List tasks = Collections.synchronizedList(new ArrayList()); // Of TaskInfo
   private ServiceManager services = new ServiceManager();

   public static void main(String[] args) {
      System.out.println(dbXML.VersionString);
      if ( args.length > 0 )
         new Server(args[0]);
      else {
         File cfg = new File(new File(System.getProperty(dbXML.PROP_DBXML_HOME)), CONFIG_FILE);
         new Server(cfg.getAbsolutePath());
      }
   }

   public static void stop(String[] args) {
      getInstance().shutDown(0);
   }

   public Server() {
      this("system.xml");
   }

   public Server(final String name) {
      instance = this;

      // Read in the configuration
      Document tmp = null;
      try {
         tmp = DOMHelper.parse(new File(name));
      }
      catch ( Exception e ) {
         System.err.println("FATAL ERROR: Reading configuration file '" + name + "'");
         e.printStackTrace(System.err);
         System.exit(1);
      }
      final Document doc = tmp;
      config = new Configuration(doc);

      Configuration serverConfig = config.getChild(SERVER);

      try {
         boolean logging = config.getBooleanAttribute(LOGGING, true);
         if ( logging ) {
            // Hijack System.out and System.err
            File logFile = null;
            String log = config.getAttribute(LOG);
            if ( log != null && log.length() > 0 )
               logFile = new File(log);
            else
               logFile = new File(new File(System.getProperty(dbXML.PROP_DBXML_HOME)), LOG_FILE);

            if ( logFile != null ) {
               boolean header = !logFile.exists();
               System.out.println("Logging to " + logFile.getPath());

               FileOutputStream fos = new FileOutputStream(logFile.getPath(), true);
               LoggingStream ls = new LoggingStream(fos);
               PrintStream ps = new PrintStream(ls, true);

               if ( header ) {
                  String s = dbXML.Title + " " + dbXML.Version + " Console Log File";
                  ps.println(s);
                  StringBuffer sb = new StringBuffer(s.length());
                  for ( int i = 0; i < s.length(); i++ )
                     sb.append('-');
                  ps.println(sb.toString());
               }
               System.setOut(ps);
               System.setErr(ps);
            }
         }
         else
            System.out.println("Logging to standard output");

      }
      catch ( Exception e ) {
         System.err.println("FATAL ERROR: Can't override standard logging");
         e.printStackTrace(System.err);
         System.exit(1);
      }

      System.out.println("Server startup");

      // Configure the Service Manager
      services.setServer(this);
      services.setConfig(serverConfig.getChild(SERVICES));

      if ( !services.startServices() ) {
         System.err.println("FATAL ERROR: Service manager could not be started");
         System.exit(1);
      }

      ObjectResolver.register(new ID("dbXML"), new ServerProxy(this));

      // Register the shutdown handler
      Runtime.getRuntime().addShutdownHook(new ShutdownHandler());

      // Start The Scheduling Daemon
      scheduler = new TaskScheduler();
      scheduler.start();

      running = true;
   }

   /**
    * getInstance returns the VM's Server instance.  Only one instance
    * reference is allowed per VM, and any new instance will shadow
    * the previous one.
    *
    * @return The Server instance
    */
   public static Server getInstance() {
      return instance;
   }

   /**
    * addTask adds an interval Task to the Server instance or overrides
    * a previously defined interval.
    *
    * @param task The Task to add
    * @param interval The Interval (milliseconds)
    */
   public synchronized void addTask(Task task, long interval) {
      int size = tasks.size();
      for ( int i = 0; i < size; i++ ) {
         TaskInfo info = (TaskInfo)tasks.get(i);
         if ( task == info.task ) {
            info.interval = interval;
            return;
         }
      }
      tasks.add(new TaskInfo(task, interval));
   }

   /**
    * removeTasks removes an interval Task from the Server instance.
    *
    * @param task The task to remove
    */
   public synchronized void removeTask(Task task) {
      int size = tasks.size();
      for ( int i = 0; i < size; i++ ) {
         TaskInfo info = (TaskInfo)tasks.get(i);
         if ( task == info.task ) {
            tasks.remove(i);
            return;
         }
      }
   }

   /**
    * shutDown shuts down the Server instance.  This will trigger a shutdown
    * handler that will cleanly stop any active Services.
    *
    * @param exitCode The shutdown exit code
    */
   public void shutDown(int exitCode) {
      System.exit(exitCode);
   }

   /**
    * isRunning returns whether or not the Services are all running.
    *
    * @return Whether or not the Services are running.
    */
   public boolean isRunning() {
      return running;
   }

   /**
    * listServices lists the names of all registered Services.
    *
    * @return The Service list
    */
   public String[] listServices() {
      return services.listServices();
   }

   /**
    * getService retrieves a registered Service by name.
    *
    * @param name The Service name
    * @return The Service
    */
   public Service getService(String name) {
      return services.getService(name);
   }


   /**
    * ShutdownHandler
    */

   private class ShutdownHandler extends Thread {
      public ShutdownHandler() {
         super("Shutdown Handler");
      }

      public void run() {
         System.out.println();

         removeTask(finalizer);
         services.stopServices();

         services.dispose();

         System.out.println("Server shutdown");
      }
   }


   /**
    * TaskScheduler
    */

   private class TaskScheduler extends Thread {
      public TaskScheduler() {
         super("Task Scheduler");
         setDaemon(false);
      }

      public void run() {
         TaskInfo info;
         long wait = 5000;
         long time = 0;
         long done = 0;
         long diff = 0;
         int size = 0;
         int i = 0;
         while ( true ) {
            try {
               wait = 5000;
               size = tasks.size();
               for ( i = 0; i < size; i++ ) {
                  info = (TaskInfo)tasks.get(i);
                  time = System.currentTimeMillis();
                  if ( time >= info.lastrun + info.interval ) {
                     try {
                        info.task.runTask();
                     }
                     catch ( Exception e ) {
                        if ( info.error == false ) {
                           info.error = true;
                           System.err.println("TASK ERROR: " + e);
                        }
                     }
                     done = System.currentTimeMillis();
                     info.lastrun = time;
                     diff = done - time;
                     if ( info.interval - diff < wait )
                        wait = info.interval - diff;
                  }
                  else {
                     diff = (info.lastrun + info.interval) - time;
                     if ( diff > 0 && diff < wait )
                        wait = diff;
                  }
               }
               Thread.sleep(wait);
            }
            catch ( Exception e ) {
            }
         }
      }
   }


   /**
    * TaskInfo
    */

   private class TaskInfo {
      public Task task;
      public long interval = 0;
      public long lastrun = 0;
      public boolean error;

      public TaskInfo(Task task, long interval) {
         this.task = task;
         this.interval = interval;
         this.lastrun = System.currentTimeMillis();
      }
   }
}

