package com.sun.grizzly.http.embed;


import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.logging.Level;

import javax.management.ObjectName;

import org.apache.log4j.Logger;

import com.sun.grizzly.SSLConfig;
import com.sun.grizzly.arp.AsyncFilter;
import com.sun.grizzly.arp.AsyncHandler;
import com.sun.grizzly.arp.DefaultAsyncHandler;
import com.sun.grizzly.http.Management;
import com.sun.grizzly.http.SelectorThread;
import com.sun.grizzly.ssl.SSLSelectorThread;
import com.sun.grizzly.tcp.Adapter;
import com.sun.grizzly.tcp.http11.GrizzlyAdapter;
import com.sun.grizzly.tcp.http11.GrizzlyAdapterChain;
import com.sun.grizzly.util.net.jsse.JSSEImplementation;

public class MyGrizzlyWebServer  {
	private static Logger log = Logger.getLogger(MyGrizzlyWebServer.class);


    // The port
    private static final int DEFAULT_PORT = 8080;

    // The port
    private static final String DEFAULT_WEB_RESOURCES_PATH = ".";

    // The underlying {@link SelectorThread}
    private SelectorThread st = null;
    
    // The underlying {@link GrizzlyAdapterChain}
    private GrizzlyAdapterChain adapterChains = new GrizzlyAdapterChain();
    
    
    // List of {@link GrizzlyAdapter} and its associated mapping
    private HashMap<GrizzlyAdapter,String[]> adapters
            = new HashMap<GrizzlyAdapter,String[]>();

    
    // Are we started?
    private boolean isStarted = false;

    
    // List of {@link AsyncFilter}
    private ArrayList<AsyncFilter> asyncFilters = new ArrayList<AsyncFilter>();
    
    
    // The path to static resource.
    private String webResourcesPath = DEFAULT_WEB_RESOURCES_PATH;
    
    
    // The mBean default object name.
    private String mBeanName = "com.sun.grizzly:type=GrizzlyWebServer,name=GrizzlyHttpEngine-" 
            + DEFAULT_PORT;
       
    
    // The {@link Statistis} instance associated with this instance.
    private Statistics statistics;

    //
    private String bindAddress;

  /**
     * Create a default GrizzlyWebServer
 * @param maxThreads TODO
     */
    public MyGrizzlyWebServer(String bindAddress, int port, int maxThreads, int coreThread, boolean asyncWrite,int keepAliveTimeoutInSeconds) {
    	this.bindAddress = bindAddress;
        createSelectorThread(bindAddress, port, false);
		setMaxThreads(maxThreads);
		useAsynchronousWrite(asyncWrite);
		//MT SET DEI THREAD CORE
		setCoreThreads(coreThread);
		st.setDisplayConfiguration(true);
		st.setKeepAliveTimeoutInSeconds(keepAliveTimeoutInSeconds);

		this.webResourcesPath = DEFAULT_WEB_RESOURCES_PATH;
    }
    
    public MyGrizzlyWebServer(int port, int maxThreads, int coreThreads, boolean asyncWrite,int keepAliveTimeoutInSeconds) {
    	this(null, port, maxThreads, coreThreads, asyncWrite, keepAliveTimeoutInSeconds);
    }
    
    /**
     * Create an underlying {@link SelectorThread}
     * @param port The port to listen to.
     * @param secure <tt>true</tt> if https needs to be used.
     */
    private void createSelectorThread(String bindAddress, int port, boolean secure){
        if (secure) {
            SSLSelectorThread sslSelectorThread = new SSLSelectorThread();
            try {
                sslSelectorThread.setSSLImplementation(new JSSEImplementation());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e);
            }
            st = sslSelectorThread;
        } else {
            st = new SelectorThread();
        }
  
        // BindAddress
        if(bindAddress != null && !bindAddress.trim().equals("")){
	        try {
	        	log.info("set bindAddress="+bindAddress);
	        	InetAddress ip = InetAddress.getByName(bindAddress);
	        	st.setInet(ip);
	        	st.setAddress(ip);
	        	log.info("bindAddress setted");
			} catch (UnknownHostException e1) {
		          throw new IllegalArgumentException("UnknownHostException "+bindAddress);
			}
        }else{
        	log.info("BindAddress was null!");
        }
        
        // Port
        st.setPort(port); 
        

    }

    
    /**
     * Return the underlying {@link SelectorThread}. Only advanced users
     * should manipulate that class.
     * @return {@link SelectorThread}
     */
    public SelectorThread getSelectorThread(){        
        return st;
    }
    
    
    /**
     * Add an {@link AsyncFilter}. Adding {@link AsyncFilter} automatically
     * enable Grizzly Asynchronous Request Processing mode. {@link AsyncFilter}s
     * are always invoked before {@link GrizzlyAdapter}
     *  
     * @param asyncFilter An {@link AsyncFilter}
     */
    public void addAsyncFilter(AsyncFilter asyncFilter){
        asyncFilters.add(asyncFilter);
    }
    
    
    /**
     * Add a {@link GrizzlyAdapter}. {@link GrizzlyAdapter} will be invoked by
     * Grizzly in the order they are added, e.g the first added is always the 
     * first invoked. {@link GrizzlyAdapter}s
     * are always invoked after {@link AsyncFilter}
     * @param grizzlyAdapter a {@link GrizzlyAdapter}
     * @deprecated - Use {@link #addGrizzlyAdapter(com.sun.grizzly.tcp.http11.GrizzlyAdapter, java.lang.String[])}
     */
    public void addGrizzlyAdapter(GrizzlyAdapter grizzlyAdapter){
        adapters.put(grizzlyAdapter,new String[0]);
    }

    
    /**
     * Add a {@link GrizzlyAdapter} with its associated mapping. A request will
     * be dispatched to a {@link GrizzlyAdapter} based on its mapping value.
     * @param grizzlyAdapter a {@link GrizzlyAdapter}
     * @param mapping An array contains the context path mapping information.
     */
    public void addGrizzlyAdapter(GrizzlyAdapter grizzlyAdapter, String[] mapping){
        adapters.put(grizzlyAdapter,mapping);
        adapterChains.setHandleStaticResources(false);
        if (isStarted) {
            Adapter ga = st.getAdapter();
            if (ga instanceof GrizzlyAdapterChain){
                ((GrizzlyAdapterChain)ga).addGrizzlyAdapter(grizzlyAdapter, mapping);
            } else {
                updateGrizzlyAdapters();
            }  
        }
        
    }

    /**
     * Remove a {@link GrizzlyAdapter}
     * return true of the operation was successful.
     */
    public boolean removeGrizzlyAdapter(GrizzlyAdapter grizzlyAdapter){
        if (adapters.size() > 1){
            adapterChains.removeAdapter(grizzlyAdapter);
        }
        boolean removed = adapters.remove(grizzlyAdapter) != null;
        if (isStarted) {
            Adapter ga = st.getAdapter();
            if (ga instanceof GrizzlyAdapterChain){
                ((GrizzlyAdapterChain)ga).removeAdapter(grizzlyAdapter);
            } else {
                updateGrizzlyAdapters();
            }    
        }
        return removed;
    }
    
    
    /**
     * Set the {@link SSLConfig} instance used when https is required
     */
    public void setSSLConfig(SSLConfig sslConfig){
        if (!(st instanceof SSLSelectorThread)){
            throw new IllegalStateException("This instance isn't supporting SSL/HTTPS");
        }
        ((SSLSelectorThread)st).setSSLConfig(sslConfig);
    }
    
    /**
     * Set to <tt>true</tt> if you want to use asynchronous write operations. Asynchronous
     * write operations may significantly improve performance under high load, but
     * may also comsume more memory. Default is set to false.
     * @param asyncWrite true to enabled asynchronous write I/O operations.
     */
    public void useAsynchronousWrite(boolean asyncWrite){
        st.setAsyncHttpWriteEnabled(asyncWrite);
    }

    /**
     * Start the GrizzlyWebServer and start listening for http requests. Calling 
     * this method several time has no effect once GrizzlyWebServer has been started.
     * @throws java.io.IOException
     */
    public void start() throws IOException{
        if (isStarted) return;
        isStarted = true;

        // Use the default
        updateGrizzlyAdapters();
        
        if (asyncFilters.size() > 0){
            st.setEnableAsyncExecution(true);
            AsyncHandler asyncHandler = new DefaultAsyncHandler();
            for (AsyncFilter asyncFilter: asyncFilters){
                asyncHandler.addAsyncFilter(asyncFilter); 
            }
            st.setAsyncHandler(asyncHandler);    
        }
        
        try {
            st.listen();
        } catch (InstantiationException ex) {
            throw new IOException(ex.getMessage());
        }
    }

    @SuppressWarnings("deprecation")
	private void updateGrizzlyAdapters() {
        adapterChains = new GrizzlyAdapterChain();
        if (adapters.size() == 0){
            adapterChains.setHandleStaticResources(true);
            st.setAdapter(adapterChains);
        } else if (adapters.size() == 1){
            st.setAdapter(adapters.keySet().iterator().next());
        } else {          
            for (Entry<GrizzlyAdapter,String[]> entry: adapters.entrySet()){
                // For backward compatibility
                if (entry.getValue().length == 0){
                    adapterChains.addGrizzlyAdapter(entry.getKey());
                } else {
                    adapterChains.addGrizzlyAdapter(entry.getKey(),entry.getValue());
                }
            }
            st.setAdapter(adapterChains);
            adapterChains.setHandleStaticResources(true);
            adapterChains.setRootFolder(webResourcesPath);
        }
    }

    /**
     * Enable JMX Management by configuring the {@link Management}
     * @param jmxManagement An instance of the {@link Management} interface
     */
    public void enableJMX(Management jmxManagement){
        if (jmxManagement == null) return;
        
        st.setManagement(jmxManagement);
        try {
            ObjectName sname = new ObjectName(mBeanName);                   
            jmxManagement.registerComponent(st, sname, null);
        } catch (Exception ex) {
            SelectorThread.logger().log(Level.SEVERE, "Enabling JMX failed", ex);
        }      
    }
    
    
    /**
     * Return a {@link Statistics} instance that can be used to gather
     * statistics. By default, the {@link Statistics} object <strong>is not</strong>
     * gathering statistics. Invoking {@link Statistics#startGatheringStatistics}
     * will do it.
     */
    public Statistics getStatistics(){
        if (statistics == null){
            statistics = new Statistics(st);
        }
        return statistics;
    } 
 
    /**
     * Set the initial number of  threads in a thread pool.
     * @param coreThreads the initial number of threads in a thread pool.
     */
    public void setCoreThreads(int coreThreads) {
        st.setCoreThreads(coreThreads);
    }

    /**
     * Set the maximum number of  threads in a thread pool.
     * @param maxThreads the maximum number of threads in a thread pool.
     */
    public void setMaxThreads(int maxThreads) {
        st.setMaxThreads(maxThreads);
    }
    
    /**
     * Stop the GrizzlyWebServer.
     */ 
    public void stop(){
        if (!isStarted) return;
        isStarted = false;
        st.stopEndpoint();
    }
    
    
	@SuppressWarnings("unused")
	private String getBindAddress() {
		return this.bindAddress;
	}

	
}