#
# bitclust/nameutils.rb
#
# Copyright (c) 2006-2007 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the Ruby License.
#

require 'bitclust/compat'

module BitClust

  module NameUtils

    module_function

    LIBNAME_RE     = %r<[\w\-]+(/[\w\-]+)*>
    CONST_RE       = /[A-Z]\w*/
    CONST_PATH_RE  = /#{CONST_RE}(?:::#{CONST_RE})*/
    CLASS_NAME_RE  = /(?:#{CONST_RE}|fatal)/
    CLASS_PATH_RE  = /(?:#{CONST_PATH_RE}|fatal)/
    METHOD_NAME_RE = /\w+[?!=]?|===|==|=~|<=>|<=|>=|!=|!|\[\]=|\[\]|\*\*|>>|<<|\+@|\-@|[~+\-*\/%&|^<>`]/
    TYPEMARK_RE    = /(?:\.|\#|\.\#|::|\$)/
    METHOD_SPEC_RE = /#{CLASS_PATH_RE}#{TYPEMARK_RE}#{METHOD_NAME_RE}/
    GVAR_RE        = /\$(?:\w+|-.|\S)/

    def libname?(str)
      (/\A#{LIBNAME_RE}\z/o =~ str) ? true : false
    end

    def libname2id(name)
      name.split('/').map {|ent| encodename_url(ent) }.join('.')
    end

    def libid2name(id)
      id.split('.').map {|ent| decodename_url(ent) }.join('/')
    end

    def classname?(str)
      (/\A#{CLASS_PATH_RE}\z/o =~ str) ? true : false
    end

    def classname2id(name)
      name.gsub(/::/, '=')
    end

    def classid2name(id)
      id.gsub(/=/, '::')
    end

    def method_spec?(str)
      (/\A#{METHOD_SPEC_RE}\z/o =~ str) ? true : false
    end

    def split_method_spec(spec)
      case spec
      when /\AKernel\$/
        return 'Kernel', '$', $'
      else
        m = /\A(#{CLASS_PATH_RE})(#{TYPEMARK_RE})(#{METHOD_NAME_RE})\z/o.match(spec) or
            raise ArgumentError, "wrong method spec: #{spec.inspect}"
        return *m.captures
      end
    end

    def methodid2specstring(id)
      c, t, m, lib = *split_method_id(id)
      classid2name(c) + typechar2mark(t) + decodename_url(m)
    end

    def methodid2specparts(id)
      c, t, m, lib = *split_method_id(id)
      return classid2name(c), typechar2mark(t), decodename_url(m), libid2name(lib)
    end

    def methodid2libid(id)
      c, t, m, lib = *split_method_id(id)
      lib
    end

    def methodid2classid(id)
      c, t, m, lib = *split_method_id(id)
      c
    end

    def methodid2typechar(id)
      c, t, m, lib = *split_method_id(id)
      t
    end

    def methodid2typename(id)
      c, t, m, lib = *split_method_id(id)
      typechar2name(t)
    end

    def methodid2typemark(id)
      c, t, m, lib = *split_method_id(id)
      typechar2mark(t)
    end

    def methodid2mname(id)
      c, t, m, lib = *split_method_id(id)
      decodename_url(m)
    end

    def gvarname?(str)
      GVAR_RE =~ str ? true : false
    end

    MID = /\A#{METHOD_NAME_RE}\z/

    def methodname?(str)
      (MID =~ str) ? true : false
    end

    def build_method_id(libid, cid, t, name)
      "#{cid}/#{typename2char(t)}.#{encodename_url(name)}.#{libid}"
    end

    # private module function
    def split_method_id(id)
      return *id.split(%r<[/\.]>, 4)
    end

    NAME_TO_MARK = {
      :singleton_method => '.',
      :instance_method  => '#',
      :module_function  => '.#',
      :constant         => '::',
      :special_variable => '$'
    }

    MARK_TO_NAME = NAME_TO_MARK.invert

    def typename?(n)
      NAME_TO_MARK.key?(n)
    end

    def typename2mark(name)
      NAME_TO_MARK[name] or
          raise "must not happen: #{name.inspect}"
    end

    def typemark2name(mark)
      MARK_TO_NAME[mark] or
          raise "must not happen: #{mark.inspect}"
    end

    NAME_TO_CHAR = {
      :singleton_method => 's',
      :instance_method  => 'i',
      :module_function  => 'm',
      :constant         => 'c',
      :special_variable => 'v'
    }

    CHAR_TO_NAME = NAME_TO_CHAR.invert

    def typechar?(c)
      CHAR_TO_NAME.key?(c)
    end

    def typename2char(name)
      NAME_TO_CHAR[name] or
          raise "must not happen: #{name.inspect}"
    end

    def typechar2name(char)
      CHAR_TO_NAME[char] or
          raise "must not happen: #{char.inspect}"
    end

    MARK_TO_CHAR = {
      '.'  => 's',
      '#'  => 'i',
      '.#' => 'm',
      '::' => 'c',
      '$'  => 'v'
    }

    CHAR_TO_MARK = MARK_TO_CHAR.invert

    def typemark?(m)
      MARK_TO_CHAR.key?(m)
    end

    def typechar2mark(char)
      CHAR_TO_MARK[char] or
          raise "must not happen: #{char.inspect}"
    end

    def typemark2char(mark)
      MARK_TO_CHAR[mark] or
          raise "must not happen: #{mark.inspect}"
    end

    def functionname?(n)
      /\A\w+\z/ =~ n ? true : false
    end

    # string -> case-sensitive ID
    def encodename_url(str)
      str.gsub(/[^A-Za-z0-9_]/n) {|ch| sprintf('=%02x', ch[0].ord) }
    end

    # case-sensitive ID -> string
    def decodename_url(str)
      str.gsub(/=[\da-h]{2}/ni) {|s| s[1,2].hex.chr }
    end

    # case-sensitive ID -> encoded string (encode only [A-Z])
    def encodeid(str)
      str.gsub(/[A-Z]/n) {|ch| "-#{ch}" }.downcase
    end

    # encoded string -> case-sensitive ID (decode only [A-Z])
    def decodeid(str)
      str.gsub(/-[a-z]/ni) {|s| s[1,1].upcase }
    end

    def encodename_fs(str)
      str.gsub(/[^a-z0-9_]/n) {|ch|
        (/[A-Z]/n =~ ch) ? "-#{ch}" : sprintf('=%02x', ch[0].ord)
      }.downcase
    end

    def decodename_fs(str)
      str.gsub(/=[\da-h]{2}|-[a-z]/ni) {|s|
        (/\A-/ =~ s) ? s[1,1].upcase : s[1,2].hex.chr
      }
    end

  end

end
