package daruma.wfs.filter;

import daruma.xml.handler.XSAXDOMCreateHandler;
import daruma.xml.util.ElementUtil;
import daruma.xml.SAXExceptionObserver;
import daruma.storage_manager.type_definition.TypeDefinition;
import daruma.xml.UniversalName;
import daruma.xml.URI;
import daruma.xml.util.XMLParseErrorException;
import daruma.storage_manager.StorageManager;
import daruma.storage_manager.type_definition.TypeException;
import daruma.sql.SQLDataType;
import daruma.sql.TableColumnDefinition;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.w3c.dom.Element;
import java.io.OutputStream;
import java.util.List;

public class SortByHandler extends XSAXDOMCreateHandler
{
    private TypeDefinition specifiedElementType;
    private StorageManager storage;
    private SAXExceptionObserver errorReceiver;

    private String sqlExpression = null;

    public SortByHandler( OutputStream out,
			  XMLReader parser,
			  boolean isTopLevelHandler,
			  TypeDefinition specifiedElementType,
			  StorageManager storage,
			  SAXExceptionObserver errorReceiver )
    {
	super( out, parser, isTopLevelHandler );

	this.specifiedElementType = specifiedElementType;
	this.storage = storage;
	this.errorReceiver = errorReceiver;
    }

    public void xEndDocument() throws SAXException
    {
	Element sortByElement = super.getDocumentElement();

	if ( ! sortByElement.getNamespaceURI().equals( URI.MISP )
	  || ! sortByElement.getLocalName().equals( "SortBy" ) )
	{
	    this.throwError( "invalid " + sortByElement.getLocalName()
			     + " element." );
	}


	List<Element> sortProperties
		      = ElementUtil.getChildElements( sortByElement );

	if ( sortProperties.size() == 0 )
	{
	    this.throwError( "SortProperty element not found in "
			     + sortByElement.getLocalName() + " element." );
	}

	for( Element prop : sortProperties )
	{
	    if ( ! prop.getNamespaceURI().equals( URI.MISP )
	      || ! prop.getLocalName().equals( "SortProperty" ) )
	    {
		this.throwError( "unexpected element ["
				 + new UniversalName( prop ) + "], "
				 + "expected was SortProperty in namespace "
				 + URI.MISP + "." );
	    }


	    List<Element> propSortDescription
			  = ElementUtil.getChildElements( prop );

	    boolean isSortOrderAscending = true;

	    if ( propSortDescription.size() == 0 )
	    {
		this.throwError( "empty " + prop.getLocalName()
				 + " element." );
	    }
	    else if ( ! ( propSortDescription.size() == 1
			  || propSortDescription.size() == 2 ) )

	    {
		this.throwError( "too many elements in "
				 + prop.getLocalName() + " element." );
	    }


	    Element propertyNameElement = propSortDescription.get(0);

	    if ( ! propertyNameElement.getNamespaceURI().equals( URI.MISP )
	      || ! propertyNameElement.getLocalName()
		   .equals( "PropertyName" ) )
	    {
		this.throwError( "invalid child of "
				 + sortByElement.getLocalName() + " element,"
				 + " expected was PropertyName." );
	    }

	    if ( propSortDescription.size() >= 2 )
	    {
		Element sortOrderElement = propSortDescription.get(1);

		if ( ! sortOrderElement.getNamespaceURI().equals( URI.MISP )
		  || ! sortOrderElement.getLocalName().equals( "SortOrder" ) )
		{
		    this.throwError( "invalid child of "
				     + prop.getLocalName() + " element,"
				     + " expected was SortOrder." );
		}

		String sortOrderString = ElementUtil.getChildNodesWholeText
							( sortOrderElement );

		if ( sortOrderString.equals( "ASC" ) )
		{
		    isSortOrderAscending = true;
		}
		else if ( sortOrderString.equals( "DESC" ) )
		{
		    isSortOrderAscending = false;
		}
		else
		{
		    this.throwError( "invalid sort order ["
				     + sortOrderString + "], "
				     + "expected was \"ASC\" or \"DESC\"." );
		}
	    }


	    String propertyPath
		   = ElementUtil.getChildNodesWholeText( propertyNameElement );

	    String sqlColumnName = null;

	    try
	    {
		sqlColumnName = PropertyPathConverter
				.convertPropertyPathToShortXPath
				 ( propertyPath,
				   propertyNameElement,
				   this.storage );

		if ( ! this.isSortableColumn( sqlColumnName ) )
		{
		    this.throwError( "Can't sort by property ["
				     + propertyPath + "]." );
		}
	    }
	    catch( XMLParseErrorException e )
	    {
		this.throwError( e );
	    }


	    if ( this.sqlExpression == null )
	    {
		this.sqlExpression = "ORDER BY ";
	    }
	    else
	    {
		this.sqlExpression += ",";
	    }

	    this.sqlExpression += sqlColumnName + " "
				  + (isSortOrderAscending? "ASC" : "DESC");
	}
    }


    private boolean isSortableColumn( String columnName )
						throws XMLParseErrorException
    {
	SQLDataType sqlType = this.specifiedElementType.getSingleSQLDataType();

	if ( sqlType == null )
	{
	    List<TableColumnDefinition>	columns;

	    try
	    {
		columns = this.specifiedElementType.getCompositeSQLDataType
			       ( this.storage, null, null );
	    }
	    catch( TypeException e )
	    {
		throw new XMLParseErrorException
			  ( "type definition error", e );
	    }

	    for ( TableColumnDefinition c : columns )
	    {
		if ( c.getColumnName().equals( columnName ) )
		{
		    sqlType = c.getSQLDataType();
		    break;
		}
	    }
	}

	if ( sqlType == null )
	{
	    return false;
	}

	// in this implementation, not reached because sqlType is null.
	return ! sqlType.getSQLBaseDataTypeString().equals( "geometry" );
    }


    // XXX: should return structure of sorting information
    public String getSQLExpression()
    {
	return this.sqlExpression;
    }


    private void throwError( String errorMessage ) throws SAXException
    {
	this.throwError( new SAXParseException( errorMessage,
						super.getLocator() ) );
    }

    private void throwError( Exception e ) throws SAXException
    {
	this.throwError( new SAXParseException( e.getMessage(),
						super.getLocator(),
						e ) );
    }

    private void throwError( SAXParseException e ) throws SAXException
    {
	this.errorReceiver.notifyError( e );

	throw e;
    }
}
