##
# String
#
# ISO 15.2.10
class String
  # ISO 15.2.10.3
  include Comparable

  ##
  # Calls the given block for each line
  # and pass the respective line.
  #
  # ISO 15.2.10.5.15
  def each_line(rs = "\n", &block)
    return to_enum(:each_line, rs, &block) unless block
    return block.call(self) if rs.nil?
    rs.__to_str
    offset = 0
    rs_len = rs.length
    this = dup
    while pos = this.index(rs, offset)
      block.call(this[offset, pos + rs_len - offset])
      offset = pos + rs_len
    end
    block.call(this[offset, this.size - offset]) if this.size > offset
    self
  end

  # private method for gsub/sub
  def __sub_replace(pre, m, post)
    s = ""
    i = 0
    while j = index("\\", i)
      break if j == length-1
      t = case self[j+1]
          when "\\"
            "\\"
          when "`"
            pre
          when "&", "0"
            m
          when "'"
            post
          when "1", "2", "3", "4", "5", "6", "7", "8", "9"
            ""
          else
            self[j, 2]
          end
      s += self[i, j-i] + t
      i = j + 2
    end
    s + self[i, length-i]
  end

  ##
  # Replace all matches of +pattern+ with +replacement+.
  # Call block (if given) for each match and replace
  # +pattern+ with the value of the block. Return the
  # final value.
  #
  # ISO 15.2.10.5.18
  def gsub(*args, &block)
    return to_enum(:gsub, *args) if args.length == 1 && !block
    raise ArgumentError, "wrong number of arguments" unless (1..2).include?(args.length)

    pattern, replace = *args
    plen = pattern.length
    if args.length == 2 && block
      block = nil
    end
    if !replace.nil? || !block
      replace.__to_str
    end
    offset = 0
    result = []
    while found = index(pattern, offset)
      result << self[offset, found - offset]
      offset = found + plen
      result << if block
        block.call(pattern).to_s
      else
        replace.__sub_replace(self[0, found], pattern, self[offset..-1] || "")
      end
      if plen == 0
        result << self[offset, 1]
        offset += 1
      end
    end
    result << self[offset..-1] if offset < length
    result.join
  end

  ##
  # Replace all matches of +pattern+ with +replacement+.
  # Call block (if given) for each match and replace
  # +pattern+ with the value of the block. Modify
  # +self+ with the final value.
  #
  # ISO 15.2.10.5.19
  def gsub!(*args, &block)
    raise FrozenError, "can't modify frozen String" if frozen?
    return to_enum(:gsub!, *args) if args.length == 1 && !block
    str = self.gsub(*args, &block)
    return nil unless self.index(args[0])
    self.replace(str)
  end

#  ##
#  # Calls the given block for each match of +pattern+
#  # If no block is given return an array with all
#  # matches of +pattern+.
#  #
#  # ISO 15.2.10.5.32
#  def scan(pattern, &block)
#    # TODO: String#scan is not implemented yet
#  end

  ##
  # Replace only the first match of +pattern+ with
  # +replacement+. Call block (if given) for each
  # match and replace +pattern+ with the value of the
  # block. Return the final value.
  #
  # ISO 15.2.10.5.36
  def sub(*args, &block)
    unless (1..2).include?(args.length)
      raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2)"
    end

    pattern, replace = *args
    pattern.__to_str
    if args.length == 2 && block
      block = nil
    end
    unless block
      replace.__to_str
    end
    result = []
    this = dup
    found = index(pattern)
    return this unless found
    result << this[0, found]
    offset = found + pattern.length
    result << if block
      block.call(pattern).to_s
    else
      replace.__sub_replace(this[0, found], pattern, this[offset..-1] || "")
    end
    result << this[offset..-1] if offset < length
    result.join
  end

  ##
  # Replace only the first match of +pattern+ with
  # +replacement+. Call block (if given) for each
  # match and replace +pattern+ with the value of the
  # block. Modify +self+ with the final value.
  #
  # ISO 15.2.10.5.37
  def sub!(*args, &block)
    raise FrozenError, "can't modify frozen String" if frozen?
    str = self.sub(*args, &block)
    return nil unless self.index(args[0])
    self.replace(str)
  end

  ##
  # Call the given block for each byte of +self+.
  def each_byte(&block)
    return to_enum(:each_byte, &block) unless block
    bytes = self.bytes
    pos = 0
    while pos < bytes.size
      block.call(bytes[pos])
      pos += 1
    end
    self
  end

  ##
  # Modify +self+ by replacing the content of +self+.
  # The portion of the string affected is determined using the same criteria as +String#[]+.
  def []=(*args)
    anum = args.size
    if anum == 2
      pos, value = args[0], args[1].__to_str
      case pos
      when String
        posnum = self.index(pos)
        if posnum
          b = self[0, posnum]
          a = self[(posnum + pos.length)..-1]
          self.replace([b, value, a].join(''))
        else
          raise IndexError, "string not matched"
        end
      when Range
        head = pos.begin
        tail = pos.end
        tail += self.length if tail < 0
        unless pos.exclude_end?
          tail += 1
        end
        return self[head, tail-head]=value
      else
        pos = pos.__to_int
        pos += self.length if pos < 0
        if pos < 0 || pos > self.length
          raise IndexError, "index #{args[0]} out of string"
        end
        b = self[0, pos]
        a = self[pos + 1..-1]
        self.replace([b, value, a].join(''))
      end
      return value
    elsif anum == 3
      pos, len, value = args[0].__to_int, args[1].__to_int, args[2].__to_str
      pos += self.length if pos < 0
      if pos < 0 || pos > self.length
        raise IndexError, "index #{args[0]} out of string"
      end
      if len < 0
        raise IndexError, "negative length #{len}"
      end
      b = self[0, pos]
      a = self[pos + len..-1]
      self.replace([b, value, a].join(''))
      return value
    else
      raise ArgumentError, "wrong number of arguments (#{anum} for 2..3)"
    end
  end

  # those two methods requires Regexp that is optional in mruby
  ##
  # ISO 15.2.10.5.3
  #def =~(re)
  # re =~ self
  #end

  ##
  # ISO 15.2.10.5.27
  #def match(re, &block)
  #  re.match(self, &block)
  #end
end
