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.NoNeedToConvertTypeException;
import daruma.storage_manager.type_definition.ColumnNameFactory;
import daruma.storage_manager.type_definition.ElementName;
import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;

import daruma.geometry.TransformationContext;
import daruma.geometry.TransformationException;
import daruma.geometry.CoordinateSystem;

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.URI;
import daruma.xml.Lexicon;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLParseErrorException;
import daruma.xml.util.XMLFormatConverter;

import daruma.geometry.DrmGeometry;
import daruma.geometry.DrmPoint;
import daruma.geometry.DrmLineString;
import daruma.geometry.DrmPolygon;
import daruma.geometry.GeometryFormatConverter;
import daruma.geometry.GeometryException;
import daruma.util.Itk;
import daruma.util.Pair;
import daruma.util.EqualChecker;
import daruma.util.LogWriter;
import daruma.global_switch.ImplementationSwitches;

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

import javax.xml.transform.TransformerException;
import java.util.List;
import java.util.ArrayList;

import javax.xml.transform.TransformerException;

import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;


public class GeometryPropertyTypeDefinition extends TypeDefinition
{
	/* !!! [06/08/16 19:12 I.Noda] !!! 
	 * カラム名の拡張部分の文字列定数
	 */
	final static private String ColNameGeoValueSuffix   = "_geo_" ;
	final static public  String ColNameGeoSrsNameSuffix = "_srs_name_" ;
	final static private String ColNameGeoXmlSuffix     = "_xml_" ;


	/* !!! [06/08/24 15:39 I.Noda] !!! 
	 * 以下、どこか適切なところで設定・定義できる必要がある。
	 */
	final static public String DefaultSrsName = "#system" ;


	final static private int ColIndexGeoValue  = 0;
	final static private int ColIndexSrsName   = 1;
	final static private int ColIndexGeoXml    = 2;

	final static private int NUMBER_OF_COLUMNS = 3;

	public	GeometryPropertyTypeDefinition()
	{
		super( true , true );
	}

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

	//------------------------------------------------------------
	/**
	 * Column の定義のリストを返す。
	 */
	@Override
	public	List<TableColumnDefinition>	getCompositeSQLDataType
						  ( StorageManager  storage ,
						    SimpleXPath  path )
							throws TypeException
	{
	    /* --------------------
	     * return value は最初は空リスト 
	     */
	    List<TableColumnDefinition>
		ret = new ArrayList<TableColumnDefinition>();

	    /* --------------------
	     * カラム名の本体の取得 
	     */
	    String colNameBody = null ;
	    
	    if( path == null )
	    {
		/* top level element の場合 */
		colNameBody = ColumnNameFactory.getTopLevelElementColumnName();
	    }
	    else
	    {
		/* sub level element の場合 */
		SimpleXPath	newPath = new SimpleXPath( path );

		try
		{
		    colNameBody = storage.getShortXPathStringForDB( newPath );
		}
		catch( StorageException  ex )
		{
		    throw new TypeException( ex );
		}
	    }

	    /* --------------------
	     * 補助カラムの生成
	     * 別の箇所で参照するため、順序が大事。
	     */
	    ret.add(new TableColumnDefinition
		    (ColumnNameFactory
		     .getColumnName(colNameBody, this.ColNameGeoValueSuffix),
		     new SQLDataType("geometry", "AsText",DrmGeometry.class)));

	    ret.add(new TableColumnDefinition
		    (ColumnNameFactory
		     .getColumnName(colNameBody, this.ColNameGeoSrsNameSuffix),
		     SQLDataTypeConstant.BLOB ) );

	    ret.add(new TableColumnDefinition
		    (ColumnNameFactory
		     .getColumnName(colNameBody, this.ColNameGeoXmlSuffix),
		     SQLDataTypeConstant.BLOB ) );
	    /* >>> [06/08/24 22:36 I.Noda] >>> */

	    return( ret );
	}

    //------------------------------------------------------------
    /**
     * SQL へ入力するための情報を返す。
     * 返す情報は、TypedInstance と、element の子ノードの数のペア。
     * TypedInstance はカラム情報のリストとカラム定義（このクラスインタンス）
     * のペア。
     */
	@Override
	public	Pair<TypedInstance, Integer>
				createInstance( Element  element ,
						ElementName  topLevelElement ,
						SimpleXPath  path ,
						StorageManager  storage ,
						int  elementIndex )
							throws TypeException
	{
		//
		// debug print
		//
		LogWriter.qwrite("DEBUG",  this.getClass().getName(),
				 ": createInstance()" );
		LogWriter.qwrite("DEBUG",  "elementIndex = ", elementIndex );

		/* <<< [06/08/17 15:14 I.Noda] <<< */
		//ElementUtil.debugPrint( element , System.err );
		/* === [06/08/17 15:14 I.Noda] === */
		LogWriter.qwrite("DEBUG", element) ;
		/* >>> [06/08/17 15:14 I.Noda] >>> */

		Element	geometryElement;

		try
		{
			geometryElement = ElementUtil.getSingleChildElement
								  ( element );
		}
		catch( XMLParseErrorException  e )
		{
			throw new TypeException( e.getMessage() , e );
		}


		ElementName	geometryTypeName
					= new ElementName( geometryElement );

		if ( geometryElement.getNamespaceURI() == null
		  || ! geometryElement.getNamespaceURI().equals( URI.GML ) )
		{
			throw new TypeException( "unexpected geometry type \""
						 + geometryTypeName
						 + "\"" );
		}


		String	wktValue;
		String	srsName;
		try
		{
			DrmGeometry	geometry;

			geometry = GeometryFormatConverter
				   .parseFromGMLElement( geometryElement );

			wktValue = GeometryFormatConverter
				   .convertGeometryToWKTString( geometry ,
								true,
								true );

			srsName  = geometry.getSrsName();
		}
		catch( GeometryException  e )
		{
			throw new TypeException
				( "unexpected geometry type \""
				  + geometryTypeName + "\": "
				  + e.getMessage() );
		}


		String	rawXMLString;

		try
		{
			rawXMLString = XMLFormatConverter
					.toString( geometryElement , false );
		}
		catch( TransformerException  e )
		{
			throw new TypeException
				( "unexpected exception,"
				  + " Can't convert XML to string: "
				  + e.getMessage() , e );
		}


		//
		// 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 );

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

		columns.add( new TableColumn
			     ( defs.get(this.ColIndexGeoValue) , wktValue ) );
		
		columns.add( new TableColumn
			     ( defs.get(this.ColIndexSrsName) ,
			       srsName ) );

		columns.add( new TableColumn
			     ( defs.get(this.ColIndexGeoXml) ,
			       rawXMLString ) );

		return( new Pair<TypedInstance, Integer>
			( new TypedInstance( columns , this ) ,
			  element.getChildNodes().getLength() ) );
	}


	@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 + NUMBER_OF_COLUMNS - 1 >= columns.size() )
		{
		    throw new TypeException
			( "internal error in " + this.getClass().getName()
			  + ": unexpected instantiation" );
		}


		String stringValue = columns.get( index + ColIndexGeoXml )
								.getValue();

		if ( stringValue == null )
		{
		    // means occures == 0
		    return( index + NUMBER_OF_COLUMNS );
		}


		Element	geometryElement;
		try
		{
			geometryElement = XMLFormatConverter.stringToElement
					  ( stringValue, doc );
		}
		catch( TransformerException  e )
		{
			throw new TypeException
				( "unexpected exception,"
				  + " Can't convert string to XML: "
				  + e.getMessage() , e );
		}


		if ( ImplementationSwitches.instance()
		     .getEnableGeometryConversion() )
		{
			DrmGeometry	geometry;
	
			try
			{
				geometry = GeometryFormatConverter
					   .parseFromGMLElement
					    ( geometryElement );

				geometry = transformGeometry( geometry ,
							      trans );

				element.appendChild( GeometryFormatConverter
						     .convertToGMLElement
						      ( doc , geometry ) );
			}
			catch( GeometryException  e )
			{
				// check was done when inserting, will not
				// occur error except version mismatch
				throw new TypeException( "internal error: "
							 + e.getMessage() );
			}
		}
		else
		{
			element.appendChild( geometryElement );
		}

		return( index + NUMBER_OF_COLUMNS );
	}


	public	static	String	convertShortXPathToColumnName
						( String  shortXPathString )
	{
	    return( ColumnNameFactory.getColumnName(shortXPathString,
						    ColNameGeoValueSuffix)) ;
	}

	public	static	String	convertShortXPathToSrsColumnName
						( String  shortXPathString )
	{
	    return( ColumnNameFactory.getColumnName(shortXPathString,
						    ColNameGeoSrsNameSuffix)) ;
	}


	private static DrmGeometry transformGeometry
				    ( DrmGeometry  geometry ,
				      TransformationContext  trans )
					throws NoNeedToConvertTypeException
	{
	    LogWriter.qwrite( "DEBUG",
			      "target SRS = " ,
			      trans.getTargetCoordinateSystem().getSrsName() );

	    LogWriter.qwrite( "DEBUG",
			      "object SRS = " , geometry.getSrsName() );

	    switch( trans.getTransformationType() )
	    {
	    case Selective:
	    case Conv:
		CoordinateSystem target = trans.getTargetCoordinateSystem();
		String objCoord = geometry.getSrsName();

		LogWriter.qwrite( "DEBUG",
				  "target.getSrsName() = ",
				  target.getSrsName() );

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

		if ( ! EqualChecker.equals( target.getSrsName() , objCoord ) )
		{
			try
			{
				geometry = trans.transform( geometry );
			}
			catch( TransformationException  e )
			{
				// geometry has not changed
				LogWriter.qwrite( "DEBUG",
						  "transform failed:"
						  + e.getMessage() );
			}
		}
		break;

	    case NoConv:
		break;
	    }

	    return geometry;
	}
}
