package daruma.wfs;

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

import daruma.wfs.filter.FilterHandler;

import daruma.xml.handler.MispDefaultHandler;
import daruma.xml.SAXExceptionObserver;
import daruma.xml.XMLTag;
import daruma.xml.URI;
import daruma.xml.SimpleXPath;
import daruma.xml.util.TextUtil;
import daruma.xml.util.XMLParseErrorException;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.TypeDefinition;

import daruma.geometry.TransformationContext;

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

import daruma.util.LogWriter;
import daruma.util.Pair;

import java.io.OutputStream;
import java.util.List;
import java.util.ArrayList;



public class TransactionUpdateHandler
    /* !!! [06/08/18 23:19 I.Noda] !!!
     * MISPDefaultHandler の導入
     */
    extends MispDefaultHandler
    //    extends XSAXDefaultHandler
    implements SAXExceptionObserver
{
	private	enum UpdateMode { PRESERVE , OVERRIDE , RESTORE };

	private	StorageManager		storage;
	private	SAXExceptionObserver	errorReceiver;

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

	private	ElementName		specifiedElement;
	private	TypeDefinition		specifiedElementType;
	private	UpdateMode		updateMode;
	private	FilterHandler		filter;
	private	boolean			filterTagFound;

	private	List<TransactionUpdateHandler.PropertyHandler>	properties;


	public	TransactionUpdateHandler( OutputStream  out ,
					  XMLReader  parser ,
					  boolean  isTopLevelHandler ,
					  StorageManager  storage ,
					  SAXExceptionObserver  errorReceiver,
					  /* !!! [06/08/22 18:56 I.Noda] !!! 
					   * TransactionResultInfo を導入。
					   */
					  TransactionResultInfo resultInfo)
	{
		super( out , parser , isTopLevelHandler );

		this.storage              = storage;
		this.errorReceiver        = errorReceiver;
		this.specifiedElement     = null;
		this.specifiedElementType = null;
		this.updateMode           = UpdateMode.PRESERVE;
		this.filter               = null;
		this.filterTagFound       = false;

		/* !!! [06/08/22 18:56 I.Noda] !!! 
		 * TransactionResultInfo を導入。
		 */
		this.resultInfo  = resultInfo;
		if(this.resultInfo.countUpdate < 0) {
		    this.resultInfo.countUpdate = 0 ;
		}


		this.properties = new ArrayList<TransactionUpdateHandler
						.PropertyHandler>();
	}

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

		XMLTag	t = new XMLTag( uri , localName );

		final SAXParseException	mispNamespaceException
					 = new SAXParseException
					       ( t.getLocalName() + " tag "
						 + "should be in namespace "
						 + URI.MISP ,
						 super.getLocator() );

		if ( super.getCurrentLevel() == 1 )
		{
			if ( ! t.getURI().equals( URI.MISP ) )
			{
				this.throwError( mispNamespaceException );

				throw mispNamespaceException;
			}

			if ( ! t.getLocalName().equals( "Update" ) )
			{
				this.throwError( new SAXParseException
						 ( "unknown tag <"
						   + t.getLocalName() + ">." ,
						   super.getLocator() ) );
			}


			// XXX: should unify codes
			//      with Transaction*.java, GetFeatureHandler.java

			//
			// typeName
			//
			String	typeNameString = attrs.getValue
							( "" , "typeName" );

			if ( typeNameString == null )
			{
				this.throwError( new SAXParseException
						 ( "typeName attribute"
						   + " not found in "
						   + t.getLocalName()
						   + " tag." ,
						   super.getLocator() ) );
			}

			try
			{
				this.specifiedElement
				 = new ElementName
				   ( super.convertQNameStringToUniversalName
							  ( typeNameString ) );
			}
			catch( SAXException  e )
			{
				this.throwError( new SAXParseException
						 ( e.getMessage() ,
						   super.getLocator() ) );
			}


			TypeName	elementTypeName = null;

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

			if ( elementTypeName == null )
			{
				this.throwError
				     ( new SAXParseException
				       ( "element "
					 + specifiedElement.getLocalName()
					 + " in namespace "
					 + specifiedElement.getNamespace()
					 + " not registered" ,
					 super.getLocator() ) );
			}

			try
			{
				this.specifiedElementType
					= this.storage.getTypeDefinition
							( elementTypeName );
			}
			catch( StorageException  e )
			{
				this.throwError
				     ( new SAXParseException
				       ( "type of element "
					 + specifiedElement.getLocalName()
					 + " in namespace "
					 + specifiedElement.getNamespace()
					 + " not registered" ,
					 super.getLocator() ) );
			}



			//
			// update mode
			//
			String	updateModeString
				= attrs.getValue( "" , "mode" );

			if ( updateModeString != null )
			{
				if ( updateModeString.equals("preserve") )
				{
					this.updateMode = UpdateMode.PRESERVE;
				}
				else if ( updateModeString.equals("override") )
				{
					this.updateMode = UpdateMode.OVERRIDE;
				}
				else if ( updateModeString.equals("restore") )
				{
					this.updateMode = UpdateMode.RESTORE;
				}
				else
				{
					this.throwError
					  ( new SAXParseException
					    ( "mode attribute of Update"
					      + " must be one of ["
					      + "preserve|override|restore]" ,
					      super.getLocator() ) );
				}
			}

			return;
		}
		else if ( super.getCurrentLevel() == 2 )
		{
			if ( ! t.getURI().equals( URI.MISP ) )
			{
				this.throwError( mispNamespaceException );

				throw mispNamespaceException;
			}

			if ( this.properties.size() == 0
			  && ! t.getLocalName().equals( "Property" ) )
			{
				SAXParseException
					e = new SAXParseException
					    ( "unexpected tag \""
					      + t.getLocalName()
					      + "\" found, "
					      + "expected was Property tag." ,
					      super.getLocator() );

				this.throwError( e );
			}

			if ( t.getLocalName().equals( "Property" ) )
			{
				if ( this.filterTagFound )
				{
					SAXParseException
					  e = new SAXParseException
						( "Property tag found"
						  + " after Filter tag" ,
						  super.getLocator() );

					this.throwError( e );
				}

				TransactionUpdateHandler.PropertyHandler
				    h = new TransactionUpdateHandler
					      .PropertyHandler
						  ( super.getOutputStream() ,
						    super.getParser() ,
						    false ,
						    this );

				this.properties.add( h );

				super.setContentHandlerDelegator
					( h ,
					  uri , localName , qName , attrs );

				return;
			}
			else if ( t.getLocalName().equals( "Filter" ) )
			{
				if ( this.filterTagFound )
				{
					SAXParseException
					  e = new SAXParseException
						( "Filter multiply specified" ,
						  super.getLocator() );

					this.throwError( e );
				}


				this.filterTagFound = true;

				this.filter = new FilterHandler
						  ( super.getOutputStream() ,
						    super.getParser() ,
						    false ,
						    this.specifiedElementType ,
						    new TransformationContext() ,
						    this.storage ,
						    this );

				super.setContentHandlerDelegator
					( this.filter ,
					  uri , localName , qName , attrs );
				return;
			}
			else
			{
				SAXParseException
					e = new SAXParseException
					    ( "unexpected tag \""
					      + t.getLocalName()
					      + "\" found, "
					      + "expected was"
					      + " Filter or Property tag." ,
					      super.getLocator() );

				this.throwError( e );
			}
		}
	}

	public	void  xEndDocument() throws SAXException
	{
		if ( ! this.filterTagFound )
		{
			this.throwError( new SAXParseException
					 ( "Filter tag not found" ,
					   super.getLocator() ) );
		}

		// XXX
		for ( PropertyHandler  h  :  this.properties )
		{
			LogWriter.qwrite("DEBUG",  "property = [",
					 h.getProperty(), "], ",
					 "value = [",
					 h.getValue(), "]" );
		}


		List< Pair<SimpleXPath,String> >
		    updateValues = new ArrayList< Pair<SimpleXPath,String> >();

		for ( PropertyHandler  h  :  this.properties )
		{
			updateValues.add( new Pair<SimpleXPath, String>
					  (h.getProperty() , h.getValue() ) );
		}


		try
		{
			int count;
			count = this.storage.updateElement
				( this.specifiedElement ,
				  updateValues ,
				  this.filter.getPredicate() ,
				  this.getTransactionSN(),
				  true );

			this.resultInfo.countUpdate += count;

			/*
			if ( this.updateMode.equals
			     ( TransactionUpdateHandler
			       .MODE_PRESERVE ) )
			{
				this.storage.updateElement
					( this.specifiedElement ,
					  this.filter.getPredicate() ,
					  true );
			}
			else if ( this.updateMode.equals
				  ( TransactionUpdateHandler
				    .MODE_OVERRIDE ) )
			{
				this.storage.updateElement
					( this.specifiedElement ,
					  this.filter.getPredicate() ,
					  false );
			}
			else if ( this.updateMode.equals
				  ( TransactionUpdateHandler
				    .MODE_RESTORE ) )
			{
				this.storage.restoreElement
					( this.specifiedElement ,
					  this.filter.getPredicate() );
			}
			*/
		}
		catch( StorageException  e )
		{
			this.throwError( new SAXParseException
					 ( e.getMessage() ,
					   super.getLocator() ) );
		}
	}


	public	void	notifyError( SAXParseException  e ) throws SAXException
	{
		this.errorReceiver.notifyError( e );
	}

	public	void	throwError( SAXParseException  e ) throws SAXException
	{
		this.notifyError( e );

		throw e;
	}


	//
	// static class for handle Property element
	//
	// <Property>
	//   <Name>...</Name>
	//   <Value>...</Value>
	// </Property>
	//
	static private class  PropertyHandler extends MispDefaultHandler
					      implements SAXExceptionObserver
	{
		private	SAXExceptionObserver	errorReceiver;

		private	StringBuilder		propertyBuffer;
		private	StringBuilder		valueBuffer;

		private	boolean			readingProperty;
		private	boolean			readingValue;

		private	SimpleXPath		property;
		private	String			value;

		public	PropertyHandler( OutputStream  out ,
					 XMLReader  parser ,
					 boolean  isTopLevelHandler ,
					 SAXExceptionObserver  errorReceiver )
		{
			super( out , parser , isTopLevelHandler );

			this.errorReceiver   = errorReceiver;
			this.propertyBuffer  = null;
			this.valueBuffer     = null;
			this.readingProperty = false;
			this.readingValue    = false;
			this.property        = null;
			this.value           = null;
		}

		public	SimpleXPath	getProperty()
		{
			return( this.property );
		}

		public	String	getValue()
		{
			return( this.value );
		}


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

			XMLTag	t = new XMLTag( uri , localName );

			final SAXParseException
				mispNamespaceException
					 = new SAXParseException
					       ( t.getLocalName() + " tag "
						 + "should be in namespace "
						 + URI.MISP ,
						 super.getLocator() );

			if ( ! t.getURI().equals( URI.MISP ) )
			{
				this.throwError( mispNamespaceException );

				throw mispNamespaceException;
			}

			if ( super.getCurrentLevel() == 1 )
			{
				if ( ! t.getLocalName().equals( "Property" ) )
				{
					this.throwError
						( new SAXParseException
						  ( "Property tag expected,"
						    + " but was "
						    + "t.getLocalName()" ,
						    super.getLocator() ) );
				}
			}
			else if ( super.getCurrentLevel() == 2 )
			{
				if ( t.getLocalName().equals( "Name" ) )
				{
					if ( this.propertyBuffer != null )
					{
						this.throwError
						( new SAXParseException
						  ( "Name multiply defined" ,
						    super.getLocator() ) );
					}

					this.propertyBuffer
						= new StringBuilder();

					this.readingProperty = true;
				}
				else if ( t.getLocalName()
					  .equals( "Value" ) )
				{
					if ( this.valueBuffer != null )
					{
						this.throwError
						( new SAXParseException
						  ( "Value multiply defined" ,
						    super.getLocator() ) );
					}

					this.valueBuffer = new StringBuilder();

					this.readingValue = true;
				}
				else
				{
					this.throwError
						( new SAXParseException
						  ( "Unexpected tag: "
						    + t.getLocalName() ,
						    super.getLocator() ) );
				}
			}
		}

		public	void  xEndElement( String uri ,
					   String localName ,
					   String qName ) throws SAXException
		{
			XMLTag	t = new XMLTag( uri , localName );

			if ( t.getLocalName().equals( "Name" ) )
			{
				this.readingProperty = false;

				try
				{
					this.property
					 = TextUtil
					   .convertQNamePathToSimpleXPath
					    ( this.propertyBuffer.toString() ,
					      super.getPrefixMap() );
				}
				catch( XMLParseErrorException  e )
				{
					throw new SAXException
						  ( e.getMessage() , e );
				}
			}
			else if ( t.getLocalName().equals( "Value" ) )
			{
				this.readingValue = false;

				this.value = this.valueBuffer.toString();
			}
		}

		@Override
		public void  xCharacters
			     ( char[]  str ,  int  offset ,  int  length )
							    throws SAXException
		{
			if ( this.readingProperty )
			{
				this.propertyBuffer
					.append( str , offset , length );
			}
			else if ( this.readingValue )
			{
				this.valueBuffer
					.append( str , offset , length );
			}
		}

		@Override
		public	void  xEndDocument() throws SAXException
		{
			if ( this.propertyBuffer == null )
			{
				this.throwError( new SAXParseException
						 ( "Property tag not found" ,
						   super.getLocator() ) );
			}

			if ( this.valueBuffer == null )
			{
				this.throwError( new SAXParseException
						 ( "Value tag not found" ,
						   super.getLocator() ) );
			}
		}

		public	void	notifyError( SAXParseException  e )
							 throws SAXException
		{
			this.errorReceiver.notifyError( e );
		}

		public	void	throwError( SAXParseException  e )
							 throws SAXException
		{
			this.notifyError( e );

			throw e;
		}
	}
}
