package gov.grants.apply.support.bizobj;

import org.apache.log4j.Logger;

import com.ximpleware.VTDNav;

import gov.grants.apply.support.vo.AttachmentDetails;
import gov.grants.commons.util.XMLHashUtil;

public class SubmissionXml extends VtdXml {
	
	private static final String CLASSNAME = SubmissionXml.class.getName();
	private static final Logger log = Logger.getLogger( CLASSNAME );
	
	/* Constructor(s) */
	
	public SubmissionXml( byte[] submissionXmlBytes ) throws Exception {
		try {
			
			log.info( "initialize submission xml bytes" );
			super.init( submissionXmlBytes );
    		
    		setMultiProjectFlag();
    		log.debug( "isMultiProject: " + this.isMultiProject );
    		
    		initAttachmentDetailsMap();
    		
		} catch ( Exception e ) {
			log.error( "Exception parsing xml bytes: " + e.getMessage() );
			throw new Exception( e.getMessage() );
		}// try-catch
		
	}
	
	
	public SubmissionXml( VTDNav vNav ) throws Exception {
		try {
			if ( vNav == null ) {
				throw new Exception( "VTDNav is null" );
			}// if
			
    		this.vn = vNav;
    		setMultiProjectFlag();
    		log.debug( "isMultiProject: " + this.isMultiProject );
    		
    		initAttachmentDetailsMap();
    		
		} catch ( Exception e ) {
			log.error( "Exception parsing xml bytes: " + e.getMessage() );
			throw new Exception( e.getMessage() );
		}// try-catch
		
	}
	
	
	/* Public Method(s) */
	
	public VTDNav getVTDNav() { return this.vn; }
	
	
	public boolean isMultiProject() { return this.isMultiProject; }
	
	/**
	 * Extracts the header XML element.
	 * <br><br>
	 * Single Project submission XML: GrantSubmissionHeader element
	 * <br><br>
	 * Multi-Project submission XML: ApplicationHeader element
	 * 
	 * @return
	 * @throws Exception
	 */
	public String getHeaderXml( boolean removeWhitespaceBetweenTags ) throws Exception {
		String xml = null;
		try {
			resetXmlNav();// make sure pointer is at root element
			
			if ( isMultiProject ) {
				vn.toElement( VTDNav.FIRST_CHILD );// ApplicationHeader
				log.debug( vn.toNormalizedString2( vn.getCurrentIndex() ) + " @ index: " + vn.getCurrentIndex()  );
				
			} else {
				vn.toElement( VTDNav.FIRST_CHILD );// GrantSubmissionHeader | Header_2_0
				log.debug( vn.toNormalizedString2( vn.getCurrentIndex() ) + " @ index: " + vn.getCurrentIndex()  );
				
			}// if-else
			
			xml = getRawXmlStr();
			
			if ( removeWhitespaceBetweenTags ) {
				
				String regex = ">[\\s]+<";
				xml = xml.replaceAll( regex, "><" );
				
			}// if
			
			
		} catch ( Exception e ) {
			String s = "Exception caught: " + e.getMessage();
			log.error( s );
			throw new Exception( s );
		}// try-catch
		
		return xml;
		
	}// getHeaderXml
	
	
	
	
	public String createSubmissionXmlHashValue() throws Exception {
		try {
			String elementNsUri = null;
			String elementLocalPart = null;
			
			if ( isMultiProject ) {
				elementNsUri = META_MULTI_GRANT_APPLICATION_NS_URI;
				elementLocalPart = APPLICATION_PACKAGE_ELEMENT_LOCAL_PART; // ApplicationPackage
			} else {
				elementNsUri = META_GRANT_APPLICATION_NS_URI;
				elementLocalPart = FORMS_ELEMENT_LOCAL_PART; // Forms
			}// if-else
			
			String calculatedHash = XMLHashUtil.createXmlHash( getXmlBytes(), elementNsUri, elementLocalPart );
			
			log.debug( "returning calculated hash value: " + calculatedHash );
			
			return calculatedHash;
			
    	} catch ( Exception e ) {
    		String s = "Exception caught creating submission xml hash: " + e.getMessage();
    		log.error( s );
    		throw new Exception( s );
    	}// try-catch
		
	}// createSubmissionXmlHashValue
	
	public String createAttHashValue( String attFilePath ) 
	throws Exception {
		return super.createAttHashValue( attFilePath );
	}// createAttHashValue
	
	
	
	public String getSubmittedXmlHashValue() throws Exception {
		log.debug( "get submitted xml hash value" );
		String hash = null;
		try {
			resetXmlNav();
			
			if ( isMultiProject ) {
				// un-comment out for multi-project project
				vn.toElement( VTDNav.FIRST_CHILD );// ApplicationHeader
			}// if
			
			vn.toElement( VTDNav.FIRST_CHILD );// GrantSubmissionHeader
			
			if ( vn.toElementNS( VTDNav.FIRST_CHILD, CommonConstants.GLOBAL_NS_URI, CommonConstants.HASH_VALUE_ELEMENT_LOCAL_PART ) ) {
				log.debug( vn.toNormalizedString2( vn.getCurrentIndex() ) + " @ index: " + vn.getCurrentIndex()  );
				if ( vn.getText() > -1 ) {
					hash = vn.toNormalizedString2( vn.getText() );
				} else { 
					throw new Exception( "GrantSubmissionHeader HashValue element is blank." );
				}// if-else
				
				log.debug( "GrantSubmissionHeader hash: " + hash );
				
			} else {
				throw new Exception( "GrantSubmissionHeader HashValue element is missing." );
			}// try-catch
			
		} catch ( Exception e ) {
			String s = "Exception caught retrieving submission xml hash value: " + e.getMessage();
			log.error( s );
			throw new Exception( s );
		}// try-catch
		
		log.debug( "submitted hash value: " + hash );
		return hash;
		
	}// getSubmittedXmlHashValue
	
	/**
	 * Finds the <b>att:FileLocation</b> element by <code>cid</code> in the submission XML and returns and <code>Attachment</code> object.
	 * <br><br>
	 * Returns <code>null</code> if <code>cid</code> cannot be found.
	 * 
	 * @param cid
	 * @return
	 * @throws Exception
	 */
	public AttachmentDetails getAttachmentDetailsByCID( String cid ) throws Exception {
		
		if ( !hasAttachments() ) { 
			log.info( "no attachments" );
			return null; 
		}// if
		
		log.debug( "get attachment details for cid: " + cid );
		log.debug( "attachmentDetailsMap cid(s): " + attachmentDetailsMap.keySet() );
		return this.attachmentDetailsMap.get( cid );
		
	}// getAttInfoByCid
	
	
	
	/**
	 * Finds the <b>att:FileLocation</b> element by <code>cid</code> in the submission XML and returns the index.
	 * <br><br>
	 * Returns <code>-1</code> if <code>cid</code> cannot be found or the XML does not have attachments.
	 * 
	 * @param cid
	 * @return
	 * @throws Exception
	 */
	public int getFileLocationElementIndexByCid( String cid ) throws Exception {
		log.debug( "get FileLocation element index for cid: " + cid );
		int i = -1;
		
		if ( !this.attachmentDetailsMap.containsKey( cid ) ) { 
			log.info( "no attachment with cid: " + cid );
			return i; 
		}
		
		try {
			resetXmlNav();
			ap.bind( vn );
			
			String xpath = "//" + AttachmentDetails.FILE_LOCATION_ELEMENT + "[@att:href]";
			ap.selectXPath( xpath );
			log.debug( "evaluate xpath: " + xpath );
			log.debug( "evaluate xpath expr: " + ap.getExprString() );
			String hrefAttrValue = null;
			while ( ( i = ap.evalXPath() ) != -1 ) {
				log.debug( i + " ==> " + vn.toRawString( i ) );
				hrefAttrValue = vn.toRawString( vn.getAttrValNS( AttachmentDetails.ATTACHMENT_NS_URI, AttachmentDetails.CID_ATTRIBUTE_LOCAL_PART ) );
				log.debug( "href attribute value: " + hrefAttrValue );
				log.debug( "CID: " + cid );
				if ( cid.equals( hrefAttrValue ) ) {
					log.debug( "found CID match @ index: " + i );
					return i;
				}// if
				
			}// while
			
		} catch ( Exception e ) {
			String s = "Exception creating AttachmentDetails map: " + e.getMessage();
			log.error( s );
			throw new Exception( s );
		}// try-catch
		
		log.debug( "could not find cid: " + i );
		return i;
		
	}// getFileLocationElementIndexByCid
	
	
	/**
	 * Convenience method. Checks if attachment count is > 0.
	 * @return
	 * @throws Exception
	 */
	public boolean hasAttachments() throws Exception {
		return getAttachmentCount() > 0;
	}// hasAttachments
	
	
	
	/**
	 * Counts all of the <b>att:FileName</b> elements in the submission XML
	 * 
	 * @return
	 * @throws Exception
	 */
	public int getAttachmentCount() throws Exception {
		
		try {
			resetXmlNav();
			ap.bind( vn );
			
			// find all attachment FileLocation elements
			String xpath = "//" + AttachmentDetails.FILE_LOCATION_ELEMENT;
			ap.selectXPath( xpath );
			int count = 0;
			while ( ( ap.evalXPath() ) > -1 ) {
				count++;
			}// while
			
			log.debug( "returning attachment count: " + count );
			return count;
			
		} catch ( Exception e ) {
			String s = "Exception getting attachment count: " + e.getMessage();
			log.error( s );
			throw new Exception( s );
		}// try-catch
		
	}// getAttachmentCount
	
	/**
	 * NOTE: This method resets the vNav pointer to root. 
	 * Be aware when using this during XML navigation.
	 * <br><br>
	 * Sets the <code>isMultiProject</code> boolean flag
	 * 
	 * @throws Exception
	 */
	private void setMultiProjectFlag() throws Exception {
		try {
			resetXmlNav();
			log.debug( "root element: " + vn.toNormalizedString2( vn.getCurrentIndex() ) );
			
			if ( !vn.endsWith( vn.getCurrentIndex(), GRANT_APPLICATION_ELEMENT_LOCAL_PART ) ) {
				this.isMultiProject = true;
			} // if
			
		} catch ( Exception e ) {
			String s = "Exception caught checking if is a multi-project XML: " + e.getMessage();
			log.error( s );
			throw new Exception( s );
		}// try-catch
		
	}// setMultiProjectFlag
	
}

