package tcra.math;

import java.util.*;

/**
 * MathString is an abstraction of the mathematical "String of
 * [ElementType]". MathString is parameterized to allow the elements
 * to be of any type of MathType.
 */
public class MathString<ElementType extends MathType>
       implements MathType
{
   private Vector<ElementType> contents;
   private MathStringParser    parser;

   /**
    * Class constructor specifying the elements in the string and
    * the MathStringParser used to parse the String from user input.
    *
    * @param elem Vector of the parameterized type ElementType that
    *             is the contents of the MathString
    * @param msp  MathStringParser used to parse the user input
    */
   public MathString(Vector<ElementType> elem, MathStringParser msp)
   {
      parser   = msp;
      contents = elem;
   } // end ctor(Vector)

   /**
    * Returns the number of elements in the MathString
    *
    * @return number of elements contained in the MathString
    */
   public int length() { return contents.size(); }

   /**
    * Returns a MathString that is the concatenation of this 
    * MathString and another MathString provided by the user.
    *
    * @param latter MathString to be concatenated onto
    *               this MathString.
    * @return       Concatenized MathString.
    */
   public MathString<ElementType> concat(
                  MathString<ElementType> latter)
   {
      Vector<ElementType> temp = new Vector<ElementType>();
      temp.addAll(contents);
      temp.addAll(latter.contents);

      return new MathString<ElementType>(temp, parser);
   } // end concat(MathString)

   /**
    * Returns a MathString that has had the provided element
    * concatenated on to it.
    *
    * @param latter Element to be added to this MathString
    * @return       Concatenized MathString
    */
   public MathString<ElementType> concat(ElementType latter)
   {
      Vector<ElementType> temp = new Vector<ElementType>(contents);
      temp.add(latter);
      return new MathString<ElementType>(temp, parser);
   } // end concat(ElementType)
   
   /**
    * Returns a MathString that has had the provided MathString
    * concatenated on to the beginning of this string.
    *
    * @param former MathString to be added to this MathString
    * @return       Concatenized MathString
    */
   public MathString<ElementType> prepend(
          MathString<ElementType> former)
   {
      Vector<ElementType> temp = new Vector<ElementType>();
      temp.addAll(former.contents);
      temp.addAll(contents);
      return new MathString<ElementType>(temp, parser);
   } // end prepend(MathString)

   /**
    * Returns a MathString that has had the provided element
    * concatenated on to the beginning of this string.
    *
    * @param former Element to be added to this MathString
    * @return       Concatenized MathString
    */
   public MathString<ElementType> prepend(ElementType former)
   {
      Vector<ElementType> temp = new Vector<ElementType>();
      temp.add(former);
      temp.addAll(contents);
      return new MathString<ElementType>(temp, parser);
   } // end concat(ElementType)

   /**
    * Tests the MathString provided and this MathString for equality.
    * They are equal if the contents of the MathStrings are equal and
    * in the same order.
    *
    * @param other MathString that this is compared to.
    * @return <code>true</code> if the MathString and the MathString
    *         provided are equal.
    *         <code>false</code> otherwise.
    */
   @SuppressWarnings("unchecked")
   public boolean equals(Object other)
   {
      if(other == null) return false;
      if(!(other instanceof MathString)) return false;

      MathString<ElementType> check  = (MathString<ElementType>)other;
      Vector<ElementType> v_contents = 
                             (Vector<ElementType>)check.contents;

      return contents.equals(v_contents);
   } // end equals(String)

   /**
    * Checks the provided [ElementType] and the element of the string
    * at 'index' for equality.
    *
    * @param index Index of the element we want to compare the
    *              provided [ElementType] to.
    * @param compare the [ElementType] to compare to
    * @return result of the comparison 
    */
   public boolean compareElement(int index, ElementType compare)
   {
      return contents.elementAt(index).equals(compare);
   } // end compareElement(int,ElementType)

   /**
    * Returns two seperate MathStrings that this MathString
    * split after the specified element number.
    *
    * @param elemNum position to split the MathString around.
    * @return Vector containing two MathStrings. The first will
    *         contain elemNum elements, and the second will
    *         contain this.length() - elemNum elements.
    */
   public Vector<MathString<ElementType>> split(int elemNum)
   {
      // put in array and return
      Vector<MathString<ElementType>> pair = 
                                new Vector<MathString<ElementType>>();
      if(elemNum < -1)
      {
         pair.add(new MathString<ElementType>(
                  new Vector<ElementType>(), parser));
         pair.add(new MathString<ElementType>(
                  new Vector<ElementType>(), parser));
         return pair;
      } // end if

      pair.add(new MathString<ElementType>(new Vector<ElementType>(
                            contents.subList(0, elemNum+1)), parser));

      if(elemNum+1 == contents.size())
      {
         pair.add(new MathString<ElementType>(
                  new Vector<ElementType>(), parser));
      }
      else
      {
          pair.add(new MathString<ElementType>(new Vector<ElementType>
                                          (contents.subList(elemNum+1,
                                                    contents.size())),
                                                    parser));
      }

     return pair;
   } // end split(int)
   
   /**
    * Get the reverse of this MathString
    * @return MathString who's contents is reversed from
    *         this MathString
    */
   public MathString<ElementType> reverse()
   {
      Vector<ElementType> temp = new Vector<ElementType>();
      for(int i = contents.size()-1; i >= 0; --i)
      {
         temp.add(contents.elementAt(i));
      } // end for
      
      return new MathString<ElementType>(temp, parser);
   } // end reverse
   
   /**
    * Checks to see if the provided MathString is contained at the end
    * of this MathString. 
    * @param suffix MathString to check for
    * @return <code>true</code> if suffix is present at the end
    *         <code>false</code> otherwise
    */
   public boolean isSuffix(MathString<ElementType> suffix)
   {      
      Vector<ElementType> temp =
                             new Vector<ElementType>(contents.subList(
                                      contents.size()-suffix.length(),
                                      contents.size()));
      
      return(temp.equals(suffix.contents));
   } // end isSuffix(MathString)
   
   /**
    * Returns the element in the string at the specified index. Index
    * is 0-based.
    * 
    * @param index 0-based index of element
    * @return Element at index
    */
   public ElementType elementAt(int index)
   {
      return((ElementType)contents.get(index));
   } // end elementAt(int)

   public String toString()
   {
      if(contents.size() == 0) return "<>";

      String ret = "< ";
      ret += getContentStrings();
      ret += contents.get(contents.size()-1) + ">";

      return ret;
   } // end toString()

   public String toHTMLString()
   {
      if(contents.size() == 0) return "&lt &gt";

      String ret = "&lt ";
      ret += getContentStrings();
      ret += contents.get(contents.size()-1) + " &gt";

      return ret;
   } // end toString()

   private String getContentStrings()
   {
      String ret = "";
      for(int i = 0; i < contents.size()-1; ++i)
      {
        ret += contents.get(i).toString() + ", ";
      } // end for
      return ret;
   } // end getContentStrings
} // end class
