package edu.vt.marian.Document;

import java.io.*;
import java.net.*;
import java.util.*;

import edu.vt.marian.common.*;
import edu.vt.marian.Document.SOIFException;

import gnu.regexp.*;


/**
 * A set of data encapsulated in a SOIF
 */
public class RFC1807Document implements Document
{

    /** this string contains all the information of this document
		the format of the string is in SGML
	*/
	private String rfc1807String = null;
	
	
    /**
     * The URL in the SOIF
     */
	private URL URL;

    /**
     * Property list for the SOIF
     */
	private Hashtable attributeValues;

    /**
     * Allows us to pull things out of the property list in the order they
     * were inserted.
     */
	private Vector keyList;

    /**
     * Common delimitter that separates SOIF attributes and values
     */
	static final String soifDelimitter = ":: ";

    
	
	Debug debug;
	
    /**
        Build a new SOIF object
    */
	
	
	
	public RFC1807Document(Debug debug) {
		this.debug = debug;
		attributeValues = new Hashtable();
		keyList = new Vector();
	}

  
  /**
   * Build a new SOIF object from an input stream
   * @param soifInput input stream from which the SOIF should be built.
   */
	public RFC1807Document(String rfc1807Input, Debug debug) throws SOIFException {
		this(debug);

		rfc1807String = rfc1807Input;
		//*** ParseRFC1807Buffer(rfc1807String);
	}

  /**
   * Return a string array of the attribute names in this SOIF
   * @return An array of String that are the attribute (property)
   * names for the SOIF instance
   */
	public String[] GetAttributeNames() {
		Enumeration elements = attributeValues.keys();
		String attributeNames[] = new String[attributeValues.size()];
		int i = 0;

		while (elements.hasMoreElements()) {
			attributeNames[i++] = (String) elements.nextElement();
		}
		return(attributeNames);
	}

  /**
   * Return a count of the number of attributes in this SOIF
   */
	public int GetNumAttributes() {
		return(attributeValues.size());
	}

  /**
   * Return the value of a specified attribute in this SOIF
   * @param attribute name of the attribute to get the value of.
   */
	public String GetAttributeValue(String attribute) {
		if (!attributeValues.containsKey(attribute)) {
			return null;
		}
		return((String) attributeValues.get(attribute));
	}

  /**
   * Set an attribute/value pair for this SOIF
   * @param attribute name of the attribute to set.
   * @param value value to set the attribute to.
   */
	public void SetAttributeValue(String attribute, String value) {
		attributeValues.put(attribute, value);
		keyList.addElement(attribute);

	}

  /**
   * Return a StringBuffer which is a marshalled version of this SOIF
   * @param close A boolean value determining whether the soif should be closed
   * (I.E. whether the closing brace should be appended to the soif).
   */
	public StringBuffer SOIFToBuffer(boolean close) {
		StringBuffer output = new StringBuffer();
		Enumeration keys = attributeValues.keys();
		String attribute, value;

		//*** output.append("@" + GetTemplateType() + "{");
		for (int i = 0; i < keyList.size(); i++) {
   			value = (String) attributeValues.get(keyList.elementAt(i));
            output.append(UnparseSOIFElement((String) keyList.elementAt(i), value));
		}
        if (close) {
    		output.append(SOIFClose());
    	}

		return(output);
	}

    /**
     * Turn a attribute/value pair into a strintg.
     * @param attribute The attribute
     * @param value The attribute's value.
     */
    public static String UnparseSOIFElement(String attribute, String value) {
        StringBuffer output = new StringBuffer();

        output.append(attribute);
        output.append("{" + String.valueOf(value.length()) + "}");
		output.append(soifDelimitter + value);
		output.append("\n");

		return output.toString();
	}

	/**
	 * Return the SOIF close symbol (the brace)
	 *
	 */
	public String SOIFClose() {
	    return("}");
	}

  /**
   * Parse a marshalled SOIF object extracting all its attribute/value
   * pairs
   * @param soifString the string to parse.
   */
	public void ParseSOIFBuffer(String soifString) throws SOIFException {
		String soifBody, couldBeURL;
		RE reg;
		REMatch result;
		int pos;

		soifString.trim();

        // separate the template type and the body (inside {})
		if (!soifString.startsWith("@")) {
			throw new SOIFException("Illegal SOIF file format");
		}
        if ((pos = soifString.indexOf("{")) < 0) {
			throw new SOIFException("Illegal SOIF file format");
		}
		//*** templateType = soifString.substring(1, pos).trim();
		soifBody = soifString.substring(pos + 1).trim();
		if (!soifBody.endsWith("}")) {
			throw new SOIFException("Illegal SOIF file format");
		}
        soifBody = soifBody.substring(0, soifBody.length() - 1);

        // first token in the body may be a url
        StringTokenizer st = new StringTokenizer(soifBody);
        try {
            couldBeURL = st.nextToken();
            URL = new URL(couldBeURL);
			soifBody = soifBody.substring(couldBeURL.length()).trim();
		}
		catch (MalformedURLException e) {
			// we don't care if there is no URL, It is optional and if there is none, then the
			// body is just attribute/value pairs.
		}
		catch (NoSuchElementException e1) {
			throw new SOIFException("Illegal SOIF file format");
		}


		ParseSOIFBody(soifBody);
	}

  /**
   * Parse the body of the SOIF extracting the attribute/value pairs
   * @param body the body to parse
   */
	public void ParseSOIFBody(String body) throws SOIFException {
		int curPos = 0, bodyLength = body.length();
		attributeValues = new Hashtable();
		String attribute, attributeName, value;
		int valueLength,startValue, endValue;
		RE reg;
		REMatch result, nextResult;
        
		// pattern for an attribute (alpha-num string followed by :: - e.g. AUTHOR::)
		try  {   reg = new RE("\\s*(\\S+)\\::\\t");
			//{throw new gnu.regexp.REException();}
                     
        body = body.trim();

		while (curPos < body.length()) {
			if ((result = reg.getMatch(body, curPos)) == null) {
				throw new SOIFException("Illegal SOIF file format - can't find attribute name");
			}

			curPos = result.getStartIndex();
			attributeName = result.toString();
			attributeName = attributeName.substring(1);
			nextResult = reg.getMatch(body,result.getEndIndex() );
			startValue = result.getEndIndex();
			if(nextResult != null)
			{
			   attribute = nextResult.toString();	
			   endValue = nextResult.getStartIndex();
			}   
			else 
				endValue = body.length();
			valueLength = endValue - startValue -1;
			try {
				value = (body.substring(startValue , endValue));
				}
			catch (StringIndexOutOfBoundsException e) {
				throw new SOIFException("Illegal RFC1807 file format");
			}

			SetAttributeValue(attributeName, value);
			curPos += (endValue - curPos);
			
		 }
		}
		            catch (gnu.regexp.REException e){}

	}




 /**
        Return a short description 
            (probably only one sentence) of the document this represents.
        @param    markupType -- specifies the charater set type need to be returned
        @return    the short description of this object as a string
    */      
    public String presentShort(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
        int Err;
        try {
            if ( (Err = presentShort(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
       } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }


   public int presentShort(int markupType, BufferedWriter out) throws IOException
    {
        
        int Err;
		String AttributeValue,AUTHOR, TITLE;
		String []  AttributeNames;
		AttributeNames = this.GetAttributeNames();
		String shortPresentation;
		shortPresentation = "";
		AUTHOR= null;
		TITLE = null;
		switch ( markupType )
        {
         default:
            // only ASCII and XML are supported now.  ASCII produces the sort
            //  of single-line description used in results lists.  XML produces
            //  an OAMS description of the document.
            debug.dumpTrace("MarcDocument.presentShort(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH:
            
         case DigInfObj.ASCII:
            boolean seenAuthor = false;
            
         for(int i=0; i < this.GetNumAttributes(); i++)
	         {
                
                if(AttributeNames[i].indexOf("AUTHOR")!=-1)
				{
					seenAuthor = true;
					AUTHOR = this.GetAttributeValue(AttributeNames[i]);
					shortPresentation = shortPresentation +  AUTHOR;
				}	
				//Main title	
                if(AttributeNames[i].indexOf("TITLE")!=-1)
				 { 
				   	TITLE = this.GetAttributeValue(AttributeNames[i]);
					if (seenAuthor)
                    {
                        // add separator
						shortPresentation = shortPresentation + ": ";
					}
				       shortPresentation = shortPresentation +  TITLE;
                    
					
            	}
				                
            } // end -- for(...)
		     shortPresentation = shortPresentation + "\n";
             out.write(shortPresentation);
        
        } // end -- switch

        return( ReturnCodes.OK );
    }
		
   public String presentLong(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
         int Err;
        try {
            if ( (Err = presentLong(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
        } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }


    public int presentLong(int markupType, BufferedWriter out) throws IOException
    {
        String lineBreak;
        String paraBreak;
        int Err;
		String AttributeValue;
		String  presentLong = null;
		String [] AttributeNames = this.GetAttributeNames();
		
        switch ( markupType )
        {
         case DigInfObj.HTML:
            lineBreak = new String("<BR>" + System.getProperty("line.separator"));
            paraBreak = new String("<P>" + System.getProperty("line.separator"));
           // out.write("<B>Call Number:  </B>");
            break;

         default:
            debug.dumpTrace("MarcDocument.presentLong(): unsupported markup type:  using ASCII.");
            //  FALL THROUGH.

         case DigInfObj.ASCII: 
         case DigInfObj.ANSEL:
            lineBreak = System.getProperty("line.separator");
            paraBreak = new String(System.getProperty("line.separator") +
                                   System.getProperty("line.separator"));
           
            break;
        }

        //this.presentShort(markupType, out);
		
		presentLong= rfc1807String;	
		out.write(presentLong);
		return( ReturnCodes.OK );
    }

public boolean isValid()
	{
		return( true );
	}
	
	public DigInfObj copy()
	{
	 return null;
	}
	
 public String presentFull(int markupType)
    {
        StringWriter sw = new StringWriter();
        BufferedWriter out = new BufferedWriter( sw );
        int Err;
        try {
            if ( (Err = presentFull(markupType, out)) != ReturnCodes.OK )
                return( null );
			out.flush();
        } catch( Exception e ) { return( null ); }
        return(sw.toString());
    }  
 
 public Vector attributes()
	{
	return null;
	}
 
 public Vector attributes(int markupType)
 {
return null;
 }
 
 public Vector presentAttributes(int markupType)
	{
	return null;	
	}
 
 public Object presentAttribute(int attrID, int markupType)
	{
		return null;
	}

 public int presentFull(int markupType, BufferedWriter out) throws IOException
	{
		return( ReturnCodes.NOT_YET_IMPLEMENTED );
	}
   
/**
        Default "short" presentation for any variable field:  just use the 'a' subfield.
        @param	markupType    see edu.vt.marian.common.DigInfObj
        @param	out	A BufferedWriter (presumably String or OutputStream) to present on.
        @return    OK -- everything jake.
        <BR>    IO_ERROR or PARSE_ERROR -- problems.
    */      
/*    public int presentShortField(int markupType, BufferedWriter out) throws IOException
    {
		int Err;
        switch ( markupType )
        {
         default:
            debug.dumpTrace("MarcVarField.presentShort(): Unexpected markup type " +
                            markupType + ":  treating as ASCII.");
            // Fall through:

         case DigInfObj.XML:
         case DigInfObj.SGML:
         case DigInfObj.HTML:
         case DigInfObj.ASCII:
         case DigInfObj.ANSEL:
            Enumeration subfld = subfields.elements();
            try { while ( true )
            {
                //MarcSubField sf = (MarcSubField) subfld.nextElement();
                //if ( sf.getLabel() == 'a' )
                   if ( (Err = sf.present(markupType, out)) != ReturnCodes.OK )
					   return( Err );
                if ( subfld.hasMoreElements() )
                   out.write(' ');
            }
			  } catch( NoSuchElementException e) {};
        }
     return( ReturnCodes.OK );
    }
*/

}