package daruma.storage_manager.type_definition.types;

import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.TypedInstance;
import daruma.storage_manager.type_definition.TypeException;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.XMLSchemaElementDefinition;
import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;
import daruma.storage_manager.ElementInfo;

import daruma.geometry.TransformationContext;
import daruma.sql.SQLDataType;
import daruma.sql.SQLDataTypeConstant;
import daruma.sql.TableColumnDefinition;
import daruma.sql.TableColumn;
import daruma.xml.UniversalName;
import daruma.xml.SimpleXPath;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLFormatConverter;
import daruma.util.Pair;
import daruma.util.LogWriter;

import javax.xml.transform.TransformerException;

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

import java.util.List;
import java.util.ArrayList;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;


public class MultipleOccursElementTypeDefinition extends TypeDefinition
{
	private	TypeName	typeName;
	private	ElementName	elementName;
	private	long		minOccurs;
	private	long		maxOccurs;

	public	MultipleOccursElementTypeDefinition( TypeName  typeName ,
						     ElementName  elementName ,
						     long  minOccurs ,
						     long  maxOccurs )
	{
		super( true , true );

		super.setIsSQLExternalTable( true );

		this.typeName = typeName;
		this.elementName = elementName;
		this.minOccurs = minOccurs;
		this.maxOccurs = maxOccurs;
	}

	@Override
	public SQLDataType	getSingleSQLDataType()
	{
		return( null );
	}

	@Override
	public	List<TableColumnDefinition>	getCompositeSQLDataType
						  ( StorageManager  storage ,
						    SimpleXPath  path,
						    ElementInfo  elementInfo )
							throws TypeException
	{
		List<TableColumnDefinition>
			ret = new ArrayList<TableColumnDefinition>();

		SimpleXPath	newPath = new SimpleXPath( path ,
							   this.elementName );

		LogWriter.qwrite( "DEBUG",
				  "MultipleOccursElementTypeDefinition [",
				  newPath, "], name = [",
				  this.elementName, "], type name = [",
				  this.typeName, "]" );

		String	shortXPathString;
		try
		{
			shortXPathString
			    = storage.getShortXPathStringForDB( newPath );
		}
		catch( StorageException  e )
		{
			throw new TypeException( e );
		}

		TableColumnDefinition	c = new TableColumnDefinition
						( shortXPathString ,
						  SQLDataTypeConstant.BLOB );
		c.setIsExternalTable( true );
		ret.add( c );

		return( ret );
	}


	@Override
	public	Pair<TypedInstance, Integer>
				createInstance( Element  element ,
						ElementName  topLevelElement ,
						SimpleXPath  path ,
						StorageManager  storage ,
						int  elementIndex )
							throws TypeException
	{
		//
		// debug print
		//
		LogWriter.qwrite("DEBUG", "");
		LogWriter.qwrite("DEBUG", "");
		LogWriter.qwrite("DEBUG",  this.getClass().getName(),
				 ": createInstance()" );
		LogWriter.qwrite("DEBUG", element) ;


		NodeList	childNodes = element.getChildNodes();
		int		nThChildNode = elementIndex;

		long		nOccurs = 0;

		List<String>	values = new ArrayList<String>();

		while( nOccurs <= this.maxOccurs
		    || this.maxOccurs == XMLSchemaElementDefinition
						.MAX_OCCURS_UNBOUNDED )
		{
			Node	n = null;

			while( nThChildNode < childNodes.getLength() )
			{
				n = childNodes.item( nThChildNode );

				nThChildNode ++;

				if ( n instanceof Element )
				{
					// XXX: check tag equals <annotation>

					break;
				}
			}

			LogWriter.qwrite("DEBUG",
					 "nThChildNode = ", nThChildNode );

			if ( nThChildNode >= childNodes.getLength() )
			{
				break;
			}

			Element		e = (Element)n;
			ElementName	elementName = new ElementName( e );

			LogWriter.qwrite("DEBUG",
					 "elementName = ", elementName );
			LogWriter.qwrite("DEBUG",
					 "this.elementName = ",
					 this.elementName );

			if ( elementName.equals( this.elementName ) )
			{
				LogWriter.qwrite("DEBUG",
						 "element mached!!" );

				nOccurs ++;


				ByteArrayOutputStream
					buf = new ByteArrayOutputStream();

				try
				{
					XMLFormatConverter.print( e , buf ,
								  false );
				}
				catch( TransformerException  te )
				{
					throw new TypeException
						( "Can't convert XML to text" ,
						  te );
				}

				values.add( buf.toString() );
			}
			else
			{
				nThChildNode --;

				LogWriter.qwrite("DEBUG",
						 "element unmached!!" );

				break;
			}
		}

		LogWriter.qwrite("DEBUG",  "nOccurs = ", nOccurs );

		if ( nOccurs < this.minOccurs )
		{
			throw new TypeException
				( "too few elements,"
				  + " expected at least "
				  + this.minOccurs
				  + " element"
				  + (this.minOccurs == 1 ? "" : "s")
				  + " of " + this.elementName );
		}
		else if ( this.maxOccurs != XMLSchemaElementDefinition
							  .MAX_OCCURS_UNBOUNDED
		       && nOccurs > this.maxOccurs )
		{
			throw new TypeException
				( "too many elements,"
				  + " expected up to "
				  + this.maxOccurs
				  + " element"
				  + (this.maxOccurs == 1 ? "" : "s")
				  + " of " + this.elementName );
		}


		//
		// create return value
		//
		SimpleXPath	p;
		if ( path == null )
		{
			p = null;
		}
		else
		{
			p = new SimpleXPath( path ,
					     new UniversalName( element ) );
		}

		List<TableColumnDefinition>
			defs = this.getCompositeSQLDataType
				    ( storage , p , null );

		List<TableColumn>	columns = new ArrayList<TableColumn>();

		SimpleXPath	tableNamePath = new SimpleXPath
						( topLevelElement ,
						  new SimpleXPath
						  ( p ,
						    this.elementName ) );

		String	tableName;
		try
		{
			tableName = storage.getShortXPathStringForDB
							( tableNamePath );
		}
		catch( StorageException  e )
		{
			throw new TypeException
				( "internal error: "
				  + "cannot convert element "
				  + topLevelElement.toString()
				  + " to ID: " + e.getMessage() ,
				  e );
		}

		TableColumn	c = new TableColumn( defs.get(0) , tableName );
		c.setSubValues( values );

		columns.add( c );

		return( new Pair<TypedInstance, Integer>
			( new TypedInstance( columns , this ) ,
			  nThChildNode ) );
	}


	@Override
	public	int	convertToXMLElement( Element  element ,
					     Document  doc ,
					     StorageManager  storage ,
					     TransformationContext  trans ,
					     List<TableColumn>  columns ,
					     int  index ,
					     long  id )
							throws TypeException
	{
		LogWriter.qwrite("DEBUG",  this.getClass().getName(),
				 ".convertToXMLElement()" );

		if ( index >= columns.size() )
		{
			throw new TypeException
			  ( "internal error in " + this.getClass().getName()
			    + ": unexpected instantiation" );
		}

		TableColumn	c = columns.get( index );

		String	tableName = c.getValue();

		if ( tableName == null )
		{
			return( index + 1 );
		}


		List<String>	values;

		try
		{
			values = storage.getSubTableValues( tableName , id );
		}
		catch( StorageException  e )
		{
			throw new TypeException( e.getMessage() , e );
		}

		for ( String  v  :  values )
		{
			try
			{
				XMLFormatConverter.readNode
					( new ByteArrayInputStream
					  ( v.getBytes() ) ,
					  element );
			}
			catch( TransformerException  e )
			{
				throw new TypeException( e.getMessage() , e );
			}
		}

		return( index + 1 );
	}
}
