package daruma.server;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.PrintWriter;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;

import javax.xml.transform.TransformerException;
import javax.xml.parsers.ParserConfigurationException;

import daruma.sql.DatabaseConnectionException;
import daruma.util.LogWriter;
import daruma.util.PropertyReader;
import daruma.xml.util.XMLFormatConverter;
import daruma.wfs.SOAPFaultDocumentBuilder;

import daruma.xml.util.XPointerUtil;
import daruma.util.ParseException;

import daruma.xml.URI;
/*
import daruma.xml.util.DOMDocumentFactory;
import org.w3c.dom.Document;
*/

public class DarumaServlet extends HttpServlet
{
    private static final long serialVersionUID = 0L;

    private long connectionID = 0;

    private void initializeEnvironment()
    {
	//
	// initialize global Classes
	//
	daruma.util.PropertyReader.initialize
	    ( this.getClass().getClassLoader().getResourceAsStream
	      ( daruma.util.PropertyReader.getDefaultPropertyFile() ) );
	daruma.util.LogWriter.initialize
	    ( this.getClass().getClassLoader().getResource
	      ( daruma.util.PropertyReader.getDefaultPropertyFile() ) );
    }



    @Override
    public void doGet( HttpServletRequest request,
		       HttpServletResponse response )
	throws IOException
    {
	this.initializeEnvironment();

	daruma.util.LogWriter.qwrite("INFO",
				     "query = ["
				     + request.getQueryString() + "]" );

	response.setContentType( "text/xml; charset=UTF-8" );


	//
	// wsdl
	//
	if ( request.getQueryString() != null
	     && request.getQueryString().equals( "wsdl" ) )
	{
	    response.getOutputStream().print( this.getWSDL() );

	    return;
	}


	//
	// get parameters
	//
	final String service = request.getParameter( "SERVICE" );
	final String version = request.getParameter( "VERSION" );
	final String req     = request.getParameter( "REQUEST" );
	final String ns      = request.getParameter( "NAMESPACE" );


	//
	// check each parameters
	//

	// check SERVICE
	if ( service == null )
	{
	    this.returnError( "no SERVICE parameter found."
			      + " add SERVICE parameter.\n"
			      + " format: SERVICE=WFS",
			      response );

	    return;
	}
	else if ( ! service.equals( "WFS" )
	       && ! service.equals( "MISP" ) )
	{
	    this.returnError( "SERVICE type [" + service + "] unsupported, "
			      + "expected was WFS or MISP",
			      response );

	    return;
	}

	// check VERSION
	if ( version == null )
	{
	    if ( ! ( req != null && req.equals( "GetCapabilities" ) ) )
	    {
		this.returnError( "no VERSION parameter found."
				  + " add VERSION parameter.\n"
				  + " format: VERSION=1.1.0",
				  response );

		return;
	    }
	}
	else if ( ! version.equals( "1.1.0" ) )
	{
	    this.returnError( "VERSION [" + version + "] not supported,"
			      + " use VERSION=1.1.0",
			      response );

	    return;
	}

	// check NAMESPACE
	final String namespaceParam = request.getParameter( "NAMESPACE" );

	Map<String, String> namespaceMap;
	if ( namespaceParam == null )
	{
	    namespaceMap = new TreeMap<String, String>();
	}
	else
	{
	    try
	    {
		namespaceMap = XPointerUtil.parseXmlnsString( namespaceParam );
	    }
	    catch( ParseException e )
	    {
		this.returnError( e.getMessage() , response );

		return;
	    }
	}

	if ( req == null )
	{
	    // XXX: return more detail message, REQUEST candidates
	    this.returnError( "no REQUEST found. add REQUEST parameter.",
			      response );

	    return;
	}


	//
	// handle each operation
	//
	String reqStr = null;

	try
	{
	    if ( req.equals( "GetCapabilities" ) )
	    {
		reqStr = "<_misp:GetCapabilities"
			 + " xmlns:_misp=\"http://www.infosharp.org/misp\"/>";
	    }
	    else if ( req.equals( "DescribeFeatureType" ) )
	    {
		reqStr = this.getDescribeFeatureTypeRequest( request ,
							     namespaceMap );
	    }
	    else if ( req.equals( "GetFeature" ) )
	    {
		reqStr = this.getGetFeatureRequest( request ,
						    namespaceMap );
	    }
	    else if ( req.equals( "Transaction" )
		   || req.equals( "RegisterFeatureType" )
		   || req.equals( "RegisterCoordinateSystemTransformation" ) )
	    {
		this.returnError( "REQUEST [" + req + "] by http GET method"
				  + " not supported yet."
				  + " Use http POST instead.",
				  response );

		return;
	    }
	    else
	    {
		// XXX: return more detail message, REQUEST candidates
		this.returnError( "bad REQUEST [" + req + "]",
				  response );

		return;
	    }

	    System.err.println( reqStr );

	    //
	    // delegate to main handler
	    //
	    this.handleConnection
		 ( new ByteArrayInputStream( reqStr.getBytes() ),
		   response.getOutputStream() );

	}
	catch( IOException e )
	{
	    this.returnError( e.getMessage() , response );

	    return;
	}
    }


    @Override
    public void doPost( HttpServletRequest request,
			HttpServletResponse response )
	throws IOException
    {
	this.initializeEnvironment();

	response.setContentType( "text/xml; charset=UTF-8" );

	this.handleConnection( request.getInputStream(),
			       response.getOutputStream() );
    }


    private void handleConnection( InputStream in, OutputStream out )
							throws IOException
    {
	/*
	//
	// initialize global Classes
	//
	daruma.util.PropertyReader.initialize
	    ( this.getClass().getClassLoader().getResourceAsStream
	      ( daruma.util.PropertyReader.getDefaultPropertyFile() ) );
	daruma.util.LogWriter.initialize
	    ( this.getClass().getClassLoader().getResource
	      ( daruma.util.PropertyReader.getDefaultPropertyFile() ) );
	*/

	//
	// create connection
	//
	ConnectionHandler c;

	try
	{
	    c = new ConnectionHandler
		    ( in,
		      out,
		      PropertyReader.getProperty( "daruma.db.name" ),
		      PropertyReader.getProperty( "daruma.db.uid" ),
		      PropertyReader.getProperty( "daruma.db.pass" ),
		      false, // compress receiving stream by gzip
		      false, // compress sending stream by gzip
		      "servlet",
		      true,
		      false,
		      null,
		      this.connectionID );

	    this.connectionID ++;
	}
	catch( DatabaseConnectionException  e )
	{
	    throw new IOException( e.getMessage() );
	}


	//
	// do main process
	//
	c.run();
    }


    private void returnError( String  errorMessage,
			      HttpServletResponse  response )
						throws IOException
    {
	try
	{
	    XMLFormatConverter.print( new SOAPFaultDocumentBuilder
					  ( errorMessage ).newDocument(),
				      response.getOutputStream() );
	}
	catch( ParserConfigurationException e )
	{
	    e.printStackTrace();
	}
	catch( TransformerException e )
	{
	    e.printStackTrace();
	}
    }

    private String getDescribeFeatureTypeRequest
			( HttpServletRequest request ,
			  Map<String, String> namespaceMap )
	throws IOException
    {
	final String typeNameParam  = request.getParameter( "TYPENAME" );

	if ( typeNameParam == null )
	{
	    throw new IOException( "no TYPENAME parameter found."
				   + " add TYPENAME parameter." );
	}

	/*
	throw new IOException( "debug: "
	       + "NAMESPACE = [" + namespaceParam + "], "
	       + "nsMap = [" + namespaceMap.toString() + "], "
	       + "TYPENAME = [" + typeNameParam + "]" );
	*/

	/*
	Document doc = DOMDocumentFactory.create();

	Element describeFeatureTypeElement
		    = doc.createElementNS( URI.MISP,
					   "DescribeFeatureType" );

	doc.appendChild( describeFeatureTypeElement );

	Element typeNameElement
		    = doc.createElementNS( URI.MISP ,
					   "TypeName" );
	describeFeatureTypeElement.appendChild( typeNameElement );

	Text type = doc.createText();
	*/

	StringBuilder buf = new StringBuilder();

	buf.append( "<_misp:DescribeFeatureType"
		    + " xmlns:_misp=\"" + URI.MISP + "\"" );

	for( Map.Entry<String, String> e : namespaceMap.entrySet() )
	{
	    buf.append( " " );
	    if ( e.getKey() == null )
	    {
		buf.append( "xmlns=\"" );
		buf.append( e.getValue() );
		buf.append( "\"" );
	    }
	    else
	    {
		buf.append( "xmlns:" );
		buf.append( e.getKey() );
		buf.append( "=\"" );
		buf.append( e.getValue() );
		buf.append( "\"" );
	    }
	}

	buf.append( ">\n" );
	buf.append( " " );
	buf.append( "<_misp:TypeName>");
	buf.append( typeNameParam );
	buf.append( "</_misp:TypeName>");
	buf.append( "</_misp:DescribeFeatureType>" );

	return buf.toString();
    }

    private String getGetFeatureRequest( HttpServletRequest request ,
					 Map<String, String> namespaceMap )
	throws IOException
    {
	final String typeNameParam  = request.getParameter( "TYPENAME" );

	if ( typeNameParam == null )
	{
	    throw new IOException( "no TYPENAME parameter found."
				   + " add TYPENAME parameter." );
	}

	final String bbox = request.getParameter( "BBOX" );
	final String filter = request.getParameter( "FILTER" );

	if ( bbox != null && filter != null )
	{
	    throw new IOException( "BBOX and FILTER is exclusive." );
	}

	if ( bbox != null )
	{
	    throw new IOException( "BBOX parameter is not supported yet." );
	}

	/*
	if ( filter != null )
	{
	    throw new IOException( "FILTER parameter is not supported yet." );
	}
	*/

	StringBuilder buf = new StringBuilder();

	buf.append( "<_misp:GetFeature xmlns:_misp=\"" + URI.MISP + "\"" );

	for( Map.Entry<String, String> e : namespaceMap.entrySet() )
	{
	    buf.append( " " );
	    if ( e.getKey() == null )
	    {
		buf.append( "xmlns=\"" );
		buf.append( e.getValue() );
		buf.append( "\"" );
	    }
	    else
	    {
		buf.append( "xmlns:" );
		buf.append( e.getKey() );
		buf.append( "=\"" );
		buf.append( e.getValue() );
		buf.append( "\"" );
	    }
	}

	buf.append( ">\n" );
	buf.append( " <_misp:Query typeName=\"" );
	buf.append( typeNameParam );
	buf.append( "\">\n" );
	if ( filter != null )
	{
	    buf.append( filter );
	}
	else if ( bbox != null )
	{
	}
	else
	{
	    buf.append( "  <_misp:Filter>\n" );
	    buf.append( "   <_misp:True/>\n" );
	    buf.append( "  </_misp:Filter>\n" );
	}
	buf.append( " </_misp:Query>\n" );
	buf.append( "</_misp:GetFeature>" );

	daruma.util.LogWriter.qwrite( "INFO",
				      "wfs query = [" + buf.toString() + "]" );

	return buf.toString();
    }

    private String getWSDL() throws IOException
    {
	StringBuilder buf = new StringBuilder();

	buf.append( "<wsdl:definition"
		    + " xmlns:wsdl=\"" + "http://schemas.xmlsoap.org/wsdl/"
		    + "\">" );

	buf.append( "</wsdl:definition>" );

	return buf.toString();
    }
}
