package daruma.xml.handler;

import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.Attributes;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.Locator;
import org.xml.sax.helpers.LocatorImpl;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.PrintStream;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Date;

import daruma.xml.util.EndOfDocumentException;
import daruma.xml.util.PrefixMap;
import daruma.xml.UniversalName;
import daruma.xml.QName;

public class XSAXDefaultHandler extends DefaultHandler
{
	private	OutputStream	outputStream;
	private	PrintWriter	out;
	private	PrintStream	log;

	private	XSAXDefaultHandler	parentHandler;

	private	XMLReader	parser;
	private	int		level;

	private	Locator		locator;

	private	boolean		debugPrintFlag;

	private	PrefixMap	prefixMap;

	private	boolean		throwEODWithEndOfTopLevelElement;
	private	boolean		isTopLevelHandler;
	private	boolean		inhibitEndPrefixBeforeEndDocument;

	public	XSAXDefaultHandler( OutputStream  out ,  XMLReader  parser ,
				    boolean  isTopLevelHandler )
	{
		this.outputStream = out;
		this.out = new PrintWriter( this.outputStream );
		this.log = System.err;
		this.parser = parser;
		this.level = 0;

		this.debugPrintFlag = false;

		this.locator = new LocatorImpl();
		this.prefixMap = new PrefixMap();
		this.throwEODWithEndOfTopLevelElement = false;
		this.isTopLevelHandler = isTopLevelHandler;
		this.inhibitEndPrefixBeforeEndDocument = false;
	}


	/**
	 * set enable/disable degug print
	 */
	public final void setDebugFlag( boolean  flag )
	{
		this.debugPrintFlag = flag;
	}

	public final Locator  getLocator()
	{
		return( this.locator );
	}

	public	final boolean  isTopLevelHandler()
	{
		return( this.isTopLevelHandler );
	}


	public final void  setThrowEODWithEndOfTopLevelElement
				( boolean  throwEODWithEndOfTopLevelElement )
	{
		this.throwEODWithEndOfTopLevelElement
			= throwEODWithEndOfTopLevelElement;
	}

	public	final void  setInhibitEndPrefixBeforeEndDocument
				( boolean  inhibitEndPrefixBeforeEndDocument )
	{
		this.inhibitEndPrefixBeforeEndDocument
			= inhibitEndPrefixBeforeEndDocument;
	}

	/**
	 * returns a Map which has 'prefix' key to identify 'namespace'
	 *
	 * before parsing or error has occured, this method returns empty map
	 */
	public	PrefixMap	getPrefixMap()
	{
		return( this.prefixMap );
	}


	/**
	 * returns an UniversalName which bound to QName
	 */
	public	UniversalName	convertQNameStringToUniversalName
							( String  qNameString )
							    throws SAXException
	{
		QName	qName = new QName( qNameString );

		if ( ! qName.isValid() )
		{
			throw new SAXException( "invalid QName ["
						+ qNameString + "]" );
		}


		String	ns = this.prefixMap.get( qName.getPrefix() );

		if ( ns == null )
		{
			throw new SAXException( "unbound prefix ["
						+ (qName.getPrefix() != null ?
						   qName.getPrefix() : "")
						+ "]" );
		}

		return( new UniversalName( ns , qName.getLocalName() ) );
	}



	@Override
	public	void	setDocumentLocator( Locator  locator )
	{
		super.setDocumentLocator( locator );
		this.locator = locator;
	}

	public final void  startDocument() throws SAXException
	{
		this.level = 0;

		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xStartDocument" );
			this.log.flush();
		}

		this.xStartDocument();
	}


	public final void  endDocument() throws SAXException
	{
		if ( ! this.inhibitEndPrefixBeforeEndDocument )
		{
			Set<String>  prefixSet = new HashSet<String>
						 ( this.prefixMap.keySet() );

			for ( String  prefix : prefixSet )
			{
				this.endPrefixMapping( prefix );
			}

			this.prefixMap.clear();
		}


		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName()
					  + ": xEndDocument" );
			this.log.flush();
		}

		this.xEndDocument();


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


		if ( this.parentHandler != null )
		{
			if ( this.debugPrintFlag )
			{
				this.log.println( this.getClass().getName()
						  + ": return to parent "
						  + this.parentHandler
						    .getClass().getName() );

				this.log.println( this.getClass().getName()
						  + ": isTopLevelHandler = "
						  + this.isTopLevelHandler );

				this.log.flush();
			}


			this.parser.setContentHandler( this.parentHandler );
			this.parser.setErrorHandler( this.parentHandler );

			this.parentHandler.checkDocumentEnd();
			this.parentHandler.takeBackControlHook();
		}
	}


	public final void  startElement( String uri ,
					 String localName ,
					 String qName ,
					 Attributes  attrs )
						throws SAXException
	{
		this.level ++;

		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xStartElement: "
					  + "localName = [" + localName + "], "
					  + "uri = [" + uri + "]" );

			this.log.println( this.getClass().getName() + ": "
					  + "level = " + this.level );

			this.log.flush();
		}

		this.xStartElement( uri , localName , qName , attrs );
	}


	public final void  endElement( String uri ,
				       String localName ,
				       String qName ) throws SAXException
	{
		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xEndElement  : "
					  + "localName = [" + localName + "], "
					  + "uri = [" + uri + "]" );
			this.log.flush();
		}
		this.xEndElement( uri , localName , qName );

		this.level --;

		this.checkDocumentEnd();
	}


	protected void takeBackControlHook() throws SAXException
	{
	}

	private void  checkDocumentEnd() throws SAXException
	{
		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "checkDocumentEnd" );
			this.log.println( this.getClass().getName() + ": "
					  + "level = " + this.level );
			this.log.flush();
		}

		if ( this.level == 0 )
		{
			this.endDocument();

			if ( this.throwEODWithEndOfTopLevelElement )
			{
				throw new EndOfDocumentException();
			}
		}
	}


	public final void  characters( char[]  str ,
				       int  offset ,  int  length )
							throws SAXException
	{
		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xCharacters  : "
					  + "str = ["
					  + (new String( str ,
							 offset ,
							 length ))
						 .replace( "\n" , "\\n" )
					  + "]" );
			this.log.flush();
		}

		this.xCharacters( str , offset , length );
	}

	public final void  ignorableWhitespace( char[]  str ,
						int  offset ,  int  length )
							throws SAXException
	{
		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xIgnorableWhitespace: "
					  + "str = ["
					  + (new String( str ,
							 offset ,
							 length ))
						 .replace( "\n" , "\\n" )
					  + "]" );
			this.log.flush();
		}

		this.xIgnorableWhitespace( str , offset , length );
	}


	public final void  startPrefixMapping( String  prefix ,  String  uri )
							throws SAXException
	{
		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xStartPrefixMapping: "
					  + "prefix = [" + prefix + "], "
					  + "uri = " + uri );
			this.log.flush();
		}

		this.xStartPrefixMapping( prefix , uri );

		this.prefixMap.put( prefix , uri );
	}

	public final void  endPrefixMapping( String  prefix )
							throws SAXException
	{
		/*
		if ( this.inhibitEndPrefixMapping )
		{
			return;
		}
		*/

		if ( this.debugPrintFlag )
		{
			this.log.println( this.getClass().getName() + ": "
					  + "xEndPrefixMapping  : "
					  + "prefix = [" + prefix + "]" );
			this.log.flush();
		}

		this.xEndPrefixMapping( prefix );

		this.prefixMap.remove( prefix );
	}


	public	void	fatalError( SAXParseException  exception )
						throws SAXException
	{
		this.xFatalError( exception );
	}

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

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



	//
	// wrapped callbacks
	//
	protected  void  xStartDocument() throws SAXException
	{
	}

	protected  void  xEndDocument() throws SAXException
	{
	}

	protected  void  xStartElement( String uri ,
					String localName ,
					String qName ,
					Attributes  attrs ) throws SAXException
	{
	}

	protected  void  xEndElement( String uri ,
				      String localName ,
				      String qName ) throws SAXException
	{
	}

	protected  void  xCharacters( char[]  str ,
				      int  offset ,  int  length )
							   throws SAXException
	{
	}

	protected  void  xIgnorableWhitespace( char[]  str ,
					       int  offset ,  int  length )
							   throws SAXException
	{
	}

	protected  void  xStartPrefixMapping( String  prefix ,  String  uri )
							throws SAXException
	{
	}

	protected  void  xEndPrefixMapping( String  prefix )
							throws SAXException
	{
	}

	protected  void  xFatalError( SAXParseException  exception )
						throws SAXException
	{
	}

	protected  void  xError( SAXParseException  exception )
						throws SAXException
	{
	}

	protected  void  xWarning( SAXParseException exception )
						throws SAXException
	{
	}


	public	XMLReader	getParser()
	{
		return( this.parser );
	}

	public	OutputStream	getOutputStream()
	{
		return( this.outputStream );
	}

	public	PrintWriter	getPrintWriter()
	{
		return( this.out );
	}


	public  int	getCurrentLevel()
	{
		return( this.level );
	}

	protected  void  setParentHandler( XSAXDefaultHandler  parentHandler )
	{
		this.parentHandler = parentHandler;
	}

	/* !!! [06/08/18 23:19 I.Noda] !!! 
	 * MispDefaultHandler でアクセスできるようにする。
	 */
	protected  XSAXDefaultHandler  getParentHandler()
	{
	    return (this.parentHandler) ;
	}

	protected  void  setDelegator( XSAXDefaultHandler  h ,
				       String uri ,
				       String localName ,
				       String qName ,
				       Attributes  attrs ) throws SAXException
	{
		this.level --;

		h.setParentHandler( this );
		h.setDebugFlag( this.debugPrintFlag );

		this.parser.setContentHandler( h );
		this.parser.setErrorHandler( h );

		try
		{
			h.startDocument();

			for( Map.Entry<String, String>  e
				     : this.prefixMap.entrySet() )
			{
				h.startPrefixMapping( e.getKey() ,
						      e.getValue() );
			}

			h.startElement( uri , localName , qName , attrs );
		}
		catch( SAXException e )
		{
			throw e;
		}
	}


	protected  void  setContentHandlerDelegator
					( XSAXDefaultHandler  h ,
					  String uri ,
					  String localName ,
					  String qName ,
					  Attributes  attrs )
							throws SAXException
	{
		this.level --;

		h.setParentHandler( this );
		h.setDebugFlag( this.debugPrintFlag );

		this.parser.setContentHandler( h );

		try
		{
			h.startDocument();

			for( Map.Entry<String, String>  e
				     : this.prefixMap.entrySet() )
			{
				h.startPrefixMapping( e.getKey() ,
						      e.getValue() );
			}

			h.startElement( uri , localName , qName , attrs );
		}
		catch( SAXException e )
		{
			throw e;
		}
	}
}
