package daruma.wfs;

import daruma.xml.handler.XSAXDefaultHandler;
import daruma.xml.handler.XSAXDOMCreateHandler;
import daruma.xml.URI;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLParseErrorException;
import daruma.xml.util.XMLFormatConverter;
import daruma.util.ISO8601DateFormat;
import daruma.util.LogWriter;

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

import daruma.geometry.CoordinateSystem;
import daruma.geometry.CoordinateSystemTransformation;
import daruma.geometry.AffineCoordinateSystemTransformation;

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

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

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

import java.util.List;
import java.util.ArrayList;
import javax.xml.transform.TransformerException;

import daruma.xml.Lexicon;

public class RegisterCoordinateSystemTransformationHandler extends XSAXDOMCreateHandler
{
    private StorageManager	storage;
    private Date		startTime;
    private Date		endTime;
    private boolean		errorOccured;
    private List<String>	errorMessages;

    public RegisterCoordinateSystemTransformationHandler( 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;
	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();

/*
        Sample

        <misp:Transformation>
          <misp:identifier>Robot1-to-Room1</misp:identifier>
          <misp:sourceCS>Robot1</misp:sourceCS>
          <misp:targetCS>Room1</misp:targetCS>
      
          <misp:transformationMethod>
            <misp:AffineTransformation>
              <misp:sourceDimensions>2</misp:sourceDimensions>
              <misp:targetDimensions>2</misp:targetDimensions>
              <!--
                  x' = 10x + 20y + 1
                  y' = 30x + 40y + 2
              -->
              <misp:parameter>
                10 20 1
                30 40 2
              </misp:parameter>
            </misp:AffineTransformation>
          </misp:transformationMethod>
      
        </misp:Transformation>
*/

	List<Element> transformationElements = ElementUtil.getChildElements( request );

	for( Element transElement : transformationElements )
	{
	    if ( ! transElement.getLocalName().equals( "Transformation" )
	      || ! transElement.getNamespaceURI().equals( URI.MISP ) )
	    {
		this.throwError( new SAXParseException
				 ( "Transformation in namespace "
				   + URI.MISP + " expected, found "
				   + transElement.getLocalName()
				   + " in namespace "
				   + transElement.getNamespaceURI()
				   + ".",
				   super.getLocator() ) );
	    }



	    // XXX
	    // XXX: no error checks for parsing
	    // XXX

	    List<Element> children = ElementUtil.getChildElements( transElement );

	    if ( children.size() != 4
	      && children.size() != 5 )
	    {
		this.throwError( new SAXParseException
				 ( "number of child elements in "
				   + transElement
				   + " unmached, expected was 4 or 5.",
				   super.getLocator() ) );
	    }


	    String identifier = children.get(0).getTextContent();
	    String sourceCS   = children.get(1).getTextContent();
	    String targetCS   = children.get(2).getTextContent();

	    LogWriter.qwrite( "DEBUG", "identifier = [", identifier, "]" );
	    LogWriter.qwrite( "DEBUG", "sourceCS = [", sourceCS, "]" );
	    LogWriter.qwrite( "DEBUG", "targetCS = [", targetCS, "]" );


	    List<Element> methodChildren = ElementUtil.getChildElements( children.get(3) );

	    if ( methodChildren.size() != 1 )
	    {
		this.throwError( new SAXParseException
				 ( "number of child elements in " + children.get(3)
				   + " unmached, expected was 1.",
				   super.getLocator() ) );
	    }

	    Element affineElement = methodChildren.get(0);
	    // XXX: should check affineElement == misp:AffineTransformation

	    List<Element> affineChildren = ElementUtil.getChildElements( affineElement );

	    if ( affineChildren.size() != 3 )
	    {
		this.throwError( new SAXParseException
				 ( "number of child elements in " + affineElement
				   + " unmached, expected was 3.",
				   super.getLocator() ) );
	    }


	    String sourceDimensionsString = affineChildren.get(0).getTextContent();
	    String targetDimensionsString = affineChildren.get(1).getTextContent();
	    String parameterString = affineChildren.get(2).getTextContent();

	    int sourceDimensions = -1;
	    int targetDimensions = -1;

	    try
	    {
		sourceDimensions = new Integer(sourceDimensionsString);
	    }
	    catch( NumberFormatException e )
	    {
		this.throwError( new SAXParseException
				 ( "number format error in sourceDimensions,"
				   + " input = [" + sourceDimensionsString + "]",
				   super.getLocator() ) );
	    }

	    try
	    {
		targetDimensions = new Integer(targetDimensionsString);
	    }
	    catch( NumberFormatException e )
	    {
		this.throwError( new SAXParseException
				 ( "number format error in targetDimensions,"
				   + " input = [" + targetDimensionsString + "]",
				   super.getLocator() ) );
	    }

	    LogWriter.qwrite( "DEBUG", "sourceDimensions = ", sourceDimensions );
	    LogWriter.qwrite( "DEBUG", "targetDimensions = ", targetDimensions );
	    LogWriter.qwrite( "DEBUG", "parameterString = [", parameterString, "]" );

	    if ( sourceDimensions != targetDimensions )
	    {
		this.throwError( new SAXParseException
				 ( "unsupported the case sourceDimensions != targetDimensions",
				   super.getLocator() ) );
	    }


	    String[] params = parameterString.trim().split( "[ \t\n]+" );

	    if ( params.length != sourceDimensions * (sourceDimensions + 1) )
	    {
		this.throwError( new SAXParseException
				 ( "number of affine parameter unmatched, "
				   + "found = " + params.length
				   + ", expected was " + sourceDimensions * (sourceDimensions + 1),
				   super.getLocator() ) );
	    }

	    double[][] t = new double[sourceDimensions][sourceDimensions + 1];

	    for( int i = 0  ;  i < sourceDimensions  ;  ++ i )
	    {
		for( int j = 0  ;  j < sourceDimensions + 1  ;  ++ j )
		{
		    double v = 0.0;
		    final String valueString = params[i * (sourceDimensions + 1) + j];

		    try
		    {
			v = new Double( valueString );
		    }
		    catch( NumberFormatException e )
		    {
			this.throwError( new SAXParseException
					 ( "number format error in affine parameter,"
					   + " input = [" + valueString + "]",
					   super.getLocator() ) );
		    }

		    t[i][j] = v;

		    LogWriter.qwrite( "DEBUG",
				      "value[", i, "][", j, "] = [",
				      t[i][j], "]" );
		}
	    }

	    //
	    // parse TransformationError
	    //
	    String transErrorDescriptionString = null;

	    if ( children.size() == 5 )
	    {
		Element transErrorElement = children.get(4);

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

		List<Element> transErrorChildren
		    = ElementUtil.getChildElements( transErrorElement );

		if ( transErrorChildren.size() != 1 )
		{
		    this.throwError( new SAXParseException
				     ( "number of child elements in "
				       + transErrorElement
				       + " unmached, expected was 1.",
				   super.getLocator() ) );
		}

		try
		{
		    transErrorDescriptionString
			= XMLFormatConverter.toString( transErrorChildren.get(0), false);

		    LogWriter.qwrite( "DEBUG", "trans error string = [",
				      transErrorDescriptionString, "]" );
		}
		catch( TransformerException e )
		{
		    this.throwError( new SAXParseException( e.getMessage(),
							    super.getLocator() ) );
		}
	    }

	    AffineCoordinateSystemTransformation
		trans = new AffineCoordinateSystemTransformation
			    ( identifier,
			      new CoordinateSystem( sourceCS ),
			      new CoordinateSystem( targetCS ),
			      sourceDimensions,
			      targetDimensions,
			      t,
			      transErrorDescriptionString );

	    try
	    {
		this.storage.registerCoordinateSystemTransformation( trans );

		super.setMostRecentTransactionURI
		      ( this.storage.getMostRecentTransactionURI() );
	    }
	    catch( StorageException e )
	    {
		this.throwError( new SAXParseException
				 ( "registration failed, " + e.getMessage(),
				   super.getLocator(),
				   e ) );
	    }
	}



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

	this.printOutput();
    }

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

	String	status;

	if ( this.errorOccured )
	{
	    status = "FAILURE";
	}
	else
	{
	    status = "SUCCESS";
	}


	ResponseInfo responseInfo = super.getResponse();
	Document doc = responseInfo.document;

	Element topLevelElement = ElementUtil.genElementSimple
	    ( Lexicon.MispRegisterCoordinateSystemTransformationResponse,
	      null, responseInfo.document, responseInfo.leaf, true );
	responseInfo.setResponseToLeaf( topLevelElement );

	responseInfo.addResponseStatusToLeaf( super.getMostRecentTransactionURI(),
					      this.startTime, this.endTime );

	Element registerCoordinateSystemTransformationResultElement
	    = doc.createElementNS
	      ( URI.MISP, "misp:RegisterCoordinateSystemTransformationResult" );
	
	topLevelElement.appendChild
	    ( registerCoordinateSystemTransformationResultElement );

	Element statusElement = doc.createElementNS( URI.MISP, "misp:Status" );
	registerCoordinateSystemTransformationResultElement
	    .appendChild( statusElement );
	statusElement.appendChild( doc.createTextNode( status ) );

	for ( String s : this.errorMessages )
	{
	    Element errorElement = doc.createElementNS( URI.MISP, "misp:Error" );
	    registerCoordinateSystemTransformationResultElement
		.appendChild( errorElement );

	    Element messageElement = doc.createElementNS( URI.MISP, "misp:Message" );
	    errorElement.appendChild( messageElement );

	    messageElement.appendChild( doc.createTextNode( s ) );
	}

	super.getResponse().outputHeaderPart(out);

	out.flush();

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

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

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

	this.printOutput();

	throw e;
    }
}
