package daruma.wfs;

import daruma.wfs.filter.FilterHandler;
import daruma.wfs.TransactionResultInfo ;

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

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.ElementInfo;
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.storage_manager.type_definition.TypedInstance;
import daruma.storage_manager.type_definition.TypeException;

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;
import java.util.Set;
import java.util.HashSet;

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


public class TransactionUpdateHandler
    extends MispDefaultHandler
    //    extends XSAXDefaultHandler
    implements SAXExceptionObserver
{
	private	enum UpdateMode { PRESERVE , OVERRIDE , RESTORE };

	private	StorageManager		storage;
	private	SAXExceptionObserver	errorReceiver;

	private TransactionResultInfo	resultInfo;

	private	ElementName		specifiedElement;
	private	TypeDefinition		specifiedElementType;
	private	ElementInfo		specifiedElementInfo;

	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,
					  TransactionResultInfo resultInfo)
	{
		super( out , parser , isTopLevelHandler );

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

		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() ) );
			}


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


			//
			// set element type
			//
			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() ) );
			}


			//
			// set element info
			//
			try
			{
				this.specifiedElementInfo
					= this.storage.getElementInfo
					       ( this.specifiedElement );
			}
			catch( StorageException  e )
			{
			    this.throwError
				( new SAXParseException
				  ( "Can't get information of "
				    + "element "
				    + specifiedElement.getLocalName()
				    + " in namespace "
				    + specifiedElement.getNamespace()
				    + ".",
				    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.storage,
						    this.specifiedElementInfo,
						    this.specifiedElement );

				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.specifiedElement ,
						    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() ) );
		}


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

		Set<SimpleXPath> addedProperties = new HashSet<SimpleXPath>();

		for ( PropertyHandler h : this.properties )
		{
			final SimpleXPath property = h.getProperty();

			if ( addedProperties.contains( property ) )
			{
				this.throwError
				     ( new SAXParseException
				       ( "Can't update: "
					 + "property \"" + property
					 + "\" multiply specified",
					 super.getLocator() ) );
			}

			addedProperties.add( property );

			updateValues.add( new Pair<SimpleXPath, TypedInstance>
					  ( h.getProperty(), h.getObj() ) );
		}

		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	StorageManager		storage;
		private	ElementInfo		elementInfo;
		private	ElementName		elementName;

		private	StringBuilder		propertyBuffer;

		private	boolean			readingProperty;

		private	SimpleXPath		property;
		private	String			value;

		private TransactionUpdateHandler.ValueHandler valueHandler;

		public	PropertyHandler( OutputStream  out ,
					 XMLReader  parser ,
					 boolean  isTopLevelHandler ,
					 SAXExceptionObserver  errorReceiver,
					 StorageManager storage,
					 ElementInfo elementInfo,
					 ElementName elementName )
		{
			super( out , parser , isTopLevelHandler );

			this.errorReceiver   = errorReceiver;
			this.storage         = storage;
			this.elementInfo     = elementInfo;
			this.elementName     = elementName;
			this.propertyBuffer  = null;
			this.readingProperty = false;
			this.property        = null;
			this.value           = null;
			this.valueHandler    = null;
		}

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

		public TypedInstance getObj()
		{
		    return this.valueHandler.getObj();
		}

		@Override
		public	void	xStartElement( String uri ,
					       String localName ,
					       String qName ,
					       Attributes attrs )
							throws SAXException
		{
			final 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" ) )
				{
				    this.valueHandler
					= new TransactionUpdateHandler
					      .ValueHandler
						  ( super.getOutputStream(),
						    super.getParser(),
						    false,
						    this,
						    this.storage,
						    this.elementInfo,
						    this.elementName,
						    this.property );

				    super.setContentHandlerDelegator
					( this.valueHandler,
					  uri, localName, qName, attrs );
				}
				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 );
				}
			}
		}

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

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

			if ( this.propertyBuffer == null )
			{
				this.throwError( new SAXParseException
						 ( "Property 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;
		}
	}


	static private class  ValueHandler extends XSAXDOMCreateHandler
					      implements SAXExceptionObserver
	{
	    private SAXExceptionObserver errorReceiver;

	    private StorageManager storage;
	    private ElementInfo elementInfo;
	    private ElementName topLevelElementName;
	    private SimpleXPath path;

	    private TypedInstance obj;

	    public ValueHandler( OutputStream out,
				 XMLReader parser,
				 boolean isTopLevelHandler,
				 SAXExceptionObserver errorReceiver,
				 StorageManager storage,
				 ElementInfo elementInfo,
				 ElementName topLevelElementName,
				 SimpleXPath path )
	    {
		super( out, parser, isTopLevelHandler );

		this.errorReceiver       = errorReceiver;
		this.storage             = storage;
		this.elementInfo         = elementInfo;
		this.topLevelElementName = topLevelElementName;
		this.path                = path;

		this.obj                 = null;
	    }

	    public TypedInstance getObj()
	    {
		return this.obj;
	    }

	    public void xEndDocument() throws SAXException
	    {
		Element	valueElement = super.getDocumentElement();

		UniversalName name;

		if ( path.getList().isEmpty() )
		{
		    name = topLevelElementName;
		}
		else
		{
		    name = path.getList().get( path.getList().size() - 1 );
		}

		Element element = ElementUtil.getNameReplacedElement
				  ( valueElement, name,
				    super.getDOMDocument() );

		LogWriter.qwrite("DEBUG", "data = [", valueElement, "]") ;

		LogWriter.qwrite("DEBUG", "replaced data = [", element, "]");
		LogWriter.qwrite("DEBUG", "path = [" + path + "]" );

		try
		{
		    TypeDefinition type
			    = this.elementInfo.getTypeDefinition( path );
		    LogWriter.qwrite( "DEBUG",
				      "type = [", type.getClass(), "]" );

		    SimpleXPath elementPath;
		    if ( path.getList().isEmpty() )
		    {
			elementPath = new SimpleXPath();
		    }
		    else
		    {
			elementPath = new SimpleXPath
					  ( path.getList().subList
					    ( 0, path.getList().size() - 1 ) );
		    }

		    Pair<TypedInstance, Integer>
			r = type.createInstance( element,
						 this.topLevelElementName,
						 elementPath,
						 this.storage,
						 0 );
		    this.obj = r.getFirst();

		    if ( ElementUtil.hasExtraChildElements
			 ( element, r.getSecond() ) )
		    {
			List<Element> ex = ElementUtil.getExtraChildElements
						       ( element,
							 r.getSecond() );

			for( Element e : ex )
			{
			    ElementUtil.debugPrint( e );
			}

			throw new TypeException( "too many child elements of "
						 + new ElementName( element )
								.toString() );
		    }
		}
		catch( TypeException  e )
		{
		    this.throwError( new SAXParseException
				     ( "invalid element "
				       + this.topLevelElementName
				       + ": " + e.getMessage(),
				       super.getLocator() ) );
		}

		if ( this.obj == null )
		{
			this.throwError
			     ( new SAXParseException
			       ( "invalid element "
				 + this.topLevelElementName.getLocalName()
				 + " in namespace "
				 + this.topLevelElementName.getNamespace(),
				 super.getLocator() ) );
		}
	    }

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

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