package daruma.wfs;

import daruma.xml.handler.XSAXDOMCreateHandler;
import daruma.xml.URI;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLFormatConverter;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;

import daruma.geometry.CoordinateSystem;
import daruma.geometry.CoordinateSystemTransformation;
import daruma.geometry.CoordinateSystemTransformationDictionary;
import daruma.geometry.TransformationContext;

import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

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

import org.w3c.dom.Element;
import org.w3c.dom.Document;

import java.io.PrintWriter;
import java.io.OutputStream;
import java.util.Date;

import java.util.List;
import java.util.ArrayList;

import daruma.util.Pair;

import daruma.xml.Lexicon;


public class GetCoordinateSystemTransformationHandler
    extends XSAXDOMCreateHandler
{
    private StorageManager	storage;
    private Date		startTime;
    private Date		endTime;
    private boolean		errorOccured;
    private List<String>	errorMessages;
    private Pair<CoordinateSystem, CoordinateSystem> specifiedFromTo;

    public GetCoordinateSystemTransformationHandler( OutputStream out,
						     XMLReader parser,
						     boolean isTopLevelHandler,
						     StorageManager storage )
	throws SAXException
    {
	super( out, parser, isTopLevelHandler );
	super.setInhibitEndPrefixBeforeEndDocument( true );

	this.storage = storage;

	this.errorOccured = false;
	this.errorMessages = new ArrayList<String>();

	this.endTime = null;

	this.specifiedFromTo = null;

	try
	{
	    this.startTime = this.storage.getCurrentTime();
	}
	catch( StorageException e )
	{
	    this.throwError( new SAXParseException( e.getMessage() ,
						    super.getLocator() ,
						    e ) );
	}
    }

    public void xEndDocument() throws SAXException
    {
	//
	// parse request
	//
	Element request = super.getDocumentElement();

	if ( ! request.getLocalName().equals( "GetCoordinateSystemTransformation" )
	  || ! request.getNamespaceURI().equals( URI.MISP ) )
	{
	    this.throwError( new SAXParseException
			     ( "GetCoordinateSystemTransformation in namespace "
			       + URI.MISP + " expected, found "
			       + request.getLocalName()
			       + " in namespace "
			       + request.getNamespaceURI()
			       + ".",
			       super.getLocator() ) );
	}

	if ( ElementUtil.getChildElements( request ).size() != 0 )
	{
	    this.throwError( new SAXParseException
			     ( "number of child elements in "
			       + request.getLocalName()
			       + " unexpected, expected was 0." ,
			       super.getLocator() ) );
	}

	// XXX: not implemented yet
	if ( request.hasAttributeNS( null, "fromSrs" )
	     || request.hasAttributeNS( null, "toSrs" ) )
	{
	    // should specify both of from and to
	    if ( ! request.hasAttributeNS( null, "fromSrs" )
		 || ! request.hasAttributeNS( null, "toSrs" ) )
	    {
		this.throwError( new SAXParseException
				 ( "at element "
				   + request.getLocalName() + ", "
				   + "fromSrs and toSrs should be specified"
				   + " as a pair",
				   super.getLocator() ) );
	    }

	    String fromSrs = request.getAttributeNS( null, "fromSrs" );
	    String toSrs = request.getAttributeNS( null, "toSrs" );

	    this.specifiedFromTo = new Pair<CoordinateSystem, CoordinateSystem>
				       ( new CoordinateSystem( fromSrs ),
					 new CoordinateSystem( toSrs ) );
	}

	this.printOutput();
    }

    private void printOutput() throws SAXException
    {
	PrintWriter out = super.getPrintWriter();

	ResponseInfo responseInfo = super.getResponse();
	Document doc = responseInfo.document;
	Element topLevelElement = ElementUtil.genElementSimple
				  ( Lexicon.MispGetCoordinateSystemTransformationResponse,
				    null, doc, responseInfo.leaf, true );
	responseInfo.setResponseToLeaf( topLevelElement );

	CoordinateSystemTransformationDictionary dic = null;
	try
	{
	    dic = storage.getCoordTransDictionary();
	}
	catch( StorageException e )
	{
	    this.throwError( new SAXParseException
			     ( "Can't retrieve coordinate system"
			       + " transformation," + e.getMessage(),
			       super.getLocator(),
			       e ) );
	}


	if ( this.specifiedFromTo == null )
	{
	    // return all translations
	    for ( CoordinateSystemTransformation c
		      : dic.getAllTransform().values() )
	    {
		topLevelElement.appendChild
		    ( this.convertToTransformElement( c, doc ) );
	    }
	}
	else
	{
	    List<CoordinateSystemTransformation>
		route = TransformationContext.search
			( dic,
			  this.specifiedFromTo.getFirst(),
			  this.specifiedFromTo.getSecond() );

	    // return each translation in transformation
	    for ( CoordinateSystemTransformation c : route )
	    {
		topLevelElement.appendChild
		    ( this.convertToTransformElement( c, doc ) );
	    }
	}

	out.flush();

	try
	{
	    this.getOutputStream().flush();
	}
	catch( java.io.IOException  e )
	{
	    throw new SAXException( e );
	}
    }

    private Element convertToTransformElement( CoordinateSystemTransformation c,
					       Document doc )
	throws SAXException
    {
	Element transElement = doc.createElementNS( URI.MISP, "misp:Transformation" );

	Element identifierElement = doc.createElementNS( URI.MISP, "misp:identifier" );
	identifierElement.appendChild( doc.createTextNode( c.getIdentifier() ) );
	transElement.appendChild( identifierElement );

	Element sourceCSElement = doc.createElementNS( URI.MISP, "misp:sourceCS" );
	Element targetCSElement = doc.createElementNS( URI.MISP, "misp:targetCS" );
	sourceCSElement.appendChild( doc.createTextNode( c.getSourceCS().getSrsName() ) );
	targetCSElement.appendChild( doc.createTextNode( c.getTargetCS().getSrsName() ) );
	transElement.appendChild( sourceCSElement );
	transElement.appendChild( targetCSElement );

	Element transformationMethodElement = doc.createElementNS( URI.MISP, "misp:transformationMethod" );
	transElement.appendChild( transformationMethodElement );

	Element concreteMethodNameElement = doc.createElementNS( URI.MISP, c.getTransformName() );
	transformationMethodElement.appendChild( concreteMethodNameElement );

	Element sourceDimensionsElement = doc.createElementNS( URI.MISP, "misp:sourceDimensions" );
	Element targetDimensionsElement = doc.createElementNS( URI.MISP, "misp:targetDimensions" );
	sourceDimensionsElement.appendChild( doc.createTextNode( Long.toString( c.getSourceDimension() ) ) );
	targetDimensionsElement.appendChild( doc.createTextNode( Long.toString( c.getTargetDimension() ) ) );
	concreteMethodNameElement.appendChild( sourceDimensionsElement );
	concreteMethodNameElement.appendChild( targetDimensionsElement );

	Element parameterElement = doc.createElementNS( URI.MISP, "misp:parameter" );
	parameterElement.appendChild( doc.createTextNode( c.encode() ) );
	concreteMethodNameElement.appendChild( parameterElement );

	if ( c.getTransformErrorDescription() != null )
	{
	    Element transformationErrorElement
		= doc.createElementNS( URI.MISP, "misp:transformationError" );

	    transElement.appendChild( transformationErrorElement );

	    /*
	    transformationErrorElement.appendChild
		( doc.createTextNode( c.getTransformErrorDescription() ) );
	    */

	    Element errorDescriptionElement = null;

	    try
	    {
		errorDescriptionElement = XMLFormatConverter.stringToElement
					  ( c.getTransformErrorDescription(),
					    doc );
	    }
	    catch( TransformerException e )
	    {
		this.throwError( new SAXParseException
				 ( e.getMessage(),
				   super.getLocator(),
				   e ) );
	    }

	    transformationErrorElement.appendChild( errorDescriptionElement );
	}

	return transElement;
    }

    private void setError( String  errorMessage )
    {
	this.errorOccured = true;
	this.errorMessages.add( errorMessage );
    }

    private void throwError( SAXParseException  e ) throws SAXException
    {
	this.setError( e.getMessage() );

	Document doc;

	try
	{
	    XMLFormatConverter.print( new SOAPFaultDocumentBuilder(e).newDocument() ,
				      this.getOutputStream() );
	}
	catch( ParserConfigurationException  pe )
	{
	    throw new SAXException( pe );
	}
	catch( TransformerException  te )
	{
	    throw new SAXException( te );
	}

	throw e;
    }

}
