package daruma.wfs;

import daruma.wfs.TransactionInsertHandler;
import daruma.wfs.TransactionDeleteHandler;
import daruma.wfs.TransactionUpdateHandler;

/* !!! [06/08/22 18:56 I.Noda] !!! 
 * TransactionResultInfo を導入。
 */
import daruma.wfs.TransactionResultInfo ;

import daruma.util.ISO8601DateFormat;
import daruma.util.Itk;

import daruma.xml.Lexicon;
import daruma.xml.NameSpace ;
import daruma.xml.handler.MispDefaultHandler;
import daruma.xml.SAXExceptionObserver;
import daruma.xml.util.XMLFormatConverter;
import daruma.xml.util.ElementUtil;

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


import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.transform.TransformerException;
import javax.xml.parsers.ParserConfigurationException;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;


public class TransactionHandler 
    /* !!! [06/08/18 23:19 I.Noda] !!! 
     * MISPDefaultHandler の導入
     */
    extends MispDefaultHandler
    //extends XSAXDefaultHandler
    implements SAXExceptionObserver
{
	private	StorageManager	storage;
	private	Date		startTime;
	private	Date		endTime;
	private	boolean		errorOccured;
	private	List<Exception>	errors;

	/* !!! [06/08/22 18:56 I.Noda] !!! 
	 * TransactionResultInfo を導入。
	 */
	private TransactionResultInfo resultInfo;

	public	TransactionHandler( OutputStream  out ,  XMLReader  parser ,
				    boolean  isTopLevelHandler ,
				    StorageManager  storage )
	{
		super( out , parser , isTopLevelHandler );

		this.storage = storage;
		this.startTime = null;
		this.endTime = null;
		this.errorOccured = false;
		this.errors = new ArrayList<Exception>();

		/* !!! [06/08/22 18:56 I.Noda] !!! 
		 * TransactionResultInfo を導入。
		 */
		this.resultInfo = new TransactionResultInfo() ;

	}

	/* !!! [06/08/17 17:28 I.Noda] !!! 
	 * ドキュメントの最初でtransaction にユニークな ID を振る。
	 */
	public	void  xStartDocument() throws SAXException
	{
	    try {
		this.setTransactionURI() ;

		this.setTransactionSN
		    (this.storage.registerTransactionURI
		     (this.getTransactionURI())) ;
	    } catch( StorageException  e ) {
		throw new SAXException( e );
	    }
	}

	public	void	notifyError( SAXParseException  e ) throws SAXException
	{
		this.errorOccured = true;
		this.errors.add( e );

		this.finishAndResponse();
	}


    public	void	xStartElement( String uri ,
				       String localName ,
				       String qName ,
				       Attributes  attrs ) throws SAXException
    {
	assert super.getCurrentLevel() == 1
	    || super.getCurrentLevel() == 2;

	try {
	    if ( super.getCurrentLevel() == 1 ) {
		// check tag
		Lexicon.MispTransaction.matchesOrSaxException
		    (uri, null, localName, super.getLocator(), null) ;

		try {
		    this.startTime = this.storage.getCurrentTime();
		} catch(StorageException  ex ) {
		    throw new SAXParseException(ex.getMessage() ,
						super.getLocator() ,
						ex);
		}
		return;
	    }
	    //
	    // Transaction/*
	    //
	    if (Lexicon.MispInsert.matches(uri, localName)) {
		super.setContentHandlerDelegator
		    ( new TransactionInsertHandler
		      ( super.getOutputStream() ,
			super.getParser() ,
			false,	this.storage, this,
			/* !!! [06/08/22 18:56 I.Noda] !!! 
			 * TransactionResultInfo を導入。
			 */
			this.resultInfo) ,
		      uri , localName , qName , attrs );
		return;
	    }
	    else if(Lexicon.MispDelete.matches(uri, localName)) {
		super.setContentHandlerDelegator
		    ( new TransactionDeleteHandler
		      ( super.getOutputStream() ,
			super.getParser() ,
			false, this.storage, this,
			/* !!! [06/08/22 18:56 I.Noda] !!! 
			 * TransactionResultInfo を導入。
			 */
			this.resultInfo) ,
		      uri , localName , qName , attrs );
		return;
	    }
	    else if (Lexicon.MispUpdate.matches(uri, localName)) {
		super.setContentHandlerDelegator
		    ( new TransactionUpdateHandler
		      ( super.getOutputStream() ,
			super.getParser() ,
			false, this.storage, this,
			/* !!! [06/08/22 18:56 I.Noda] !!! 
			 * TransactionResultInfo を導入。
			 */
			this.resultInfo),
		      uri , localName , qName , attrs );
	    }
	    else {
		throw new SAXParseException("unknown top level tag <"
					    + NameSpace.genUName(uri,localName)
					    + ">." ,
					    super.getLocator() );
	    }
	} catch (SAXParseException ex) {
	    /* !!! [06/10/18 11:02 I.Noda] !!! 
	     * エラー処理が、現在、SOAP Error と Transaction Response で
	     * 区別されている。将来、整理しておく必要がある。
	     */
	    //this.printError( ex , "error" );

	    throw ex;
	}
    }


	public	void  xEndDocument() throws SAXException
	{
		this.finishAndResponse();
	}


	public	void  finishAndResponse() throws SAXException
	{
		try
		{
			this.endTime = this.storage.getCurrentTime();

		}
		catch( StorageException  e )
		{
			throw new SAXException( e );
		}

		/* <<< [06/08/19 03:27 I.Noda] <<< 
		 * constructResponseInfo が完成すれば、
		 * 後半部分 (=== 以下)は省略すべし。
		 */
		constructResponseInfo() ;

		/* === [06/08/19 03:27 I.Noda] === */
		/* !!! [06/08/20 22:50 I.Noda] !!! 
		 * DOM による Response 出力への切り替えへの布石。
		 */
		if(! Itk.useDOMResponse) {
		    PrintWriter	out = super.getPrintWriter();

		    String	status;

		    if ( this.errorOccured )
			{
			    status = Lexicon.MispStatus_Failure.localname ;
			}
		    else
			{
			    status = Lexicon.MispStatus_Success.localname ;
			}


		    out.println( "<TransactionResponse"
				 + " xmlns=\"" + NameSpace.MISP.uri + "\"" 
				 + ">");

		    out.println( "  <ElapsedTime xmlns:gml=\"" 
				 + NameSpace.GML.uri + "\">" );

		    out.println( "    <gml:beginPosition>"
				 + new ISO8601DateFormat()
				 .format(this.startTime)
				 + "</gml:beginPosition>" );

		    out.println( "    <gml:endPosition>"
				 + new ISO8601DateFormat()
				 .format(this.endTime)
				 + "</gml:endPosition>" );

		    out.println( "  </ElapsedTime>" );

		    out.println( "  <TransactionResult>" );

		    /* !!! [06/08/17 17:28 I.Noda] !!! 
		     * TransactionURI を出力。
		     */
		    out.println( "    <TransactionID>" 
				 + this.getTransactionURI()
				 + "</TransactionID>" );

		    out.println( "    <Status>" + status + "</Status>" );

		    if ( ! this.errors.isEmpty() )
			{
			    out.println( "    <Error>" );

			    for ( Exception  e  :  this.errors )
				{
				    out.println( "      <Message>" + e.getMessage()
						 + "</Message>" );
				}

			    out.println( "    </Error>" );
			}

		    out.println( "  </TransactionResult>" );
		    out.println( "</TransactionResponse>" );
		    out.flush();

		    try
			{
			    super.getOutputStream().flush();
			}
		    catch( IOException  e )
			{
			    e.printStackTrace();
			}
		}
		/* >>> [06/08/19 03:27 I.Noda] >>> */
	}

    //------------------------------------------------------------
    /* !!! [06/08/19 03:27 I.Noda] !!! 
     * construct ResponseInfo
     */
    private void constructResponseInfo() {
	ResponseInfo responseInfo = this.getResponse() ;
	Element response = 
	    ElementUtil.genElementSimple
	    (Lexicon.MispTransactionResponse,
	     null, responseInfo.document, responseInfo.leaf, true) ;
	responseInfo.setResponseToLeaf(response) ;

	responseInfo.addResponseStatusToLeaf(this.getTransactionURI(),
					     this.startTime, this.endTime) ;

	Element transResult =
	    ElementUtil.genElementSimple
	    (Lexicon.MispTransactionResult, null,
		 responseInfo.document, response, true) ;
	// Element transID =
	    ElementUtil.genElementSimple
	    (Lexicon.MispTransactionID, this.getTransactionURI(),
		 responseInfo.document, transResult, true) ;

	// Element status =
	    ElementUtil.genElementSimple
	    (Lexicon.MispStatus,
	     (this.errorOccured ?
	      Lexicon.MispStatus_Failure.localname :
	      Lexicon.MispStatus_Success.localname),
	     responseInfo.document, transResult, true) ;

	/* !!! [06/08/22 18:56 I.Noda] !!! 
	 * count 部分の生成
	 */
	if(this.resultInfo.countInsert >= 0) {
	    ElementUtil.genElementSimple
		(Lexicon.MispInsertCount,
		 Long.toString(this.resultInfo.countInsert),
		 responseInfo.document, transResult, true) ;
	}
	if(this.resultInfo.countUpdate >= 0) {
	    ElementUtil.genElementSimple
		(Lexicon.MispUpdateCount,
		 Long.toString(this.resultInfo.countUpdate),
		 responseInfo.document, transResult, true) ;
	}
	if(this.resultInfo.countDelete >= 0) {
	    ElementUtil.genElementSimple
		(Lexicon.MispDeleteCount,
		 Long.toString(this.resultInfo.countDelete),
		 responseInfo.document, transResult, true) ;
	}
	    

	if(! this.errors.isEmpty()) {
	    Element error = 
		ElementUtil.genElementSimple
		(Lexicon.MispError, null, 
		 responseInfo.document, transResult, true) ;
	    for ( Exception  ex  :  this.errors ) {
		ElementUtil.genElementSimple
		    (Lexicon.MispErrorMessage, ex.getMessage(),
		     responseInfo.document, error, true) ;
	    }
	}
    }

    //------------------------------------------------------------
	public	void	xFatalError( SAXParseException  exception )
						throws SAXException
	{
		this.printError( exception , "fatal error" );
	}

	public	void	xError( SAXParseException  exception )
						throws SAXException
	{
		this.printError( exception , "error" );
	}

	public	void	xWarning( SAXParseException exception )
						throws SAXException
	{
		this.printError( exception , "warning" );
	}


	protected  void	printError( SAXParseException  e ,  String  type )
							throws SAXException
	{
		Document	doc;

		try
		{
			// XXX: should print TransactionResponse?

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