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.AttributeDefinition;
import daruma.storage_manager.type_definition.ColumnNameFactory;
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.UniversalName;
import daruma.xml.SimpleXPath;
import org.w3c.dom.Element;
import org.w3c.dom.Document;

import daruma.util.Pair;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;


public class ComplexTypeDefinition extends TypeDefinition
{
	private	TypeDefinition				base;

	private	List<AttributeDefinition>		attributes;
	private	Map<UniversalName, AttributeDefinition>	checkMap;

	public	ComplexTypeDefinition( TypeDefinition  base )
	{
		super( true , true );

		this.base = base;

		this.attributes = new ArrayList<AttributeDefinition>();

		this.checkMap = new HashMap<UniversalName,
					    AttributeDefinition>();

	}

	public	void	addAttributeDefinition( AttributeDefinition  attr )
							throws TypeException
	{
		if ( this.checkMap.containsKey( attr.getAttributeName() ) )
		{
			throw new TypeException
				( "attribute [" + attr.getAttributeName() + "]"
				  + " multiply defined" );
		}

		this.attributes.add( attr );
		this.checkMap.put( attr.getAttributeName() , attr );
	}


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


	@Override
	public	List<TableColumnDefinition>	getCompositeSQLDataType
						  ( StorageManager  storage ,
						    SimpleXPath  path )
							throws TypeException
	{
		List<TableColumnDefinition>	columns;

		if ( this.base.getSingleSQLDataType() != null )
		{
			String	c;

			try
			{
				c = ColumnNameFactory.createColumnName
							( path , storage );

			}
			catch( StorageException  e )
			{
				throw new TypeException( e );
			}

			columns = new ArrayList<TableColumnDefinition>();

			columns.add( new TableColumnDefinition
				     ( c ,
				       this.base.getSingleSQLDataType() ) );
		}
		else
		{
			columns = this.base.getCompositeSQLDataType
					( storage , path );
		}


		for ( AttributeDefinition  attr : this.attributes )
		{
			String	columnName;

			try
			{
				columnName
				= ColumnNameFactory.createAttributeColumnName
					( path ,
					  attr.getAttributeName() ,
					  storage );
			}
			catch( StorageException  e )
			{
				throw new TypeException( e );
			}

			columns.add( new TableColumnDefinition
				     ( columnName ,
				       attr.getType()
					      .getSingleSQLDataType() ) );
		}

		return( columns );
	}


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

		base = this.base.createInstance( element ,
						 topLevelElement ,
						 path ,
						 storage ,
						 elementIndex );

		List<TableColumn>	columns = base.getFirst().getColumns();

		for ( AttributeDefinition  attr : this.attributes )
		{
			UniversalName	attrName = attr.getAttributeName();

			boolean matched = element.hasAttributeNS
					  (  attrName.getNamespace() ,
					     attrName.getLocalName() );

			if ( matched
			     && attr.getUseType()
				     == AttributeDefinition.UseType.PROHIBIT )
			{
			    throw new TypeException
				( "element ["
				  + new ElementName( element ) + "]"
				  + " prohibits attribute ["
				  + attr.getAttributeName() + "]" );
			}
			else if ( ! matched
				  && attr.getUseType()
				     == AttributeDefinition.UseType.REQUIRED )
			{
			    throw new TypeException
				( "element ["
				  + new ElementName( element ) + "]"
				  + " requires attribute ["
				  + attr.getAttributeName() + "]" );
			}


			String	value = element.getAttributeNS
						( attrName.getNamespace() ,
						  attrName.getLocalName() );


			String	columnName;

			try
			{
				columnName = ColumnNameFactory
						.createAttributeColumnName
						 ( path ,
						   new ElementName( element ) ,
						   attr.getAttributeName() ,
						   storage );
			}
			catch( StorageException  e )
			{
				throw new TypeException( e );
			}

			if ( matched )
			{
				columns.add( new TableColumn
					     ( new TableColumnDefinition
					       ( columnName ,
						 attr.getType()
						 .getSingleSQLDataType() ) ,
					       value ) );
			}
			else
			{
				columns.add( new TableColumn
					     ( new TableColumnDefinition
					       ( columnName ,
						 attr.getType()
						 .getSingleSQLDataType() ) ,
					       null ) );
			}
		}

		return( new Pair<TypedInstance, Integer>
			( new TypedInstance
			  ( columns , base.getFirst().getType() ) ,
			  base.getSecond() ) );
	}


	@Override
	public	int	convertToXMLElement( Element  element ,
					     Document  doc ,
					     StorageManager  storage ,
					     TransformationContext  trans,
					     List<TableColumn>  columns ,
					     int  index ,
					     long  id )
							throws TypeException
	{
		int	i = this.base.convertToXMLElement( element ,
							   doc ,
							   storage ,
							   trans ,
							   columns ,
							   index ,
							   id );

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


		for ( AttributeDefinition  attr  :  this.attributes )
		{
			UniversalName	attrName = attr.getAttributeName();

			String	value = columns.get( i ).getValue();

			if ( value != null )
			{
				element.setAttributeNS
					( attrName.getNamespace() ,
					  attrName.getLocalName() ,
					  columns.get( i ).getValue() );
			}

			i ++;
		}

		return( i );
	}
}
