package edu.vt.marian.Document;

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

import edu.vt.marian.common.*;


/**
    A "variable field" in a US MARC record.  The user can assume a variable
        field is composed of an integer id, two one-character
        indicators and at least one subfield.
    @author	Robert France
    @author	Jianxin (Jason) Zhao (jxzhao@csgrad.cs.vt.edu)

*/

public class MarcVarField
{
    /** The USMARC field id.
    */
    protected int id;

    /** Indicator characters:  see MARC documentation.
    */
    protected char indicator1;
    protected char indicator2;

    /** the USMARC tape format subfield separator character.
    */
    protected final static String subfield_separator = "\037";

    /** all the subfields of this variable field
    */
    protected Vector subfields;
        
    /** just used for debugging
    */
    protected Debug debug;

	/** Device for mapping ANSEL (and some ASCII) characters to XML entities.
	 */
	protected EntityMap xmlMap;


    /**
        Create a MarcVarField object from a field ID and a segment
            of a MARC tape format record.
        @param	id --- this will be the id of this object
        @param	tapeFormat -- this string contains the indicators and
                 all the subfields in US MARC tape format.
        @param	debug -- used for debugging
    */      
    public MarcVarField(int fieldId, EntityMap xMap, Debug dbg)
    {
        debug = dbg;
        id = fieldId;
		xmlMap = xMap;
        subfields = null;	// Signal for an invalid field.
    }


    public void setIndicators(char ind1, char ind2)
    {
        indicator1 = ind1;
        indicator2 = ind2;
    }


    /**
        Set this object from a segment of a MARC tape format record.
        @param	tapeFormat -- this string contains 
                 all the subfields in US MARC tape format.
        @param	debug -- used for debugging
    */      
    public int setFromTapeFormat(String tapeFormat)
    {
        if ((tapeFormat == null) || (tapeFormat.length() < 2))
        {
            debug.dumpTrace("MarcVarField.setFromTapeFormat():  tape format string is invalid.");
            return( ReturnCodes.BAD_PARAMS );
        }

        // parse indicators
        setIndicators(tapeFormat.charAt(0), tapeFormat.charAt(1));

        // Analyze subfields.
        subfields = new Vector();
        MarcSubField msf = null;
        StringTokenizer st = new StringTokenizer(tapeFormat.substring(2),
						 subfield_separator);
        String s = null;
        while (st.hasMoreTokens())
        {
            // possible next subfield
            s = st.nextToken();
            if (s.length() <= 1)
            {
                // subfield length should be at least 2
                debug.dumpTrace("MarcVarField.setFromTapeFormat(): subfield length (" +
                    s.length() + ") not long enough in '" + tapeFormat + "'.");
                return( ReturnCodes.BAD_PARAMS );
            }
            else
            {
                msf = new MarcSubField(s.charAt(0), s.substring(1), xmlMap, debug);
                subfields.addElement(msf);
            }
        }

        // all the subfields have been parsed
        return( ReturnCodes.OK );
    }


    /**
        Set this object from an OA XML MARC string.
        @param	in -- a BufferedReader containing the contents
                 of a VarField (i.e., all the subfields but
                 not the initial tag) in OA XML format.
        @return    OK -- everything jake.
        <BR>    IO_ERROR or PARSE_ERROR -- problems.
        <P>
        <B>NOTE:</B>  Expects opening tag to already be read; eats closing tag.
    */      
    public int setFromXml(BufferedReader in) throws IOException 
    {
        if ( in == null)
            return( ReturnCodes.BAD_PARAMS );
		
        subfields = new Vector();
        Vector bindings;
        while ( true )
        {
            if ( (bindings = XmlDoc.acceptTag(in)) == null )
            {
                debug.dumpTrace("MarcVarField.setFromXml(): missing tag in field " + id + ".");
                return( ReturnCodes.PARSE_ERROR );
            }
		
            if ( ((String) bindings.elementAt(0)).equals("/varfield") )
            {
                 if ( subfields.size() == 0 )
            	 {
                     debug.dumpTrace("MarcVarField.setFromXml(): no subfields for field " +
                                     id + ".");
                     subfields = null;	// Mark invalid.
                     return( ReturnCodes.IO_ERROR );
                 }
                 else
                     return( ReturnCodes.OK );
            }

            // Only valid nested tag here is "<subfield label=*>".
            if ( ( ! ((String) bindings.elementAt(0)).equals("subfield") ) ||
                 ( ! ((String) bindings.elementAt(1)).equals("label") ) )
            {
                debug.dumpTrace("MarcVarField.setFromXml(): malformed subfield tag in field "
								+ id + ".");
                subfields = null;	// Mark invalid.
                return( ReturnCodes.PARSE_ERROR );
            }
            char label = ((String) bindings.elementAt(2)).charAt(0);
			
            String subFieldStr = xmlMap.getStringFromEntityReader(in);
            MarcSubField msf = new MarcSubField(label, subFieldStr, xmlMap, debug);
            subfields.addElement(msf);
			
            if ( (bindings = XmlDoc.acceptPreppedTag(in)) == null )
            {
                debug.dumpTrace("MarcVarField.setFromXml(): no </subfield> tag in field " +
								id + ".");
 			    subfields = null;	// Mark invalid.
                return( ReturnCodes.PARSE_ERROR );
            }		
            if ( ! ((String) bindings.elementAt(0)).equals("/subfield") )
            {
				debug.dumpTrace("MarcVarField.setFromXml(): unexpected tag <" +
							    (String) bindings.elementAt(0) + "> in field " + id + ".");
			    subfields = null;	// Mark invalid.
			    return( ReturnCodes.PARSE_ERROR );
            }
	    }
    }


    /**
        Send this to an output stream in the form of OAI XML.
        @param	out -- a BufferedWriter (String or InputStream, presumably) to which
			to write the next record in OA_MARC XML format.
        @return    OK -- everything jake.
        <BR>    INVALID_INPUT or PARSE_ERROR -- problems.
    */      
    public int presentAsTapeFormat(BufferedWriter out) throws IOException 
    {
        int Err;
        out.write(indicator1);
        out.write(indicator2);
        Enumeration subfld = subfields.elements();
        try { while ( true )
        {
            MarcSubField sf = (MarcSubField) subfld.nextElement();
            out.write(subfield_separator);
            out.write(sf.getLabel());
            if ( (Err = sf.present(DigInfObj.ANSEL, out)) != ReturnCodes.OK )
                return( Err );
        } } catch( NoSuchElementException e) {};
        return( ReturnCodes.OK );
    }
	

    /**
        Send this to an output stream in the form of OAI XML.
        @param	out -- a BufferedWriter (String or InputStream, presumably) to which
			to write the next record in OA_MARC XML format.
        @return    OK -- everything jake.
        <BR>    INVALID_INPUT or PARSE_ERROR -- problems.
    */      
    public int presentAsXml(BufferedWriter out) throws IOException
    {
        if ( subfields == null)		// Invalid field
        {
            return( ReturnCodes.NO_CAN_DO );
        }

        out.write("<varfield id=\"" + id + "\" i1=\"" + indicator1 + 
                         "\" i2=\"" + indicator2 + "\">");
        Enumeration subfld = subfields.elements();
		int Err;
        try { while ( true )
        {
            MarcSubField sf = (MarcSubField) subfld.nextElement();
            if ( (Err = sf.present( DigInfObj.XML, out)) != ReturnCodes.OK )
				return(Err);
            out.newLine();
        } } catch( NoSuchElementException e) {};
        out.write("</varfield>");
        return( ReturnCodes.OK );
    }


    /**
        return the id of this object
        @return    the id of this object as an integer
    */      
    public int getID()
    {
		if ( subfields == null)		// Invalid field
		{
			return( ReturnCodes.NO_CAN_DO );
		}
        return id;
    }


    /**
        return the number of subfields in this object
        @return    the number of subfields in this object as an integer
    */      
    public int getNumberSubfields()
    {
		if ( subfields == null)		// Invalid field
		{
			return( ReturnCodes.NO_CAN_DO );
		}
        return subfields.size();
    }

    /**
        return the indexth subfield of this object
        @param    index -- used to search the subfields vector
        @return    a MarcSubField object or null if the index is not invalid
    */      
    public MarcSubField getSubfieldByIndex(int index)
    {
 		if ( subfields == null)		// Invalid field
		{
			return( null );
		}
       if ((index < 0) || (index >= subfields.size()))
        {
            // invalid index
            debug.dumpTrace("MarcVarField.getSubfieldByIndex(): index is not valid.");
            return null;
        }

        // valid index, return the corresponding subfield
        return (MarcSubField) subfields.elementAt(index);
    }
    

    /**
        return all the subfields in this object with the specified label
        @param    label -- used to search the subfields vector
        @return    a vector containing all the subfields in this object
            that have the specified label.
    */      
    public Vector getSubfieldsByLabel(char label)
    {
		if ( subfields == null)		// Invalid field
		{
			return( null );
		}
        Vector msfs = new Vector();
        MarcSubField msf = null;
        for (int i = 0; i < subfields.size(); i++)
        {
            msf = (MarcSubField) subfields.elementAt(i);
            if (msf.getLabel() == label)
            {
                msfs.addElement(msf);
            }
        }

        return msfs;
    }


    /**
        return the indicators of this object
        @return    the indicators of this object as a string
    */      
    public String getIndicators()
    {
		if ( subfields == null)		// Invalid field
		{
			return( null );
		}
        char [] indChars = new char [2];
        indChars[0] = indicator1;
        indChars[1] = indicator2;
        return( new String(indChars) );
    }

}

