package daruma.wfs;

import daruma.xml.UniversalName;
import daruma.xml.handler.XSAXDOMCreateHandler;
import daruma.xml.util.ErrorDocumentBuilder;
import daruma.xml.util.FatalErrorDocument;
import daruma.xml.util.XMLFormatConverter;
import daruma.xml.util.ElementUtil;
import daruma.xml.util.XMLParseErrorException;
import daruma.storage_manager.type_definition.ElementName;
import daruma.xml.URI;
import daruma.xml.DeclaredName;
import daruma.xml.NameSpace;
import daruma.util.Pair;
import daruma.util.EqualChecker;

import daruma.storage_manager.StorageManager;
import daruma.storage_manager.StorageException;
import daruma.util.LogWriter;

import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.transform.TransformerException;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Element;
import org.w3c.dom.Document;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.List;

import daruma.auth.PermissionManager;
import daruma.auth.Account;
import daruma.auth.AuthenticationProtocol;
import daruma.auth.PermissionType;
import daruma.auth.PermissionDirective;


public class ManageAuthHandler extends XSAXDOMCreateHandler
{
    private StorageManager storage;
    private PermissionManager permissionManager;

    public ManageAuthHandler( OutputStream out,
			      XMLReader parser,
			      boolean isTopLevelHandler,
			      StorageManager storage,
			      PermissionManager permissionManager )
    {
	super( out, parser, isTopLevelHandler );

	this.storage = storage;
	this.permissionManager = permissionManager;
    }

    public void xEndDocument() throws SAXException
    {
	// List<Pair<Account,AuthenticationProtocol>> accountList;

	//
	// check directive
	//
	Element directiveElement = null;
	try
	{
	    directiveElement = ElementUtil.getSingleChildElement( super.getDocumentElement() );
	}
	catch( XMLParseErrorException e )
	{
	    this.throwError( new SAXParseException( e.getMessage(), super.getLocator() ) );
	}


	//
	// check namespace of directive
	//
	if ( ! EqualChecker.equals( directiveElement.getNamespaceURI(), URI.MISP ) )
	{
	    this.throwError( new SAXParseException
			     ( "invalid namespace of directive [" + new UniversalName( directiveElement ) + "]," + " expected was " + URI.MISP + ".",
			       super.getLocator() ) );
	}


	//
	// switch by directive
	//
	if ( directiveElement.getLocalName().equals( "CreateAccount" ) )
	{
	    this.handleCreateAccount( directiveElement );
	}
	else if ( directiveElement.getLocalName().equals( "AddPermissionRule" ) )
	{
	    this.handleAddPermissionRule( directiveElement );
	}
	else
	{
	    this.throwError( new SAXParseException
			     ( "Unknown directive [" + new UniversalName( directiveElement ) + "]," + " expected was {" + URI.MISP + "}CreateAccount or AddPermissionRule.",
			       super.getLocator() ) );
	}
    }

    private void handleCreateAccount( Element directiveElement ) throws SAXException
    {
	List<Element> accounts = ElementUtil.getChildElements( directiveElement );
	
	for ( Element accountElement : accounts )
	{
	    try
	    {
		//
		// <Account>
		//
		if ( ! EqualChecker.equals( new UniversalName( accountElement ), new UniversalName( URI.MISP, "Account" ) ) )
		{
		    this.throwError( new SAXParseException
				     ( "Unexpected tag [" + new UniversalName( accountElement ) + "]," + " expected was {" + URI.MISP + "}Account.",
				       super.getLocator() ) );
		}

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

		if ( childElements.size() != 2 )
		{
		    this.throwError( new SAXParseException
				     ( "Invalid number of child elements in [" + new UniversalName( accountElement ) + "]," + " should have 2 child elements.",
				       super.getLocator() ) );
		}

		//
		// <AccountName>
		//
		Element accountNameElement = childElements.get(0);
		String accountName = this.getAccountName( accountNameElement );
		LogWriter.qwrite( "DEBUG", "accountName = [", accountName, "]" );


		//
		// <AuthenticationProtocol>
		//
		Element authentificationProtocolElement = childElements.get(1);
		if ( ! EqualChecker.equals( new UniversalName( authentificationProtocolElement ), new UniversalName( URI.MISP, "AuthentificationProtocol" ) ) )
		{
		    this.throwError( new SAXParseException
				     ( "Invalid child element [" + new UniversalName( authentificationProtocolElement ) + "]," + " expected was {" + URI.MISP + "}AuthentificationProtocol.",
				       super.getLocator() ) );
		}

		Element concreteAuthentificationProtocol = ElementUtil.getSingleChildElement( authentificationProtocolElement );

		//
		// <PlainPasswordProtocol>
		//
		if ( new UniversalName( concreteAuthentificationProtocol ).equals( new UniversalName( URI.MISP, "PlainPasswordProtocol" ) ) )
		{
		    Element plainPasswordElement = ElementUtil.getSingleChildElement( concreteAuthentificationProtocol );

		    if ( ElementUtil.getChildElements( plainPasswordElement ).size() != 0 )
		    {
			this.throwError( new SAXParseException
					 ( "Invalid plain password element [" + new UniversalName( plainPasswordElement ) + "]," + " should not contain any tags.",
					   super.getLocator() ) );
		    }

		    String plainPassword = ElementUtil.getStringOnlyChildNodesWholeText( plainPasswordElement );
		    LogWriter.qwrite( "DEBUG", "plain password = [", plainPassword, "]" );
		}
		else
		{
		    this.throwError( new SAXParseException
				     ( "No such authentification protocol [" + new UniversalName( concreteAuthentificationProtocol ) + "]",
				       super.getLocator() ) );
		}
	    }
	    catch( XMLParseErrorException e )
	    {
		this.throwError( new SAXParseException( e.getMessage(), super.getLocator(), e ) );
	    }
	}

	this.printOutput();
    }


    private void handleAddPermissionRule( Element directiveElement ) throws SAXException
    {
	final List<Element> permissionRules = ElementUtil.getChildElements( directiveElement );

	for ( Element rule : permissionRules )
	{
	    try
	    {
		PermissionType allowDeny = PermissionType.Deny;

		//
		// <Allow>
		//
		if ( EqualChecker.equals( new UniversalName( rule ), new UniversalName( URI.MISP, "Allow" ) ) )
		{
		    allowDeny = PermissionType.Allow;
		}
		else if ( EqualChecker.equals( new UniversalName( rule ), new UniversalName( URI.MISP, "Deny" ) ) )
		{
		    allowDeny = PermissionType.Deny;
		}
		else
		{
		    this.throwError( new SAXParseException
				     ( "Unexpected tag [" + new UniversalName( rule ) + "]," + " expected was {" + URI.MISP + "}Allow or Deny.",
				       super.getLocator() ) );
		}

		final List<Element> ruleChildElements = ElementUtil.getChildElements( rule );

		if ( ruleChildElements.size() != 2 )
		{
		    this.throwError( new SAXParseException
				     ( "Invalid number of child elements in [" + new UniversalName( rule ) + "]," + " should have 2 child elements.",
				       super.getLocator() ) );
		}

		//
		// <AccountName>
		//
		final Element accountNameElement = ruleChildElements.get(0);
		final String accountName = this.getAccountName( accountNameElement );

		//
		// <FeatureType>
		//
		final Element featureTypeElement = ruleChildElements.get(1);
		final UniversalName featureName = this.getFeatureName( featureTypeElement );

		LogWriter.qwrite( "DEBUG", "accountName = [", accountName, "]" );
		LogWriter.qwrite( "DEBUG", "feature = [" + featureName + "]" );
		LogWriter.qwrite( "DEBUG", "permissionManager before:" );
		this.permissionManager.debugPrint();

		PermissionDirective directive = new PermissionDirective( allowDeny, new Account( accountName ), featureName );

		this.storage.addPermissionRule( directive );
		this.permissionManager.addPermissionRule( directive );

		LogWriter.qwrite( "DEBUG", "permissionManager after:" );
		this.permissionManager.debugPrint();
	    }
	    catch( StorageException e )
	    {
		this.throwError( new SAXParseException( e.getMessage(), super.getLocator(), e ) );
	    }
	    catch( XMLParseErrorException e )
	    {
		this.throwError( new SAXParseException( e.getMessage(), super.getLocator(), e ) );
	    }
	}

	this.printOutput();
    }


    private String getAccountName( Element accountNameElement ) throws SAXException, XMLParseErrorException
    {
	//
	// <AccountName>
	//
	if ( ! new UniversalName( accountNameElement ).equals( new UniversalName( URI.MISP, "AccountName" ) ) )
	{
	    this.throwError( new SAXParseException
			     ( "Invalid child element [" + new UniversalName( accountNameElement ) + "]," + " expected was {" + URI.MISP + "}AccountName.",
			       super.getLocator() ) );
	}

	return ElementUtil.getStringOnlyChildNodesWholeText( accountNameElement );
    }

    private UniversalName getFeatureName( Element featureTypeElement ) throws SAXException
    {
	//
	// <FeatureType>
	//
	if ( ! new UniversalName( featureTypeElement ).equals( new UniversalName( URI.MISP, "FeatureType" ) ) )
	{
	    this.throwError( new SAXParseException
			     ( "Invalid child element [" + new UniversalName( featureTypeElement ) + "]," + " expected was {" + URI.MISP + "}FeatureType.",
			       super.getLocator() ) );
	}

	List<Element> featureTypeChildElements = ElementUtil.getChildElements( featureTypeElement );

	if ( featureTypeChildElements.size() != 2 )
	{
	    this.throwError( new SAXParseException
			     ( "Invalid number of child elements in [" + new UniversalName( featureTypeElement ) + "]," + " should have 2 child elements.",
			       super.getLocator() ) );
	}

	//
	// check <Namespace>, <Name>
	//
	final Element featureNamespaceElement = featureTypeChildElements.get(0);
	final Element featureNameElement = featureTypeChildElements.get(1);
	if ( ! new UniversalName( featureNamespaceElement ).equals( new UniversalName( URI.MISP, "Namespace" ) ) )
	{
	    this.throwError( new SAXParseException
			     ( "Invalid child element [" + new UniversalName( featureNamespaceElement ) + "]," + " expected was {" + URI.MISP + "}FeatureNamespace.",
			       super.getLocator() ) );
	}

	if ( ! new UniversalName( featureNameElement ).equals( new UniversalName( URI.MISP, "Name" ) ) )
	{
	    this.throwError( new SAXParseException
			     ( "Invalid child element [" + new UniversalName( featureNameElement ) + "]," + " expected was {" + URI.MISP + "}FeatureName.",
			       super.getLocator() ) );
	}

	final String featureNamespace = ElementUtil.getChildNodesWholeText( featureNamespaceElement );
	final String featureName = ElementUtil.getChildNodesWholeText( featureNameElement );

	return new UniversalName( featureNamespace, featureName );
    }


    private void printOutput() throws SAXException
    {
	//
	// set status
	//
	String	status;

	/*
	if ( this.errorOccured )
	{
	    status = "FAILURE";
	}
	else
	{
	    status = "SUCCESS";
	}
	*/
	status = "SUCCESS";


	//
	// prepare output document object
	//
	PrintWriter out = super.getPrintWriter();

	ResponseInfo responseInfo = super.getResponse();
	Document doc = responseInfo.document;

	Element topLevelElement = ElementUtil.genElementSimple( new DeclaredName( "ManageAuthResponse", NameSpace.MISP ),
								null, doc, responseInfo.leaf, true );

	responseInfo.setResponseToLeaf( topLevelElement );

	super.getResponse().outputHeaderPart( out );

	Element statusElement = doc.createElementNS( URI.MISP, "misp:Status" );
	topLevelElement.appendChild( statusElement );
	statusElement.appendChild( doc.createTextNode( status ) );

	/*
	for ( String s : this.errorMessages )
	{
	    Element errorElement = doc.createElementNS( URI.MISP, "misp:Error" );
	    topLevelElement.appendChild( errorElement );

	    Element messageElement = doc.createElementNS( URI.MISP, "misp:Message" );
	    errorElement.appendChild( messageElement );
	    messageElement.appendChild( doc.createTextNode( s ) );
	}
	*/

	super.getResponse().outputHeaderPart(out);

	out.flush();
	try
	{
	    this.getOutputStream().flush();
	}
	catch( IOException e )
	{
	    throw new SAXException( e );
	}
    }


    protected void throwError( SAXParseException e ) throws SAXException
    {
	this.printError( e );
	throw e;
    }

    protected void printError( SAXParseException e ) throws SAXException
    {
	Document doc;

	try
	{
	    XMLFormatConverter.print( new SOAPFaultDocumentBuilder( e ).newDocument(),
				      this.getOutputStream() );
	}
	catch( ParserConfigurationException pe )
	{
	    throw new SAXException( pe );
	}
	catch( TransformerException te )
	{
	    throw new SAXException( te );
	}
    }
}
