# -*- encoding: utf-8 -*-

# String objects holds a sequence of bytes, typically representing
# characters. Strings may be constructed by using methods like
# {String.new} or literals, like the following:
#
#     String.new("foo")     # => "foo"
#     "bar"                 # => "bar"
#
# Strings in Opal are immutable; which means that their contents cannot
# be changed. This means that a lot of methods like `strip!` are not
# present, and will yield a `NoMethodError`. Thier immutable
# counterparts are still available, which typically just return a new
# string.
#
# Implementation details
# ----------------------
#
# Ruby strings are toll-free bridged to native javascript strings,
# meaning that anywhere that a ruby string is required, a normal
# javascript string may be passed. This dramatically improves the
# performance of Opal due to a lower overhead in allocating strings as
# well as the ability to used functions of the String prototype to
# perform many of the core ruby methods.
#
# It is due to this limitation that strings are immutable. Javascript
# strings are immutable too, which limits what can be done with them in
# regards to Ruby methods.
#
# Ruby compatibility
# ------------------
#
# As discussed, {String} instances are immutable so they do not
# implement any of the self mutable methods found in the ruby core
# library. Most of these methods have their relative immutable
# implementations, or alternative methods to take their place.
#
# Custom subclasses of {String} can be used, and are constructed in the
# {.new} method. To due opals internals, a regular string is constructed
# using `new String(string_content)`, and its class and method table
# simply pointed at the custom subclass. As these custom subclasses are
# simply javascript strings as well, they are also limited to being
# immutable. This is because they share the same internal structre as
# regular {String} instances.
#
# String instances will never actually have their {.allocate} methods
# called. Due to the way opal bridges strings to javascript, when a new
# string is constructed, its value must be know. This is not possible in
# `allocate` as the value is not passed. Therefore the creation of
# strings (including subclasses) is done in {.new} where the string
# value is passed as an argument.
#
# Finally, strings do not currently include the `Comparable` module, as
# it is not yet implemented. The main methods used by {String} from this
# module are implemented directly as String methods. When `Comparable`
# is implemented, these methods will be moved back to the module.
class String

  def self.new(str = "")
    `var result = new String(str);
    result.$klass = self;
    result.$m = self.$m_tbl;
    return result;`
  end

  # Copy - returns a new string containing `count` copies of the receiver.
  #
  # @example
  #
  #     'Ho! ' * 3
  #     # => 'Ho! Ho! Ho! '
  #
  # @param [Numeric] count number of copies
  # @return [String]
  def *(count)
    `var result = [];

    for (var i = 0; i < count; i++) {
      result.push(self);
    }

    return result.join('');`
  end

  # Concatenation - Returns a new string containing `other` concatenated onto
  # `self`.
  #
  # @example
  #
  #     'Hello from ' + self.to_s
  #     # => 'Hello from main'
  #
  # @param [String] other string to concatenate
  # @return [String]
  def +(other)
    `return self + other;`
  end

  # Returns a copy of `self` with the first character converted to uppercase and
  # the remaining to lowercase.
  #
  # @example
  #
  #     'hello'.capitalize
  #     # => 'Hello'
  #     'HELLO'.capitalize
  #     # => 'Hello'
  #     '123ABC'.capitalize
  #     # => '123abc'
  #
  # @return [String]
  def capitalize
    `return self.charAt(0).toUpperCase() + self.substr(1).toLowerCase();`
  end

  # Returns a copy of `self` with all uppercase letters replaces with their
  # lowercase counterparts.
  #
  # @example
  #
  #     'hELLo'.downcase
  #     # => 'hello'
  #
  # @return [String]
  def downcase
    `return self.toLowerCase();`
  end

  def to_s
    self
  end

  # Returns a printable version of `self`, surrounded with quotation marks, with
  # all special characters escaped.
  #
  # @example
  #
  #     str = "hello"
  #     str.inspect
  #     # => "\"hello\""
  #
  # @return [String]
  def inspect
    `/* borrowed from json2.js, see file for license */
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

    escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

    meta = {
      '\b': '\\b',
      '\t': '\\t',
      '\n': '\\n',
      '\f': '\\f',
      '\r': '\\r',
      '"' : '\\"',
      '\\': '\\\\'
    };

    escapable.lastIndex = 0;

    return escapable.test(self) ? '"' + self.replace(escapable, function (a) {
      var c = meta[a];
      return typeof c === 'string' ? c :
        '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
      }) + '"' : '"' + self + '"';`
  end

  # Returns the number of characters in `self`.
  #
  # @return [Numeric]
  def length
    `return self.length;`
  end

  # Returns the corresponding symbol for the receiver.
  #
  # @example
  #
  #     "koala".to_sym      # => :Koala
  #     'cat'.to_sym        # => :cat
  #     '@cat'.to_sym       # => :@cat
  #
  # This can also be used to create symbols that cannot be created using the
  # :xxxx notation.
  #
  # @return [Symbol]
  def to_sym
    `return VM.Y(self);`
  end

  def intern
    `return VM.Y(self);`
  end

  # Returns a new string with the characters from `self` in reverse order.
  #
  # @example
  #
  #     'stressed'.reverse
  #     # => 'desserts'
  #
  # @return [String]
  def reverse
    `return self.split('').reverse().join('');`
  end

  def sub(pattern)
    `return self.replace(pattern, function(str) {
      return #{yield `str`};
    });`
  end

  def gsub(pattern)
    `var r = pattern.toString();
    r = r.substr(1, r.lastIndexOf('/') - 1);
    r = new RegExp(r, 'g');
    return self.replace(pattern, function(str) {
      return #{yield `str`};
    });`
  end

  def slice(start, finish = nil)
    `return self.substr(start, finish);`
  end

  def split(split)
    `return self.split(split);`
  end

  # Comparison - returns -1 if `other` is greater than, 0 if `other` is equal to
  # or 1 if `other` is less than `self. Returns nil if `other` is not a string.
  #
  # @example
  #
  #     'abcdef' <=> 'abcde'        # => 1
  #     'abcdef' <=> 'abcdef'       # => 0
  #     'abcdef' <=> 'abcdefg'      # => -1
  #     'abcdef' <=> 'ABCDEF'       # => 1
  #
  # @param [String] other string to compare
  # @return [-1, 0, 1, nil] result
  def <=>(other)
    `if (typeof other != 'string') return nil;
    else if (self > other) return 1;
    else if (self < other) return -1;
    return 0;`
  end

  # Equality - if other is not a string, returns false. Otherwise, returns true
  # if self <=> other returns zero.
  #
  # @param [String] other string to compare
  # @return [true, false]
  def ==(other)
    `return self.valueOf() === other.valueOf() ? Qtrue : Qfalse;`
  end

  # Match - if obj is a Regexp, then uses it to match against self, returning
  # nil if there is no match, or the index of the match location otherwise. If
  # obj is not a regexp, then it calls =~ on it, using the receiver as an
  # argument
  #
  # **TODO** passing a non regexp is not currently supported
  #
  # @param [Regexp, Objec] obj
  # @return [Numeric, nil]
  def =~(obj)
    obj.match self
    nil
  end

  # Case-insensitive version of {#<=>}
  #
  # @example
  #
  #     'abcdef'.casecmp 'abcde'        # => 1
  #     'aBcDeF'.casecmp 'abcdef'       # => 0
  #     'abcdef'.casecmp 'aBcdEFg'      # => -1
  #
  # @param [String] other string to compare
  # @return [-1, 0, 1, nil]
  def casecmp(other)
    `if (typeof other != 'string') return nil;
    var a = self.toLowerCase(), b = other.toLowerCase();
    if (a > b) return 1;
    else if (a < b) return -1;
    return 0;`
  end

  # Returns `true` if self has a length of zero.
  #
  # @example
  #
  #     'hello'.empty?
  #     # => false
  #     ''.empty?
  #     # => true
  #
  # @return [true, false]
  def empty?
    `return self.length == 0 ? Qtrue : Qfalse;`
  end

  # Returns true is self ends with the given suffix.
  #
  # @example
  #
  #     'hello'.end_with? 'lo'
  #     # => true
  #
  # @param [String] suffix the suffix to check
  # @return [true, false]
  def end_with?(suffix)
    `if (self.lastIndexOf(suffix) == self.length - suffix.length) {
      return Qtrue;
    }

    return Qfalse;`
  end

  # Two strings are equal if they have the same length and content.
  #
  # @param [String] other string to compare
  # @return [true, false]
  def eql?(other)
    `return self == other ? Qtrue : Qfalse;`
  end

  # Returns true if self contains the given string `other`.
  #
  # @example
  #
  #     'hello'.include? 'lo'     # => true
  #     'hello'.include? 'ol'     # => false
  #
  # @param [String] other string to check for
  # @return [true, false]
  def include?(other)
    `return self.indexOf(other) == -1 ? Qfalse : Qtrue;`
  end

  # Returns the index of the first occurance of the given `substr` or pattern in
  # self. Returns `nil` if not found. If the second param is present then it
  # specifies the index of self to begin searching.
  #
  # **TODO** regexp and offsets not yet implemented.
  #
  # @example
  #
  #     'hello'.index 'e'         # => 1
  #     'hello'.index 'lo'        # => 3
  #     'hello'.index 'a'         # => nil
  #
  # @param [String] substr string to check for
  # @return [Numeric, nil]
  def index(substr)
    `var res = self.indexOf(substr);

    return res == -1 ? nil : res;`
  end

  # Returns a copy of self with leading whitespace removed.
  #
  # @example
  #
  #     '   hello   '.lstrip
  #     # => 'hello   '
  #     'hello'.lstrip
  #     # => 'hello'
  #
  # @return [String]
  def lstrip
    `return self.replace(/^\s*/, '');`
  end
end