/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.util;

import java.io.*;

/**
 * CharSegment is a section of a character buffer
 */
public class CharSegment implements CharSequence {
  protected char []_buffer;
  protected int _offset;
  protected int _length;

  public CharSegment()
  {
  }
  
  /**
   * Constructs a char segment based on a char array.
   */
  public CharSegment(char []buffer, int offset, int length)
  {
    _buffer = buffer;
    _offset = offset;
    _length = length;
  }

  /**
   * Sets the char segment to a new buffer triple.
   */
  public void init(char []buffer, int offset, int length)
  {
    _buffer = buffer;
    _offset = offset;
    _length = length;
  }

  /**
   * Returns the character count of the buffer's contents.
   */
  public int length()
  {
    return _length;
  }

  /**
   * Returns the buffer length
   */
  public int getLength()
  {
    return _length;
  }

  public int getOffset()
  {
    return _offset;
  }

  /**
   * Returns the char at the specified offset.
   */
  public char charAt(int i)
  {
    if (i < 0 || _length <= i)
      throw new IndexOutOfBoundsException();

    return _buffer[i + _offset];
  }

  /**
   * Returns the last character of the buffer
   *
   * @throws IndexOutOfBoundsException for an empty buffer
   */
  public char getLastChar()
  {
    if (_length == 0)
      throw new IndexOutOfBoundsException();

    return _buffer[_offset + _length - 1];
  }
  
  /**
   * Returns the buffer's char array.
   */
  public char []getBuffer()
  {
    return _buffer;
  }

  /**
   * Copies characters to the destination buffer.
   */
  public void getChars(int srcBegin, int srcEnd, char []dst, int dstBegin)
  {
    srcBegin += _offset;
    srcEnd += _offset;

    char []buffer = _buffer;
    while (srcBegin < srcEnd)
      dst[dstBegin++] = buffer[srcBegin++];
  }

  /**
   * Returns a substring
   */
  public String substring(int start)
  {
    if (_length < start || start < 0)
      throw new StringIndexOutOfBoundsException();

    return new String(_buffer, _offset + start, _length - start);
  }

  /**
   * Returns a substring
   */
  public String substring(int start, int end)
  {
    if (_length < start || start < 0 || end < start)
      throw new StringIndexOutOfBoundsException();

    return new String(_buffer, _offset + start, end - start);
  }

  /**
   * Returns a subsequence
   */
  public CharSequence subSequence(int start, int end)
  {
    if (_length < start || start < 0 || end < start)
      throw new StringIndexOutOfBoundsException();

    return new String(_buffer, _offset + start, end - start);
  }

  /**
   * Returns the index of a character in the CharSegment.
   */
  public int indexOf(char ch)
  {
    return indexOf(ch, 0);
  }
  
  /**
   * Returns the index of a character in the CharSegment starting
   * from an offset.
   */
  public final int indexOf(char ch, int start)
  {
    if (start < 0)
      start = 0;

    int end = _offset + _length;
    start += _offset;

    char []buffer = _buffer;
    for (; start < end; start++) {
      if (buffer[start] == ch)
	return start - _offset;
    }

    return -1;
  }

  /**
   * Returns the last index of a character in the CharSegment.
   */
  public final int lastIndexOf(char ch)
  {
    return lastIndexOf(ch, _length - 1);
  }
  /**
   * Returns the last index of a character in the CharSegment starting
   * from an offset.
   */
  
  public final int lastIndexOf(char ch, int start)
  {
    if (_length <= start)
      start = _length - 1;

    char []buffer = _buffer;
    int offset = _offset;
    for (; start >= 0; start--) {
      if (buffer[start + offset] == ch)
	return start;
    }

    return -1;
  }

  public int indexOf(String s) 
  {
    return indexOf(s, 0);
  }

  public int indexOf(String s, int start) 
  {
    int slen = s.length();

    if (start < 0)
      start = 0;

    int end = _offset + _length - slen + 1;
    start += _offset;

    char []buffer = _buffer;
    for (; start < end; start++) {
      int i = 0;
      for (; i < slen; i++) {
        if (buffer[start+i] != s.charAt(i))
          break;
      }
      if (i == slen)
        return start - _offset;
    }

    return -1;
  }

  /**
   * Returns the buffer's hash code
   */
  public int hashCode()
  {
    int hash = 0;
    char []buffer = _buffer;
    int begin = _offset;
    int end = begin + _length;

    for (; begin < end; begin++)
      hash = 65521 * hash + buffer[begin] * 251 + 1021;

    return hash;
  }

  /* 
   * Predicate testing if two char segments are equal
   */
  public final boolean equals(Object a)
  {
    if (this == a)
      return true;
    
    else if (a instanceof CharSegment) {
      CharSegment cb = (CharSegment) a;
      
      int length = _length;
      if (length != cb._length)
	return false;

      char []buffer = _buffer;
      char []aBuffer = cb._buffer;

      int offset = _offset;
      int aOffset = cb._offset;
      
      for (int i = length - 1; i >= 0; i--)
	if (buffer[offset + i] != aBuffer[aOffset + i])
	  return false;
      
      return true;
    }
    else if (a instanceof CharSequence) {
      CharSequence seq = (CharSequence) a;

      int length = seq.length();

      if (_length != length)
	return false;

      for (int i = length - 1; i >= 0; i--) {
	if (_buffer[i] != seq.charAt(i))
	  return false;
      }

      return true;
    }
    else
      return false;
  }

  /**
   * Returns true if the two char segments are equal.
   */
  public boolean equals(CharSegment cb)
  {
    int length = _length;
    if (length != cb._length)
      return false;

    char []buffer = _buffer;
    char []aBuffer = cb._buffer;

    int offset = _offset;
    int aOffset = cb._offset;
      
    for (int i = length - 1; i >= 0; i--)
      if (buffer[offset + i] != aBuffer[aOffset + i])
        return false;
      
    return true;
  }

  /**
   * Returns true if the CharSegment equals the char array.
   */
  public final boolean equals(char []cb, int length)
  {
    if (length != _length)
      return false;

    int offset = _offset;
    char []buffer = _buffer;
    
    for (int i = _length - 1; i >= 0; i--)
      if (buffer[offset + i] != cb[i])
        return false;
    
    return true;
  }

  /**
   * Returns true if the CharSegment equals the string.
   */
  public final boolean equalsIgnoreCase(String a)
  {
    int len = a.length();
    if (_length != len)
      return false;

    int offset = _offset;
    char []buffer = _buffer;
    
    for (int i = 0; i < len; i++) {
      char ca = buffer[offset + i];
      char cb = a.charAt(i);

      if (ca == cb) {
      }

      else if (Character.toLowerCase(ca) != Character.toLowerCase(cb))
	return false;
    }
    
    return true;
  }

  /**
   * Returns true if the two CharSegments are equivalent ignoring the case.
   */
  public final boolean equalsIgnoreCase(CharSegment b)
  {
    int length = _length;
    if (length != b._length)
      return false;

    char []buffer = _buffer;
    char []bBuffer = b._buffer;

    int offset = _offset;
    int bOffset = b._offset;
    
    for (int i = length - 1; i >= 0; i--) {
      char ca = buffer[offset + i];
      char cb = bBuffer[bOffset + i];

      if (ca != cb &&
          Character.toLowerCase(ca) != Character.toLowerCase(cb))
	return false;
    }
    return true;
  }

  /* 
   * XXX: unsure is this is legit
   */
  public final boolean matches(Object a)
  {
    if (a instanceof CharSegment) {
      CharSegment cb = (CharSegment) a;
      if (_length != cb._length)
	return false;

      int offset = _offset;
      int bOffset = cb._offset;

      char []buffer = _buffer;
      char []cbBuffer = cb._buffer;
      
      for (int i = _length - 1; i >= 0; i--)
	if (buffer[offset + i] != cbBuffer[bOffset + i])
	  return false;
      
      return true;
    } else if (a instanceof String) {
      String sa = (String) a;

      if (_length != sa.length())
	return false;

      int offset = _offset;
      char []buffer = _buffer;
      
      for (int i = _length - 1; i >= 0; i--)
	if (buffer[i + offset] != sa.charAt(i))
	  return false;
      
      return true;
    } else
      return false;
  }

  /**
   * Returns true if the charSegment matches the string.
   */
  public boolean matches(String sa)
  {
    if (_length != sa.length())
      return false;

    char []buffer = _buffer;
    int offset = _offset;
    
    for (int i = _length - 1; i >= 0; i--)
      if (_buffer[_offset + i] != sa.charAt(i))
        return false;
    
    return true;
  }

  /**
   * Returns true if the CharSegment matches the string, ignoring the case.
   */
  public boolean matchesIgnoreCase(String sa)
  {
    if (_length != sa.length())
      return false;

    char []buffer = _buffer;
    int offset = _offset;
    for (int i = _length - 1; i >= 0; i--) {
      char ca = buffer[offset + i];
      char cb = sa.charAt(i);
      
      if (ca != cb && Character.toLowerCase(ca) != Character.toLowerCase(cb))
        return false;
    }
    
    return true;
  }

  public boolean regionMatches(int off1, CharSegment buf, int off2, int len)
  {
    if (_length < off1 + len || buf._length < off2 + len)
      return false;

    char []buffer = _buffer;
    char []bufBuffer = buf._buffer;
    for (int i = len - 1; i >= 0; i--) {
      if (buffer[off1 + i] != bufBuffer[off2 + i])
	return false;
    }

    return true;
  }

  public boolean regionMatches(int off1, String buf, int off2, int len)
  {
    if (_length < off1 + len || buf.length() < off2 + len)
      return false;

    char []buffer = _buffer;
    for (int i = 0; i < len; i++) {
      if (buffer[off1 + i] != buf.charAt(off2 + i))
	return false;
    }

    return true;
  }

  public boolean regionMatchesIgnoreCase(int off1, CharSegment buf, 
					 int off2, int len)
  {
    if (_length < off1 + len || buf._length < off2 + len)
      return false;

    char []buffer = _buffer;
    char []bufBuffer = buf._buffer;
    for (int i = len -1; i >= 0; i--) {
      if (Character.toLowerCase(buffer[off1 + i]) != 
	  Character.toLowerCase(bufBuffer[off2 + i]))
	return false;
    }

    return true;
  }

  /**
   * Returns true if the CharSegment starts with the string.
   */
  public boolean startsWith(String string)
  {
    if (string == null)
      return false;

    int strlen = string.length();
    if (_length < strlen)
      return false;

    char []buffer = _buffer;
    int offset = _offset;

    while (--strlen >= 0) {
      if (buffer[offset + strlen] != string.charAt(strlen))
        return false;
    }

    return true;
  }

  /**
   * Returns true if the CharSegment ends with the string.
   */
  public boolean endsWith(String string)
  {
    if (string == null)
      return false;

    int strlen = string.length();
    if (_length < strlen)
      return false;

    char []buffer = _buffer;
    int offset = _offset + _length - strlen;

    while (--strlen >= 0) {
      if (buffer[offset + strlen] != string.charAt(strlen))
        return false;
    }

    return true;
  }

  /**
   * Returns true if the CharSegment ends with the char segment.
   */
  public boolean endsWith(CharSegment cb)
  {
    if (cb == null)
      return false;

    int strlen = cb._length;
    if (_length < strlen)
      return false;

    char []buffer = _buffer;
    int offset = _offset + _length - strlen;

    char []cbBuffer = cb._buffer;
    int cbOffset = cb._offset;

    while (--strlen >= 0) {
      if (buffer[offset + strlen] != cbBuffer[cbOffset + strlen])
        return false;
    }

    return true;
  }

  /**
   * Converts the contents of the segment to lower case.
   */
  public CharSegment toLowerCase()
  {
    char []buffer = _buffer;
    int len = _length;
    int offset = _offset;
 
    while (--len >= 0)
      buffer[offset + len] = Character.toLowerCase(buffer[offset + len]);
 
    return this;
  }

  /**
   * String representation of the buffer.
   */
  public String toString()
  {
    return new String(_buffer, _offset, _length);
  }
}
