/*
 * Copyright (c) 2003, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.knopflerfish.bundle.consoletelnet;

import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Constants;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Dictionary;
import java.net.ServerSocket;
import java.net.Socket;

import java.net.InetAddress;

import java.io.IOException;
import java.io.InterruptedIOException;

import org.knopflerfish.service.log.LogRef;
import org.knopflerfish.service.console.ConsoleService;

import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ConfigurationException;

/**
 ** The telnet console server listens to a port for connections
 ** and upon accept, creates a telnet session to handle that connection
 **
 */
public class TelnetServer implements org.osgi.framework.BundleActivator, Runnable, ManagedService {
  private static BundleContext bc;

  private static final String consoleServiceName = ConsoleService.class.getName();

  private static LogRef log = null;
  private static ConsoleService cs = null;
  private static TelnetConfig telnetConfig = null;

  private static ServiceReference servRef = null;
  private static ServiceRegistration configServReg = null;

  private static Thread telnetServerThread;
  private static Hashtable telnetSessions = null;
  private TelnetSession telnetSession = null;

  private ServiceRegistration telnetReg = null;
  private boolean accept = true;		// control of main loop running
  private boolean updated = true;		// control of main loop server update
  private boolean first = true;			// first time server start

  public TelnetServer() { ; }

  // BundleActivator methods implementation

  public void start(BundleContext bc) throws BundleException {
    this.bc = bc;

    log = new LogRef(bc, true);
    servRef = bc.getServiceReference(consoleServiceName);

    cs = (ConsoleService) bc.getService(servRef);
    if (cs == null) {
      log.error("Failed to get ConsoleService");
    }

    telnetSessions = new Hashtable() ;

    Hashtable conf = new Hashtable();
    try {
      telnetConfig = new TelnetConfig();
      conf.put(Constants.SERVICE_PID, getClass().getName());

      configServReg = bc.registerService(ManagedService.class.getName(),
                                   this,
                                   conf);
    } 
    catch (ConfigurationException cexp) {
      log.error("Consoletelnet configuration error " + cexp.toString());
    }

    telnetServerThread = new Thread(this, "ConsoleTelnetServer");
    telnetServerThread.setDaemon(true);
    telnetServerThread.start();
  }

  public void stop(BundleContext bc) throws BundleException {
    // Stop accepting new connections
    accept = false;

    // Close all pending sessions

    Enumeration e = telnetSessions.keys();
    while (e.hasMoreElements()) {
      telnetSession = (TelnetSession) e.nextElement();
      telnetSession.close();
    }

    configServReg.unregister();
    configServReg = null;

    log.close();
    log = null;
    bc = null;
  }

  // Telnet server main loop, listen for connections and 
  // also look for configuration updates.

  public void run() {
    ServerSocket serverSocket = null;
    // int socketTimeout = 10000;
    // int  backlog = 100;
    
    boolean bFailed = false;

    while (accept && !bFailed) {
      if (updated) {
        log.info("updating in server main loop");
        if (serverSocket != null) {
          try {
            serverSocket.close();
          }
          catch (IOException e) {
            log.error("Server socket exception ", e);
            log.error("Port: " + telnetConfig.getPort()); 
	    bFailed = true;
          }
        }
        try {
          // serverSocket = new ServerSocket(telnetConfig.getPort()); 
          serverSocket = new ServerSocket(telnetConfig.getPort(), telnetConfig.getBacklog(), telnetConfig.getAddress()); 
	  
	  log.info("listening on port " + telnetConfig.getPort());

          updated = false;
        }
        catch (IOException iox) { 
          log.error("Server socket exception", iox);
          updated = true;
	  bFailed = true;
        }
      } else {
        // System.out.print(":"); 
        try {
          serverSocket.setSoTimeout(telnetConfig.getSocketTimeout());
          Socket socket = serverSocket.accept();
        
	  //	  System.out.println("accepting on " + socket);
          TelnetSession telnetSession = new TelnetSession(socket, telnetConfig, cs, log, bc, this);
          Thread telnetSessionThread = new Thread(telnetSession, "TelnetConsoleSession");
          telnetSessions.put(telnetSession, telnetSessionThread);
          telnetSessionThread.setDaemon(false);
          telnetSessionThread.start();
        }
        catch (InterruptedIOException iox) {
	  //	  iox.printStackTrace();
        }
        catch (IOException iox) { 
	  //	  iox.printStackTrace();
          log.error("Server exception", iox);
        }
      }
    } // end while (accept)

    // Accept no connections, shut down server socket

    if (serverSocket != null) {
      try {
        serverSocket.close();
      }
      catch (IOException e) {
        log.error("Server socket exception " + e);
        log.error("Port: " + telnetConfig.getPort()); 
      }
    } 
  } // end run

  public void removeSession(TelnetSession tels) {
    telnetSessions.remove(tels);
  }

  public TelnetConfig getTelnetConfig() {
    return telnetConfig;
  }

  public void updated(Dictionary dict) throws ConfigurationException {
    // Get port and host of present config
    // If they differ from the new configuration,
    // set semaphore updated = true
    // so that the main loop may detect the change and
    // close and reopen the server socket

    try {
      boolean portupdated;
      boolean hostupdated = false;

      int oldport = telnetConfig.getPort();
      InetAddress oldinet = telnetConfig.getAddress();

      telnetConfig.updated(dict);

      int port = telnetConfig.getPort();
      InetAddress inet = telnetConfig.getAddress();

      // port change check
      if (oldport != port ) {
        portupdated = true;
      } else {
        portupdated = false;
      }

      // inet address change check
      if (oldinet == null && inet == null) {
        hostupdated = false; 
      } else if (oldinet == null && inet != null) {
        hostupdated = true; 
      } else if (oldinet != null && inet == null) {
        hostupdated = true; 
      } else if (oldinet != null && inet != null) {
        if (oldinet.equals(inet)) {
          hostupdated = false; 
        } else {
          hostupdated = true; 
        }
      }
      updated = portupdated || hostupdated || first;
      first = false;

      log.info("p " + portupdated);
      log.info("h " + hostupdated);
      log.info("upda " + updated);
    }
    catch (Exception ex) {
      log.error("Consoltelnet config exception " + ex);
      ex.printStackTrace();
    }
  }
} // TelnetServer

