/*
 * Copyright (C) 2005-2008 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License.
 */
package org.alfresco.jlan.smb.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
import org.alfresco.jlan.server.SessionHandlerInterface;
import org.alfresco.jlan.server.SessionHandlerList;
import org.alfresco.jlan.server.SocketSessionHandler;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.smb.mailslot.HostAnnouncer;
import org.alfresco.jlan.smb.mailslot.TcpipNetBIOSHostAnnouncer;
import org.alfresco.jlan.smb.mailslot.win32.Win32NetBIOSHostAnnouncer;
import org.alfresco.jlan.smb.server.win32.LanaListener;
import org.alfresco.jlan.smb.server.win32.Win32NetBIOSLanaMonitor;
import org.alfresco.jlan.smb.server.win32.Win32NetBIOSSessionSocketHandler;

/**
 * Threaded CIFS Connectins Handler Class
 * 
 * <p>Create a seperate thread for each enabled CIFS session handler.
 * 
 * @author gkspencer
 */
public class ThreadedCifsConnectionsHandler implements CifsConnectionsHandler {

	// Constants
	//
	// Default LANA offline polling interval

	public static final long LANAPollingInterval = 5000; // 5 seconds
	
	// List of session handlers that are waiting for incoming requests
	
	private SessionHandlerList m_handlerList;
	
	// List of host announcers
	
	private Vector<HostAnnouncer> m_hostAnnouncers;
	
	// SMB server
	
	private SMBServer m_server;
	
	// Debug output
	
	private boolean m_debug;
	
	/**
	 * Class constructor
	 */
	public ThreadedCifsConnectionsHandler() {
		m_handlerList    = new SessionHandlerList();
		m_hostAnnouncers = new Vector<HostAnnouncer>();
	}
	
	/**
	 * Check if debug output is enabled
	 * 
	 * @return boolean
	 */
	public final boolean hasDebug() {
		return m_debug;
	}
	
	/**
	 * Initialize the connections handler
	 * 
	 * @param srv SMBServer
	 * @param config CIFSConfigSection
	 * @exception InvalidConfigurationException
	 */
	public void initializeHandler( SMBServer srv, CIFSConfigSection config)
		throws InvalidConfigurationException {

		// Save the server
		
		m_server = srv;
		
		// Check if the socket connection debug flag is enabled

		if ( (config.getSessionDebugFlags() & SMBSrvSession.DBG_SOCKET) != 0)
			m_debug = true;

		// Create the NetBIOS session socket handler, if enabled

		if ( config.hasNetBIOSSMB()) {

			// Create the TCP/IP NetBIOS SMB/CIFS session handler(s), and host announcer(s)

			SocketSessionHandler sessHandler = new NetBIOSSessionSocketHandler( srv, config.getSessionPort(), config.getSMBBindAddress(), hasDebug());
			sessHandler.setSocketTimeout( config.getSocketTimeout());
			
			try {
				
				// Initialize the NetBIOS session handler
				
				sessHandler.initializeSessionHandler( srv);
				m_handlerList.addHandler( sessHandler);

				// DEBUG

				if ( Debug.EnableError && hasDebug())
					Debug.println("[SMB] TCP NetBIOS session handler created");

				// Check if a host announcer should be created

				if ( config.hasEnableAnnouncer()) {

					// Create the TCP NetBIOS host announcer

					TcpipNetBIOSHostAnnouncer announcer = new TcpipNetBIOSHostAnnouncer();

					// Set the host name to be announced

					announcer.addHostName(config.getServerName());
					announcer.setDomain(config.getDomainName());
					announcer.setComment(config.getComment());
					announcer.setBindAddress(config.getSMBBindAddress());
					if ( config.getHostAnnouncerPort() != 0)
						announcer.setPort(config.getHostAnnouncerPort());

					// Check if there are alias names to be announced

					if ( config.hasAliasNames())
						announcer.addHostNames(config.getAliasNames());

					// Set the announcement interval

					if ( config.getHostAnnounceInterval() > 0)
						announcer.setInterval(config.getHostAnnounceInterval());

					try {
						announcer.setBroadcastAddress(config.getBroadcastMask());
					}
					catch (Exception ex) {
					}

					// Set the server type flags

					announcer.setServerType(config.getServerType());

					// Enable debug output

					if ( config.hasHostAnnounceDebug())
						announcer.setDebug(true);

					// Add the announcer to the list
					
					m_hostAnnouncers.add( announcer);

					// DEBUG

					if ( Debug.EnableError && hasDebug())
						Debug.println("[SMB] TCP NetBIOS host announcer created");
				}
			}
			catch (IOException ex) {
				
			}
		}

		// Create the TCP/IP SMB session socket handler, if enabled

		if ( config.hasTcpipSMB()) {

			// Create the TCP/IP native SMB session handler(s)

			SocketSessionHandler sessHandler = new TcpipSMBSessionSocketHandler( srv, config.getTcpipSMBPort(), config.getSMBBindAddress(), hasDebug());
			sessHandler.setSocketTimeout( config.getSocketTimeout());

			try {
				
				// Initialize the native SMB handler
				
				sessHandler.initializeSessionHandler( srv);
				m_handlerList.addHandler( sessHandler);

				// DEBUG

				if ( Debug.EnableError && hasDebug())
					Debug.println("[SMB] Native SMB TCP session handler created");
			}
			catch ( IOException ex) {
				throw new InvalidConfigurationException( "Error initializing session handler, " + ex.getMessage());
			}
		}

		// Create the Win32 NetBIOS session handler, if enabled

		if ( config.hasWin32NetBIOS()) {

			// Only enable if running under Windows

			if ( isWindowsNTOnwards()) {

				// DEBUG

				if ( Debug.EnableInfo && hasDebug()) {
					int[] lanas = Win32NetBIOS.LanaEnumerate();

					StringBuffer lanaStr = new StringBuffer();
					if ( lanas != null && lanas.length > 0) {
						for (int i = 0; i < lanas.length; i++) {
							lanaStr.append(Integer.toString(lanas[i]));
							lanaStr.append(" ");
						}
					}
					Debug.println("[SMB] Win32 NetBIOS Available LANAs: " + lanaStr.toString());
				}

				// Check if the Win32 NetBIOS session handler should use a particular LANA/network adapter
				// or should use all available LANAs/network adapters (that have NetBIOS enabled).

				Win32NetBIOSSessionSocketHandler sessHandler = null;
				List<Win32NetBIOSSessionSocketHandler> lanaListeners = new ArrayList<Win32NetBIOSSessionSocketHandler>();

				if ( config.getWin32LANA() != -1) {

					// Create a single Win32 NetBIOS session handler using the specified LANA

					sessHandler = new Win32NetBIOSSessionSocketHandler(srv, config.getWin32LANA(), hasDebug());

					try {
						
						// Initialize the Win32 JNI session handler
						
						sessHandler.initializeSessionHandler( srv);
						m_handlerList.addHandler( sessHandler);

						// DEBUG

						if ( Debug.EnableInfo && hasDebug())
							Debug.println("[SMB] Win32 NetBIOS created session handler on LANA " + config.getWin32LANA());

					}
					catch (Exception ex) {

						// DEBUG

						if ( Debug.EnableError && hasDebug()) {
							Debug.println("[SMB] Win32 NetBIOS failed to create session handler for LANA " + config.getWin32LANA());
							Debug.println("      " + ex.getMessage());
						}
					}

					// Check if a host announcer should be enabled

					if ( config.hasWin32EnableAnnouncer()) {

						// Create a host announcer

						Win32NetBIOSHostAnnouncer hostAnnouncer = new Win32NetBIOSHostAnnouncer(sessHandler, config.getDomainName(), config.getWin32HostAnnounceInterval());
						hostAnnouncer.setDebug( hasDebug());

						// Add the host announcer to the SMB/CIFS server list

						m_hostAnnouncers.add( hostAnnouncer);

						// DEBUG

						if ( Debug.EnableInfo && hasDebug())
							Debug.println("[SMB] Win32 NetBIOS host announcer enabled on LANA " + config.getWin32LANA());
					}

					// Check if the session handler implements the LANA listener interface

					if ( sessHandler instanceof LanaListener)
						lanaListeners.add(sessHandler);
				}
				else {

					// Get a list of the available LANAs

					int[] lanas = Win32NetBIOS.LanaEnumerate();

					if ( lanas != null && lanas.length > 0) {

						// Create a session handler for each available LANA

						for (int i = 0; i < lanas.length; i++) {

							// Get the current LANA

							int lana = lanas[i];

							// Create a session handler

							sessHandler = new Win32NetBIOSSessionSocketHandler( srv, lana, hasDebug());

							try {
								
								// Initialize the Win32 JNI session handler
								
								sessHandler.initializeSessionHandler( srv);
								m_handlerList.addHandler( sessHandler);

								// DEBUG

								if ( Debug.EnableError && hasDebug())
									Debug.println("[SMB] Win32 NetBIOS created session handler on LANA " + lana);

							}
							catch (Exception ex) {

								// DEBUG

								if ( Debug.EnableError && hasDebug()) {
									Debug.println("[SMB] Win32 NetBIOS failed to create session handler for LANA " + lana);
									Debug.println("      " + ex.getMessage());
								}
							}

							// Check if a host announcer should be enabled

							if ( config.hasWin32EnableAnnouncer()) {

								// Create a host announcer

								Win32NetBIOSHostAnnouncer hostAnnouncer = new Win32NetBIOSHostAnnouncer(sessHandler, config.getDomainName(), config.getWin32HostAnnounceInterval());
								hostAnnouncer.setDebug( hasDebug());

								// Add the host announcer to the SMB/CIFS server list

								m_hostAnnouncers.add( hostAnnouncer);

								// DEBUG

								if ( Debug.EnableInfo && hasDebug())
									Debug.println("[SMB] Win32 NetBIOS host announcer enabled on LANA " + lana);
							}

							// Check if the session handler implements the LANA listener interface

							if ( sessHandler instanceof LanaListener)
								lanaListeners.add(sessHandler);
						}
					}

					// Create a LANA monitor to check for new LANAs becoming available

					Win32NetBIOSLanaMonitor lanaMonitor = new Win32NetBIOSLanaMonitor( srv, lanas, LANAPollingInterval, hasDebug());

					// Register any session handlers that are LANA listeners

					if ( lanaListeners.size() > 0) {

						for (int i = 0; i < lanaListeners.size(); i++) {

							// Get the current LANA listener

							Win32NetBIOSSessionSocketHandler handler = lanaListeners.get(i);

							// Register the LANA listener

							lanaMonitor.addLanaListener(handler.getLANANumber(), handler);
						}
					}
				}
			}
		}
	}
	
	/**
	 *  Return the count of active session handlers
	 *  
	 *  @return int
	 */
	public int numberOfSessionHandlers() {
		return m_handlerList.numberOfHandlers();
	}
	
	/**
	 * Start the connection handler thread
	 */
	public void startHandler() {
		
		// Start each session handler in a seperate thread
		
		for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) {
			
			SessionHandlerInterface sessHandler = m_handlerList.getHandlerAt( idx);
			
			if ( sessHandler instanceof Runnable) {
				
				// Start the session handler in a seperate thread
				
				Thread handlerThread = new Thread(( Runnable) sessHandler);
				handlerThread.setName( "CIFSSessHandler_" + sessHandler.getHandlerName());
				handlerThread.start();
				
				// DEBUG
				
				if ( Debug.EnableInfo && hasDebug())
					Debug.println("[SMB] Created session handler thread " + handlerThread.getName());
			}
		}
		
		// Start the host announcer(s)
		
		if ( m_hostAnnouncers.size() > 0) {
			
			for ( int idx = 0; idx < m_hostAnnouncers.size(); idx++) {

				// Get the current host announcer
				
				HostAnnouncer hostAnnouncer = (HostAnnouncer) m_hostAnnouncers.get( idx);
				hostAnnouncer.startAnnouncer();
				
				// DEBUG
				
				if ( Debug.EnableInfo && hasDebug())
					Debug.println( "[SMB] Started host announcer " + hostAnnouncer.getName());
			}
		}
	}

	/**
	 * Stop the connections handler
	 */
	public void stopHandler() {
		
		// Stop each session handler
		
		for ( int idx = 0; idx < m_handlerList.numberOfHandlers(); idx++) {
			
			SessionHandlerInterface sessHandler = m_handlerList.getHandlerAt( idx);
			sessHandler.closeSessionHandler( m_server);
				
			// DEBUG
			
			if ( Debug.EnableInfo && hasDebug())
				Debug.println("[SMB] Shutting down session handler thread " + sessHandler.getHandlerName());
		}
		
		// Stop the host announcer(s)
		
		if ( m_hostAnnouncers.size() > 0) {
			
			for ( int idx = 0; idx < m_hostAnnouncers.size(); idx++) {

				// Get the current host announcer
				
				HostAnnouncer hostAnnouncer = (HostAnnouncer) m_hostAnnouncers.get( idx);
				hostAnnouncer.shutdownAnnouncer();
				
				// DEBUG
				
				if ( Debug.EnableInfo && hasDebug())
					Debug.println( "[SMB] Shutting down host announcer " + hostAnnouncer.getName());
			}
		}
	}
	
	/**
	 * Determine if we are running under Windows NT onwards
	 * 
	 * @return boolean
	 */
	private final boolean isWindowsNTOnwards() {

		// Get the operating system name property

		String osName = System.getProperty("os.name");

		if ( osName.startsWith("Windows")) {
			if ( osName.endsWith("95") || osName.endsWith("98") || osName.endsWith("ME")) {

				// Windows 95-ME

				return false;
			}

			// Looks like Windows NT onwards

			return true;
		}

		// Not Windows

		return false;
	}
}
