/** Universal class to keep a set of strings. Can grow on demand.
 *  By Audrius Meskauskas.
 */

package Sight.Structures;

import Sight.io.Output;
import java.io.*;

public class Strings implements Serializable {

   public String L[];
   int step;
   /** Length of the strings list. The name matches the corresponding variable java in arrays. */
   public int length;
   int Top;

   /** Constructs new Strings object with defaul initial size and growth step.
    *  The groweth step increases *3/2 after each new size increase. */
   public Strings() { this(10,10); };

   /** Constructs new Strings object with initial size a,
    *  which on oferflow grows by site b.
    */
   public Strings(int initialSize, int growBy)
    {
      //L = Recycler.Recycled._StringArray(a+1);
      int a = initialSize;
      int b = growBy;
      L = new String[a+1];
      step = b;
      Top = a+1;
      length = 0;
    };

   /** Constructs new Strings object from array of strings.
    *  For backward conversion, see toStringArray(). */
   public Strings(String []A)
    { int k;
      length = A.length;
      L = new String[length+5];
      //L = Sight.util.Recycler.Recycled._StringArray(length+5);

      step = 5;
      Top =  length+5;
      System.arraycopy(A,0,L,0,A.length);
    };

   /** Creates a copy of this object. Strings are not copied, only pointers to them. */
   public Strings(Strings G)
    {
      this(G.L);
      length = G.length;
      step = G.step;
    };

   /** Tests if this Strings has the string N (case sensitive). */
   public boolean has(String N)
    { if (N==null) return false;
      int k;
      if (length>0)
       for (k=0;k<length;k++)
        {
          if (L[k] == null) break;
          if (L[k].compareTo(N)==0) return true;
        };
      return false;
    };

   /** Tests if this Strings has at least one from
    *  the strings from the given list */
   public boolean has(Strings list)
    { int k;
      if (length>0)
       for (k=0;k<length;k++)
        if (list.has(L[k])) return true;
      return false;
    };

   /** Number of this string in array L, -1 if not found. This method compares
    *  the whole strings while scan is looking for the string, containing the
    *  given fragment. */
   public int indexOf(String N)
    { int k;
      if (length>0)
       for (k=0;k<length;k++)
        if (L[k].compareTo(N)==0) return k;
      return -1;
    };

   /** Removes all elements. */
   public void Clear()
   { length = 0; }

   /** Member at position p, deprecated. */
   public String at(int x) throws ArrayIndexOutOfBoundsException
    {
      if (x>=length) return null;
      return L[x];
    };

   /** Member at position p. More preferred than at() because the name matches
    *  the analogical function in java.util.ArrayList and some other classes. */
   public String get(int x) throws ArrayIndexOutOfBoundsException
    {
      if (x>=length) return null;
      return L[x];
    };

   /** Alphabetically sorts all items by name. */
   public void Sort()
    { int k, l; String n;
      for (k=0; k<length; k++)
       for (l=k+1; l<length; l++)
        if (L[k].compareTo(L[l])>0) // String
         { n = L[k]; L[k] = L[l]; L[l]=n; };
    };

   /** Add the new string to collection. Returns without action if N==null */
   public void add(String N)
    { //if (has(N)) return;
      if (N==null) return; // no null element.
      length++;
      if (length>Top)
       { String nL[];
         //nL = Recycler.Recycled._StringArray(Top+step);
         nL = new String[Top+step];
         System.arraycopy(L,0,nL,0,length-1);
         //Recycler.Recycled.put(L);
         L = nL;
         Top = Top+step;
         step = (step*3)/2;
       };
      L[length-1] = N;
    };

   /** Add the new string to collection, does not allow duplicate strings.
    *   Returns without action if N==null.  */
   public void addExc(String N)
    { if (has(N)) return;
      if (N==null) return; // no null element.
      add(N);
    };

   /** Add array of strings. */
   public void add(String [] a)
    { if (a==null) return;
      ensureCapacity(length+a.length);
      for (int i = 0; i < a.length; i++) {
        add(a[i]);
      }
    };

   /** Add array of strings. */
   public void addExc(String [] a)
    { if (a==null) return;
      ensureCapacity(length+a.length);
      for (int i = 0; i < a.length; i++) {
        addExc(a[i]);
      }
    };

   /** Ensures that the given number of strings can be stored
    *  without re-allocations.
    */
   public void ensureCapacity(int size)
    {
      if (size>L.length)
       { String nL[];
         nL = new String[size];
         if (length>0)
          System.arraycopy(L,0,nL,0,length);
         L = nL;
         Top = L.length;
       };
    };

   /** Scans for the string, containing the given substring. Starts
    *  scanning the list from position p. Throws Exception on fail. */
   public int scan(String N, int p) throws Exception
    { int j = _scan(N, p);
      if (j<0) throw new Exception(N+" nf.");
      return j;
    };

   /** Scans for the string, containing the given substring. Starts
    *  scanning the list from position p. Returns -1 on fail. */
   public int _scan(String N, int p)
    {
      if (length>p)
       for (int k=p;k<length;k++)
       { if (L[k].indexOf(N)>=0) return k;
       };
      return -1;
    };

   /** Scans for the string, containing the given substring. Shortened form of Scan(String, int) */
   public int _scan(String N)
    {
      return _scan(N,0);
    };


   /** Scans for the string, containing the given substring, case
    *  insensitive. Starts
    *  scanning the list from position p. Returns -1 on fail. */
   public int _scan_ic(String N, int p)
    { N = N.toLowerCase();
      if (length>p)
       for (int k=p;k<length;k++)
       { if (L[k].toLowerCase().indexOf(N)>=0) return k;
       };
      return -1;
    };

   /** Deletes all strings, present in the specified list. */
   public void remove(Strings what)
   /** Removes all strings that are contained in 'what' */
    { for (int k=0;k<what.length;k++)
       { if (has(what.at(k))) remove(what.at(k));
       };
    };

   /** Removes string n. All strings above n in tha array L
    *   are shifted down.
    *   */
   public void remove(String n)
    { int nn;
      int k, l;
      nn = -1;
      for (k = 0 ; k < length ; k++)
       if (L[k]==n)
        { nn=k; break; };
      if (nn<0) return; // String not found.
      // Shift the rest of array down if this is not the last element:
      if (nn!=length-1)
       for (k = nn+1 ; k < length ; k++)
        L[k-1]=L[k];
      length--;
    };

   /** Gives new value for the String at position p */
   public void setElementAt(String N, int p)
    { L[p] = N;
    };

   public void dump()
   {
     dump(System.out);
   };

   public void dump(PrintStream f)
    { int k;
      for (k=0;k<length;k++)
       f.println(Integer.toString(k)+" "+at(k)+"\r");
    }

   public void dump(PrintStream f, int how)
    { int k;
           if (how == Strings.LINENUM) dump(f);
      else if (how == Strings.HTML)
       { f.println("<MENU>");
         for (k=0;k<length;k++)
          f.println("<LI>"+at(k));
         f.println("</MENU>");
       }
      else
      for (k=0;k<length;k++)
       f.println(at(k)+"\r");
    }

   /** Add all strings from x */
   public void add(Strings x)
   {if (x!=null)
    if (x.length>0)
     { ensureCapacity(length+x.length);
      for (int k=0;k<x.length;k++)
       { add(x.L[k]);
       };
     };
   };

   /** Add all strings from x that are not already present. */
   public void addExc(Strings x)
   { if (x!=null)
     if (x.length>0)
     {
      //if (x instanceof xStrings) _x_addExc(x);
      // do not access L directly, use at(..)
      //else
       { ensureCapacity(length+x.length);
         for (int k=0;k<x.length;k++)
          { if (!has(x.L[k]))
             add(x.L[k]);
          };
       };
     };
   };

   /** Add all strings from x. */
   public void _x_add(Strings x)
   {if (x!=null)
    if (x.length>0)
      {
        //if (x instanceof xStrings) _x_add(x);
        // do not access L directly, use at(..)
        //else
         {
          for (int k=0;k<x.length;k++)
           { add(x.at(k));
           };
         };
      };
   };

   /** Add all strings from x that are not already present. */
   void _x_addExc(Strings x)
   { if (x!=null)
     if (x.length>0)
     {
       for (int k=0;k<x.length;k++)
        { if (!has(x.at(k)))
           add(x.at(k));
        };
     };
   };

   /** Write the content to file. */
   public void save(String file)
   { try {
     String comment = callStack();
     FileOutputStream fs = new FileOutputStream(file);
     PrintStream f = new PrintStream(fs);
     f.println(comment);
     dump(f);
     f.close();
     fs.close();
     } catch (IOException ioex)
      { System.out.println(ioex.getMessage()); };
   };

   /** Write the content to file. how can be PLAIN, HTML or LINENUM (with line numbers). */
   public void save(String file, int how)
   { try {
     String comment = callStack();
     FileOutputStream fs = new FileOutputStream(file);
     PrintStream f = new PrintStream(fs);
     f.println(comment);
     dump(f, how);
     f.close();
     fs.close();
     } catch (IOException ioex)
      { System.out.println(ioex.getMessage()); };
   };

   /** Reads a content of file, creating new Strings object.
    *  If the file is not found or the reading error occurs,
    *  prints error message and returns the empty Strings object. */
   public Strings(String file)
   { this();
     File ft = new File(file);
     if (!ft.exists())
      { System.err.println(file+" not found. ");
      };
     try {
     FileInputStream f = new FileInputStream(file);
     BufferedReader in = new BufferedReader(
      new InputStreamReader(f));

     String str;
     int np;
     while ((str = in.readLine()) != null) {

      str = str.trim();

      if (str.startsWith("//")) continue; // ignore comments
      // remove string number at left:
      np = str.indexOf(" "); // space separates name from number
      if (str.length()>0) // ignore empty strings
       { str = str.substring(np+1).trim();
         add(str);
       };
     }

     } catch (IOException ioex)
      { System.out.println(ioex.getMessage()); };
   };

   public static int PLAIN = 1;
   public static int LINENUM = 2;
   public static int HTML = 3;

   public Strings(String file, int how)
   { this();
     File ft = new File(file);
     if (!ft.exists())
      { System.out.println(file+" not found! ");
      };
     try {
     FileInputStream f = new FileInputStream(file);
     BufferedReader in = new BufferedReader(
      new InputStreamReader(f));

     String str;
     int np;
     while ((str = in.readLine()) != null) {
      str = str.replace('\r',' ');
      str = str.replace('\n',' ');
      str = str.trim();
      if (str.length()==0) continue; // ignore empty strings
      if (str.startsWith("//")) continue; // ignore comments
      // remove string number at left:
      if (how==LINENUM)
       {
        np = str.indexOf(" "); // space separates name from number
        if (str.length()>0) // ignore empty strings
         add(str.substring(np+1).trim());
       } else add(str);
     }

     } catch (IOException ioex)
      { System.out.println(ioex.getMessage()); };
   };

   public String toString()
    { StringBuffer b = new StringBuffer(599);
      for (int k=0;k<length;k++)
       { b.append(at(k));
         b.append(", ");
       };
      return b.toString();
    };

   private String callStack()
    {
     String Stack = "";
     int pp;
     try {
      // http://developer.java.sun.com/developer/qow/archive/104/index.html
      StringWriter sw = new StringWriter();
      new Throwable().printStackTrace(
        new PrintWriter( sw )
      );
      Stack = sw.toString().replace('\n',' ').replace('\r',' ');
     } catch (Exception exc) {}; // For piece of mind!
     return Stack;
    };

   /** Converts to String[]. */
   public String[] toStringArray()
    {
      String [] r = new String[length];
      System.arraycopy(L,0,r,0,length);
      return r;
    };

 /**
  *
  */
 static public void main(String[] args) {
   Strings s = new Strings(2,2);
   for (int i = 0; i < 10; i++) {
     s.add("item "+i);
     s.add("item "+i);
   }
   s.dump();
   Strings single = new Strings();
   single.addExc(s);
   System.out.println("Single");
   single.dump();
 }

 }