//
// This software is now distributed according to
// the Lesser Gnu Public License.  Please see
// http://www.gnu.org/copyleft/lesser.txt for
// the details.
//    -- Happy Computing!
//
package com.stevesoft.pat;

import com.stevesoft.pat.wrap.*;

/** Internally used class. */
class RegHolder
{
  Regex me = null;

  RegHolder prev = null;
}

/**
 * Internally used class.
 * 
 * @see CodeRule
 */
class CodeVal
{
  int pos;

  char code;

  CodeVal(int p, char c)
  {
    pos = p;
    code = c;
  }

  public String toString()
  {
    return "(" + pos + "," + code + ")";
  }
}

/**
 * To use this class, first use either the getReplacer() method from Transformer
 * or Regex. You can then use replaceAll, replaceFirst, etc. methods on the
 * Replacer in the same way that you can from either of those two classes.
 * <p>
 * The only potential difference between using the methods of Replacer to do the
 * replacing is that Replacer remembers changes to the replacing object between
 * calls to replaceAll, replaceFirst etc. For details, see the example file <a
 * href="http://javaregex.com/code/trans3.java.html">trans3.java</a>.
 * 
 * @see com.stevesoft.pat.Transformer
 * @see com.stevesoft.pat.Regex
 */
public class Replacer
{
  boolean first;

  /** Instantiate a new Replacer. */
  public Replacer()
  {
  }

  public StringLike replaceFirstRegion(String s, Regex r, int start, int end)
  {
    return replaceFirstRegion(new StringWrap(s), r, start, end);
  }

  /**
   * This method replaces the first occurence of the Regex in the String
   * starting with position pos according to the Replacer rule of this object.
   */
  public StringLike replaceFirstRegion(StringLike s, Regex r, int start,
          int end)
  {
    first = true;
    rh.me = r;
    rh.prev = null;
    return dorep(s, start, end);
  }

  public StringLike replaceFirst(StringLike s)
  {
    return replaceFirstRegion(s, 0, s.length());
  }

  public StringLike replaceFirstFrom(StringLike s, int start)
  {
    return replaceFirstRegion(s, start, s.length());
  }

  public StringLike replaceFirstRegion(StringLike s, int start, int end)
  {
    first = true;
    return dorep(s, start, end);
  }

  RegHolder rh = new RegHolder();

  public StringLike replaceAllRegion(String s, Regex r, int start, int end)
  {
    return replaceAllRegion(new StringWrap(s), r, start, end);
  }

  /**
   * This method replaces all occurences of the Regex in the String starting
   * with postition pos according to the Replacer rule of this object.
   */
  public StringLike replaceAllRegion(StringLike s, Regex r, int start,
          int end)
  {
    first = false;
    // reset
    rh.me = r;
    rh.prev = null;
    return dorep(s, start, end);
  }

  public StringLike replaceAll(StringLike s)
  {
    return replaceAllRegion(s, 0, s.length());
  }

  public StringLike replaceAllFrom(StringLike s, int start)
  {
    return replaceAllRegion(s, start, s.length());
  }

  public StringLike replaceAllRegion(StringLike s, int start, int end)
  {
    first = false;
    return dorep(s, start, end);
  }

  public String replaceAll(String s)
  {
    return replaceAllRegion(new StringWrap(s), 0, s.length()).toString();
  }

  public String replaceAllFrom(String s, int start)
  {
    return replaceAllRegion(new StringWrap(s), start, s.length())
            .toString();
  }

  public String replaceAllRegion(String s, int start, int end)
  {
    first = false;
    return dorep(new StringWrap(s), start, end).toString();
  }

  final public boolean isSpecial(ReplaceRule x)
  {
    while (x != null)
    {
      if (x instanceof SpecialRule
              || (x instanceof RuleHolder && ((RuleHolder) x).held instanceof SpecialRule))
      {
        return true;
      }
      x = x.next;
    }
    return false;
  }

  final public void apply1(RegRes rr)
  {
    rr.charsMatched_++;
    apply(rr, null);
    rr.charsMatched_--;
  }

  final StringLike dorep(StringLike s, int start, int end)
  {
    StringLike ret = s;
    want_more_text = false;
    lastMatchedTo = 0;
    if (rh.me == null)
    {
      throw new NullPointerException("Replacer has null Regex pointer");
    }
    if (rh.me._search(s, start, end))
    {
      int rmn = rh.me.matchedTo();
      if (rh.me.charsMatched() == 0 && !isSpecial(rh.me.getReplaceRule()))
      {
        apply1(rh.me);
        rmn++;
      }
      apply(rh.me);
      if (!first)
      {
        for (int i = rmn; !want_more_text && rh.me._search(s, i, end); i = rmn)
        {
          rmn = rh.me.matchedTo();
          if (rh.me.charsMatched() == 0)
          {
            if (!isSpecial(rh.me.getReplaceRule()))
            {
              apply1(rh.me);
            }
            rmn++;
          }
          apply(rh.me);
        }
      }
      ret = finish();
      ret = ret == null ? s : ret;
    }
    return ret;
  }

  StringBufferLike sb = null;

  StringLike src = null;

  int pos = 0;

  /**
   * This method allows you to apply the results of several matches in a
   * sequence to modify a String of text. Each call in the sequence must operate
   * on the same piece of text and the matchedFrom() of each RegRes given to
   * this method must be greater in value than the preceeding RegRes's
   * matchedTo() value.
   */
  public void apply(RegRes r, ReplaceRule rp)
  {
    if (rp == null || (rp.next == null && rp instanceof AmpersandRule))
    {
      return;
    }
    if (r.didMatch())
    {
      if (src == null)
      {
        src = r.getStringLike();
      }
      if (sb == null)
      {
        sb = new StringBufferLike(src.newStringBufferLike());
      }
      int rmf = r.matchedFrom();
      for (int ii = pos; ii < rmf; ii++)
      {
        sb.append(src.charAt(ii));
      }

      for (ReplaceRule x = rp; x != null; x = x.next)
      {
        x.apply(sb, r);
        if (x instanceof SpecialRule)
        {
          if (x instanceof WantMoreTextReplaceRule && want_more_text_enable)
          {
            want_more_text = true;
          }
          else if (x instanceof PushRule)
          {
            RegHolder rh2 = new RegHolder();
            rh2.me = ((PushRule) x).NewRule;
            rh2.prev = rh;
            rh = rh2;
          }
          else if (x instanceof PopRule)
          {
            if (rh.prev != null)
            {
              rh = rh.prev;
            }
          }
          else if (x instanceof ChangeRule)
          {
            rh.me = ((ChangeRule) x).NewRule;
          }
        }
      }
      if (!want_more_text)
      {
        pos = r.matchedTo();
      }
    }
  }

  boolean want_more_text = false, want_more_text_enable = false;

  public boolean WantMoreText()
  {
    return want_more_text;
  }

  /**
   * Another form of apply, it is the same as apply(r,r.getReplaceRule()).
   */
  public void apply(Regex r)
  {
    apply(r, r.getReplaceRule());
  }

  /**
   * This finishes the replacement, appending the right() part of the last
   * RegRes given to substitute(RegRes). After this method is called, the
   * Replace object is reset to perform another substitution. If no RegRes
   * objects with a true didMatch are applied, this returns null.
   */
  public StringLike finish()
  {
    if (src == null)
    {
      return null;
    }
    // sb.append(src.substring(pos,src.length()));
    int s_end = src.length();
    for (int ii = pos; ii < s_end; ii++)
    {
      sb.append(src.charAt(ii));
    }
    src = null;
    lastMatchedTo = pos;
    pos = 0;
    StringLike retstr = sb.toStringLike();
    sb = null;
    return retstr;
  }

  int lastMatchedTo = 0;

  public Object clone()
  {
    Replacer r = new Replacer();
    r.first = first;
    r.src = src;
    r.sb = sb;
    r.pos = pos;
    r.lastMatchedTo = lastMatchedTo;
    r.want_more_text = want_more_text;
    r.want_more_text_enable = want_more_text_enable;
    r.rh.me = rh.me;
    r.rh.prev = rh.prev;
    return r;
  }

  public int lastMatchedTo()
  {
    return lastMatchedTo;
  }

  public Regex getRegex()
  {
    return rh.me;
  }

  public void setSource(StringLike sl)
  {
    src = sl;
  }

  public void setBuffer(StringBufferLike sbl)
  {
    sb = sbl;
  }

  public void setPos(int pos)
  {
    this.pos = pos;
  }
}
