/*
 * Created on 16.07.2004
 *
 */
package basics.string;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import basics.testing.Test;
import basics.unexpected.Problem;
import basics.utl.SUtl;

/**
 * This is a simple formalism for replacing tagged substrings. 
 * No overlapping allowed!
 * @author kst
 */
public class TaggedTokenReplacer
{
private static final String DEFAULT_STARTTAG = "%";
private static final String DEFAULT_ENDTAG   = "%";
private String              startTag         = DEFAULT_STARTTAG;
private String              endTag           = DEFAULT_ENDTAG;
private String              text             = "";

public TaggedTokenReplacer(String text)
{
   this.text = text;
}

public TaggedTokenReplacer clone()
{
   return new TaggedTokenReplacer(text);
}

public void setTags(String startTag, String endTag)
{
   this.startTag = startTag;
   this.endTag = endTag;
}

public String getText()
{
   return text;
}

public String toString()
{
   return text;
}

public Iterator<String> getAllTokens() throws Problem
{
   List<String> list = new ArrayList<String>();
   if(text != null)
   {
      int x1 = 0, x2 = -1;
      while(true)
      {
         x1 = text.indexOf(startTag, x2 + 1) + 1;
         if(x1 == 0)
         {
            break;
         }
         x2 = text.indexOf(endTag, x1);
         if(x2 == -1)
         {
            throw new Problem("Error in " + text + ": closing tag " + endTag + " is missing.");
         }
         list.add(text.substring(x1, x2));
      }
   }
   return list.iterator();
}

public String replace(String key, TaggedTokenReplacer value)
{
   return replace(key, value.getText());
}

public String replace(String key, Object value)
{
   return replace(key, value + "");
}

public String replace(String key, StringBuilder value)
{
   return replace(key, value.toString());
}

public String replace(String key, StringBuffer value)
{
   return replace(key, value.toString());
}

public String replace(String key, String value)
{
   StringBuilder mk = new StringBuilder();
   mk.append(startTag);
   mk.append(key);
   mk.append(endTag);
   replaceAll(mk, value);
   return text;
}

private void replaceAll(StringBuilder searchbuffer, String replace)
{
   if(searchbuffer == null)
      return;
   text = text == null ? "" : text;
   final String search = searchbuffer.toString();
   replace = replace == null ? "" : replace;
   for(int pos = text.indexOf(search), replaceLength = replace.length(); text.length() > 0
      && search.length() > 0 && pos > -1;)
   {
      String s1 = text.substring(0, pos);
      String s2 = text.substring(pos + search.length());
      int nextpos = s1.length() + replaceLength;
      text = new StringBuilder(s1).append(replace).append(s2).toString();
      pos = text.indexOf(search, nextpos);
   }
}

/**
 * Replaces all occurrences of placeholders in pattern with a value at the same array index. This
 * method is twice as fast as the non-static replace(String, String).
 * @param pattern
 * @param placeholders
 * @param values
 * @return
 */
public static final StringBuilder replaceAll(final String pattern, final String [] placeholders,
   final Object [] values)
{
   return replaceAll(null, pattern, placeholders, values);
}

/**
 * Replaces all occurrences of placeholders in pattern with a value at the same array index. The
 * returned StringBuilder is the specified appended by the processed pattern. This method is twice
 * as fast as the non-static replace(String, String).
 * @param b
 * @param pattern
 * @param placeholders
 * @param values
 * @return Specified or new StringBuilder if none was provided.
 */
public static final StringBuilder replaceAll(StringBuilder b, final String pattern,
   final String [] placeholders, final Object [] values)
{
   if(b == null)
      b = new StringBuilder(pattern.length());
   int index = pattern.indexOf(DEFAULT_STARTTAG);
   int lastIndex = 0;
   while(index != -1)
   {
      index += DEFAULT_STARTTAG.length();
      for(int i = 0; i < placeholders.length; i++)
      {
         String current = placeholders[i];
         if(pattern.regionMatches(false, index + current.length(), DEFAULT_ENDTAG, 0,
            DEFAULT_ENDTAG.length())
            && pattern.regionMatches(false, index, current, 0, current.length()))
         {
            b.append(pattern.substring(lastIndex, index - DEFAULT_STARTTAG.length()));
            b.append(values[i]);
            index += current.length() + DEFAULT_ENDTAG.length();
            lastIndex = index;
            break;
         }
      }
      index = pattern.indexOf(DEFAULT_STARTTAG, index);
   }
   return b.append(pattern.substring(lastIndex));
}

public static void unittest()
{
   String pat = "concat%tag_1%and%tag_2%and%tag_3%";
   TaggedTokenReplacer t = new TaggedTokenReplacer(pat);
   t.replace("tag_1", "A");
   t.replace("tag_2", "B");
   t.replace("tag_3", "C");
   Test.assertEqualsTrue(t.getText(), "concatAandBandC",
      "replacement failed:, expected 'concatAandBandC', not " + t.getText());
   Test.assertEqualsTrue(t.getText(), replaceAll(pat, new String [] { "tag_1", "tag_2", "tag_3" },
      new Object [] { "A", "B", "C" }), "instance method and static method not equal");
   try
   {
      t = new TaggedTokenReplacer(pat);
      Iterator<String> i = t.getAllTokens();
      int x = 0;
      Set<String> set = new HashSet<String>();
      while(i.hasNext())
      {
         String tag = i.next();
         Test.assertFalse(set.contains(tag), tag + " already hit");
         Test.assertTrue(SUtl.contains(new String [] { "tag_1", "tag_2", "tag_3" }, tag),
            "unknown tag: " + tag);
         x++;
      }
      Test.assertTrue(x == 3, "3 tags expected, not " + x);
   }
   catch(Problem e)
   {
      Test.handleAsFailure(e);
   }
}
}
