package daruma.storage_manager.type_definition;

import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.type_definition.TypedInstance;
import daruma.storage_manager.type_definition.TypeException;
import daruma.storage_manager.type_definition.XMLSchemaElementDefinition;
// XXX: tentative DateTimeTypeDefinition, GeometryPropertyTypeDefinition
import daruma.storage_manager.type_definition.types.DateTimeTypeDefinition;
import daruma.storage_manager.type_definition.types.GeometryPropertyTypeDefinition;
import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;

import daruma.geometry.TransformationContext;

import daruma.sql.SQLDataType;
import daruma.sql.TableColumnDefinition;
import daruma.sql.TableColumn;
import daruma.xml.SimpleXPath;
import daruma.util.Pair;
import daruma.util.LogWriter;

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

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


public abstract class AbstractCompositorTypeDefinition extends TypeDefinition
{
	public static class  Entry
	{
		private	XMLSchemaElementDefinition	atom;

		private	TypeDefinition			composite;

		protected Entry( XMLSchemaElementDefinition  def )
		{
			this.atom      = def;
			this.composite = null;
		}

		protected Entry( TypeDefinition  compositeEntry )
		{
			this.atom      = null;
			this.composite = compositeEntry;
		}

		public boolean	isCompositeEntry()
		{
			return( this.composite != null );
		}

		public XMLSchemaElementDefinition	getAtomEntry()
		{
			return( this.atom );
		}

		public TypeDefinition	getCompositeEntry()
		{
			return( this.composite );
		}
	}


	private	List<Entry>	entries;

	public	AbstractCompositorTypeDefinition()
	{
		super( true , true );

		this.entries = new ArrayList<Entry>();
	}

	public final SQLDataType	getSingleSQLDataType()
	{
		return( null );
	}

	protected final List<Entry>	getEntries()
	{
		return( this.entries );
	}


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

		for( Entry  e : this.getEntries() )
		{
			if ( e.isCompositeEntry() )
			{
				TypeDefinition	d = e.getCompositeEntry();

				ret.addAll( d.getCompositeSQLDataType
					    ( storage , path ) );

				continue;
			}


			TypeDefinition	type = null;

			try
			{
				type = storage.getTypeDefinition
				       ( e.getAtomEntry().getTypeName() );
			}
			catch( StorageException  se )
			{
				throw new TypeException( se );
			}


			ElementName	elementName
					  = e.getAtomEntry().getElementName();

			SimpleXPath	childPrefixSimpleXPath
					  = new SimpleXPath( path );
			childPrefixSimpleXPath.add( elementName );


			if ( ! type.isSQLMultiColumn() )
			{
				String	shortXPathString;
				try
				{
					shortXPathString
					  = storage.getShortXPathStringForDB
						   ( childPrefixSimpleXPath );
				}
				catch( StorageException  se )
				{
					throw new TypeException( se );
				}

				ret.add( new TableColumnDefinition
					 ( shortXPathString ,
					   type.getSingleSQLDataType() ) );
			}
			else
			{
				ret.addAll( type.getCompositeSQLDataType
					    ( storage ,
					      childPrefixSimpleXPath ) );
			}
		}

		return( ret );
	}

	public final void	addChild( XMLSchemaElementDefinition  def )
	{
		LogWriter.qwrite("DEBUG",
				 this.getClass().getName(), ".addChild():" );

		LogWriter.qwrite("DEBUG",
				 " typeName = [", def.getTypeName(), "],",
				 " elementName = [",
				 def.getElementName(), "]" );

		this.entries.add( new Entry( def ) );
	}

	public	void	addChild( TypeDefinition  typeDefinition )
	{
		LogWriter.qwrite("DEBUG",
				 this.getClass().getName(), ".addChild():" );

		LogWriter.qwrite("DEBUG",
				 " class = [",
				 typeDefinition.getClass().getName(), "]" );

		this.entries.add( new Entry( typeDefinition ) );
	}

	public abstract	Pair<TypedInstance, Integer>
				createInstance( Element  element ,
						ElementName  topLevelElement ,
						SimpleXPath  path ,
						StorageManager  storage ,
						int  elementIndex )
							  throws TypeException;

	@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()" );

		int	columnIndex = index;

		for( Entry  e  :  this.getEntries() )
		{
			LogWriter.qwrite( "DEBUG",
					  "column index = ", columnIndex );

			if ( e.isCompositeEntry() )
			{
				columnIndex = e.getCompositeEntry()
						.convertToXMLElement
						 ( element ,
						   doc ,
						   storage ,
						   trans ,
						   columns ,
						   columnIndex ,
						   id );

				continue;
			}


			XMLSchemaElementDefinition	a = e.getAtomEntry();

			TypeName	childTypeName    = a.getTypeName();
			ElementName	childElementName = a.getElementName();
			TypeDefinition	childType        = a.getType();


			Element	child = doc.createElementNS
					( childElementName.getNamespace() ,
					  childElementName.getLocalName() );

			TableColumn	c = columns.get( columnIndex );
			String		value = c.getValue();

			// XXX: tentative
			if ( value != null
			  && childType instanceof DateTimeTypeDefinition
			  && value.equals("") )
			{
				value = null;
			}


			LogWriter.qwrite( "DEBUG",
					  "element = ", childTypeName );
			LogWriter.qwrite( "DEBUG",
					  "type = ", childType );

			LogWriter.qwrite( "DEBUG",
					  "column == null? ",
					  (value == null) );
			LogWriter.qwrite( "DEBUG",
					  "value = [" + value + "]" );

			if ( ! childType.isXMLComplexType()
			  && ! childType.isSQLMultiColumn() )
			{
				if ( value != null )
				{
					element.appendChild( child );

					child.appendChild( doc.createTextNode
							   ( value ) );
				}

				columnIndex ++;
			}
			else
			{
				// XXX: assume that if first column's value
				//      is null, element was omitted
				if ( value == null
				     && ! (childType instanceof GeometryPropertyTypeDefinition) )
				{
					// XXX: skip columns
					columnIndex
					  += childType.getCompositeSQLDataType
						    ( storage , null ).size();
				}
				else
				{
					element.appendChild( child );

					columnIndex = childType
						      .convertToXMLElement
						       ( child ,
							 doc ,
							 storage ,
							 trans ,
							 columns ,
							 columnIndex ,
							 id );
				}
			}

			LogWriter.qwrite( "DEBUG" , "" );
		}

		return( columnIndex );
	}


	public	void	debugPrint()
	{
		for( Entry  e : this.entries )
		{
			if ( e.isCompositeEntry() )
			{
				LogWriter.qwrite("DEBUG",  "composite, " );

				TypeDefinition	t = e.getCompositeEntry();

				LogWriter.qwrite("DEBUG",  
						 t.getClass().getName() );
			}
			else
			{
				LogWriter.qwrite
				    ("DEBUG", "atomic: ",
				     e.getAtomEntry().getTypeName(),
				     ": ",
				     e.getAtomEntry().getElementName() );
			}
		}
	}
}
