package daruma.wfs.filter.predicates;

import daruma.wfs.filter.PredicateDescription;
import daruma.wfs.filter.PropertyPathConverter;

import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLParseErrorException;

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

import org.apache.xpath.XPathAPI;

import daruma.global_switch.ImplementationSwitches;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.types.GeometryPropertyTypeDefinition;

import daruma.geometry.DrmGeometry;
import daruma.geometry.GeometryException;
import daruma.geometry.GeometryFormatConverter;
import daruma.geometry.TransformationContext;

import daruma.xml.DeclaredName ;
import daruma.xml.Lexicon ;

import java.util.List;
import java.util.Map;
import java.util.HashMap;

import javax.xml.transform.TransformerException ;

import daruma.util.LogWriter;

import daruma.util.Pair ;

import daruma.global_switch.ImplementationSwitches;


public class GeometryPredicateDescription extends PredicateDescription
{
    private	String		sqlFunction;
    private	String		sqlExpression;

    public	String propertyXPath ;
    public      Element xpathContext ;
    public	DrmGeometry geometry ;

    private	static DeclaredName.Table<String> operatorTable ;

    static
    {
	operatorTable = new DeclaredName.Table<String>() ;

	switch( ImplementationSwitches.instance().getBackendDBType() )
	{
	case PostGIS:
	    operatorTable.put( Lexicon.MispFilterBBOX , "ST_Intersects" );
	    operatorTable.put( Lexicon.MispFilterBBox , "ST_Intersects" );
	    break;

	case MySQL:
	    operatorTable.put( Lexicon.MispFilterBBOX , "MBRIntersects" );
	    operatorTable.put( Lexicon.MispFilterBBox , "MBRIntersects" );
	    break;
	}

	operatorTable.put( Lexicon.MispFilterContains   , "CONTAINS" );
	operatorTable.put( Lexicon.MispFilterCrosses    , "CROSSES" );
	operatorTable.put( Lexicon.MispFilterDisjoint   , "DISJOINT" );
	operatorTable.put( Lexicon.MispFilterEquals     , "EQUALS" );
	operatorTable.put( Lexicon.MispFilterIntersects , "INTERSECTS" );
	operatorTable.put( Lexicon.MispFilterOverlaps   , "OVERLAPS" );
	operatorTable.put( Lexicon.MispFilterTouches    , "TOUCHES" );
	operatorTable.put( Lexicon.MispFilterWithin     , "WITHIN" );
    }

    public	static	boolean	isAcceptablePredicate( String  localName )
    {
	/* <<< [06/10/16 15:34 I.Noda] <<< */
	//return( GeometryPredicateDescription
	//	.operatorMap.containsKey( localName ) );
	/* === [06/10/16 15:34 I.Noda] === */
	return (GeometryPredicateDescription.operatorTable
		.getEntryByLocalName(localName) != null) ;
	/* >>> [06/10/16 15:34 I.Noda] >>> */
    }


    public GeometryPredicateDescription( Element  element ,
					 TypeName  typeName ,
					 TypeDefinition  type ,
					 TransformationContext  trans ,
					 StorageManager  storage )
	    throws XMLParseErrorException
    {
	super( storage , typeName , type , trans );

	/* === [06/10/16 15:34 I.Noda] === */
	DeclaredName.Table<String>.Entry
		entry = GeometryPredicateDescription
				.operatorTable.getEntry( element ) ;
	this.sqlFunction = entry.getValue() ;
	this.tagName = entry.getName() ;
	/* >>> [06/10/16 15:34 I.Noda] >>> */

	if ( this.sqlFunction == null )
	{
		throw new XMLParseErrorException
		    ( "cannot handle filter \""
		      + element.getLocalName() + "\"" );
	}

	this.sqlExpression = parseGeometryPropertyComparation( element );
    }

    public String getSQLExpression()
    {
	return( this.sqlExpression );
    }


    private String parseGeometryPropertyComparation( Element  comp )
						throws XMLParseErrorException
    {
	List<Element> childElements = ElementUtil.getChildElements( comp );

	if ( childElements.size() != 2 )
	{
		throw new XMLParseErrorException
		    ( "filter \"" + comp.getLocalName() + "\""
		      + " has invalid number of child elements,"
		      + " expected was 2" );
	}

	Element	propertyNameElement = childElements.get(0);
	Element	geometryElement     = childElements.get(1);

	// tag name check
	Lexicon.MispPropertyName
	    .matchesOrXmlException(propertyNameElement,
				   "in GeometryPredicates") ;
    
	String	shortXPath = PropertyPathConverter
			      .convertPropertyElementToShortXPath
			       ( propertyNameElement ,
				 this.getStorage() );

	this.propertyXPath = ElementUtil
			      .getChildNodesWholeText(propertyNameElement) ;
	this.xpathContext = propertyNameElement ;

	try
	{
		this.geometry = GeometryFormatConverter
				  .parseFromGMLElement( geometryElement );
	}
	catch( GeometryException  e )
	{
		throw new XMLParseErrorException
		    ( "geometry parse error" , e );
	}

	String wktString = GeometryFormatConverter
			   .convertGeometryToWKTString( this.geometry ,
							true,
							true );
	if ( wktString == null )
	{
	    wktString = "NULL";
	}

	final String baseSQL = sqlFunction + "("
			       + GeometryPropertyTypeDefinition
				 .convertShortXPathToColumnName( shortXPath )
			       + ",GeomFromText('" + wktString + "'))";

	String	querySQL = baseSQL;

	if ( ImplementationSwitches.instance().getEnableGeometryConversion() )
	{
	    TransformationContext trans = super.getTransformationContext();

	    switch( trans.getTransformationType() )
	    {
	    case Selective:
		if ( trans.getTargetCoordinateSystem() == null )
		{
		    throw new XMLParseErrorException
				( "srsName not specified" );
		}

		querySQL = "(" + baseSQL + ") AND "
			   + GeometryPropertyTypeDefinition
			     .convertShortXPathToSrsColumnName( shortXPath )
			   + " = '" + trans.getTargetCoordinateSystem()
							.getSrsName() + "'";

		LogWriter.qwrite( "DEBUG",
				  "Query = [", querySQL, "]" );
		break;

	    case Conv:
		// XXX
		break;

	    case NoConv:
		// no operation
		break;
	    }

	}

	return( querySQL );
    }

    //--------------------------------------------------
    /**
     * 詳細チェック
     * 地理検索などのチェックを精密に行う、2nd pass の処理
     */
    public boolean detailedCheck(Node feature) 
	throws XMLParseErrorException
    {
	try
	{
	    daruma.xml.util.XMLFormatConverter.print( feature , System.err );
	}
	catch(Exception e)
	{
	    e.printStackTrace();
	}

	boolean result = false ;

	try {
	    /* !!! [06/10/23 11:22 I.Noda] !!! 
	     * FIXME: XPath において、デフォルトの NameSpace を使って 
	     * property を指定した場合、うまくアクセスできない場合がある。
	     * 原因不明。
	     */

	    Element propNode = 
		(Element)XPathAPI.selectSingleNode(feature,
						   this.propertyXPath,
						   this.xpathContext) ;
	    Element geoNode = 
		ElementUtil.getSingleChildElement( propNode );

	    DrmGeometry drmGeo = 
		GeometryFormatConverter.parseFromGMLElement(geoNode) ;

	    if        (this.tagName == Lexicon.MispFilterBBOX
		    || this.tagName == Lexicon.MispFilterBBox) {
		result = drmGeo.intersects(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterContains) {
		result = drmGeo.contains(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterCrosses) {
		result = drmGeo.crosses(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterDisjoint) {
		result = drmGeo.disjoint(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterEquals) {
		result = drmGeo.equals(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterIntersects) {
		result = drmGeo.intersects(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterOverlaps) {
		result = drmGeo.overlaps(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterTouches) {
		result = drmGeo.touches(this.geometry) ; 
	    } else if (this.tagName == Lexicon.MispFilterWithin) {
		result = drmGeo.within(this.geometry) ; 
	    }
	}
	catch(TransformerException ex)
	{
	    throw new XMLParseErrorException("property " 
					     + "'" + this.propertyXPath + "'"
					     + " is not found in "
					     + feature,
					     ex) ;
	}
	catch(GeometryException ex)
	{
	    throw new XMLParseErrorException("property " 
					     + "'" + this.propertyXPath + "'"
					     + " is not found in "
					     + feature,
					     ex) ;
	}

	return result ;
    }
}
