package daruma.wfs.filter.predicates;

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

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

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

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;
import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.storage_manager.type_definition.TypeName;
import daruma.storage_manager.DBMSStorageManager;

import daruma.geometry.TransformationContext;

import java.util.List;


public class PropertyIsMaxMinPredicateDescription extends PredicateDescription
{
    private static final String PROPERTY_IS_MAX = "PropertyIsMax";
    private static final String PROPERTY_IS_MIN = "PropertyIsMin";
    private static final String PROPERTY_NAME   = "PropertyName";
    private static final String UNIQUE_GROUP_BY = "UniqueGroupBy";

    public static boolean isAcceptablePredicate( String localName )
    {
	return (   localName.equals( PropertyIsMaxMinPredicateDescription
				     .PROPERTY_IS_MAX )
		|| localName.equals( PropertyIsMaxMinPredicateDescription
				     .PROPERTY_IS_MIN ) );
    }

    private String sqlExpression;

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

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

	final List<Element> childElements
	    = ElementUtil.getChildElements( element );

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

	final Element propertyNameElement  = childElements.get(0);
	Element uniqueGroupByElement = null;
	boolean haveUniqueGroupByElement = false;

	if (    ! propertyNameElement.getLocalName()
		  .equals( PropertyIsMaxMinPredicateDescription.PROPERTY_NAME )
	     || ! propertyNameElement.getNamespaceURI()
		  .equals( URI.MISP ) )
	{
	    throw new XMLParseErrorException
		      ( "unexpected \""
			+ propertyNameElement.getLocalName() + "\""
			+ " in namespace "
			+ propertyNameElement.getNamespaceURI() + ","
			+ " expected was \""
			+ PropertyIsMaxMinPredicateDescription.PROPERTY_NAME
			+ "\" in namespace " + URI.MISP );
	}

	if ( childElements.size() == 2 )
	{
	    haveUniqueGroupByElement = true;

	    uniqueGroupByElement = childElements.get(1);

	    if (    ! uniqueGroupByElement.getLocalName()
		      .equals( PropertyIsMaxMinPredicateDescription
			       .UNIQUE_GROUP_BY )
		 || ! propertyNameElement.getNamespaceURI()
		      .equals( URI.MISP ) )
	    {
		throw new XMLParseErrorException
		      ( "unexpected \""
			+ uniqueGroupByElement.getLocalName() + "\""
			+ " in namespace "
			+ uniqueGroupByElement.getNamespaceURI() + ","
			+ " expected was \""
			+ PropertyIsMaxMinPredicateDescription.UNIQUE_GROUP_BY
			+ "\" in namespace " + URI.MISP );
	    }
	}


	//
	// get propertyXPath
	//
	final String propertyXPath = PropertyPathConverter
				     .convertPropertyElementToShortXPath
				      ( propertyNameElement,
					super.getStorage() );

	//
	// set min or max function
	//
	String func;
	if ( element.getLocalName()
	     .equals( PropertyIsMaxMinPredicateDescription.PROPERTY_IS_MAX ) )
	{
	    func = "MAX";
	}
	else
	{
	    func = "MIN";
	}


	//
	// get feature table name
	//
	String featureTableName;
	try
	{
	    featureTableName = storage.getUniversalNameID( typeName );
	}
	catch( StorageException e )
	{
	    throw new XMLParseErrorException( e );
	}


	if ( haveUniqueGroupByElement )
	{
	    List<Element> uniqChildElements
			  = ElementUtil
			    .getChildElements( uniqueGroupByElement );

	    if ( uniqChildElements.size() != 1 )
	    {
		throw new XMLParseErrorException
			  ( "element \"" + uniqueGroupByElement.getLocalName()
			    + "\""
			    + " has invalid number of child elements,"
			    + " expected was 1" );
	    }


	    final Element idPropertyNameElement = uniqChildElements.get(0);

	    if (    ! idPropertyNameElement.getLocalName()
		      .equals( PropertyIsMaxMinPredicateDescription
			       .PROPERTY_NAME )
		 || ! idPropertyNameElement.getNamespaceURI()
		      .equals( URI.MISP ) )
	    {
		throw new XMLParseErrorException
		      ( "unexpected \""
			+ idPropertyNameElement.getLocalName() + "\""
			+ " in namespace "
			+ idPropertyNameElement.getNamespaceURI() + ","
			+ " expected was \""
			+ PropertyIsMaxMinPredicateDescription.PROPERTY_NAME
			+ "\" in namespace " + URI.MISP );
	    }

	    final String idPropertyXPath = PropertyPathConverter
					   .convertPropertyElementToShortXPath
					    ( idPropertyNameElement,
					      super.getStorage() );

	    final String defaultSetName = DBMSStorageManager.DEFAULT_SET_NAME;
	    final String tmpSetName = defaultSetName + "_";

	    this.sqlExpression
		 = propertyXPath + " = "
		   + "(SELECT " + func
		   +  "(" + tmpSetName + "." + propertyXPath + ") FROM "
		   + featureTableName + " " + tmpSetName + " WHERE "
		   + tmpSetName + "." + idPropertyXPath + " = "
		   + defaultSetName + "." + idPropertyXPath + ")";
	}
	else
	{
	    this.sqlExpression
		 = propertyXPath + " = "
		   + "(SELECT " + func
		   +  "(" + propertyXPath + ") FROM " + featureTableName + ")";
	}
    }

    @Override
    public boolean detailedCheck( Node feature ) throws XMLParseErrorException
    {
	return true;
    }
}
