module AE


  class Console


    module Introspection

      # Finds an object from an identifier string.
      # @param [String] string is a ruby identifier or nested identifier
      # @param [Object] context is the object on whose binding evaluation is done
      # @returns [Object] the object referenced by the identifier string
      def self.get_object_from_string(string, context=MAIN)
        context = MAIN if context.nil?
        return parse_identifier_string(string, context)
      end

      unless defined?(self::MAIN)
        MAIN = Object # This did not work: eval("self", TOPLEVEL_BINDING)
        IDENTIFIER_CHAR = /[^\!\"\'\`\@\%\|\&\/\(\)\[\]\{\}\,\;\.\:\?\<\>\=\+\-\*\/\#\~\\]/ # TODO: ^ ?
        IDENTIFIER = /^#{IDENTIFIER_CHAR}+/
        CONSTANT_SEPARATOR = /^\:\:/
        METHOD_SEPARATOR = /^\.|#{CONSTANT_SEPARATOR}/
        CONSTANT = (RUBY_VERSION.to_i < 2) ? /^[A-Z]#{IDENTIFIER_CHAR}*/ : eval("/^\\p{Lu}#{IDENTIFIER_CHAR}*/") # TODO: * instead of + ?
        GLOBAL_VARIABLE = /^\$#{IDENTIFIER_CHAR}+/
        CLASS_VARIABLE = /^@@#{IDENTIFIER_CHAR}+/
        INSTANCE_VARIABLE = /^@#{IDENTIFIER_CHAR}+/
        SQUARE_BRACKET_START = /^\[/
        SQUARE_BRACKET_END = /^\]/
        SQUARE_BRACKET_NESTED = (RUBY_VERSION.to_f < 2) ? /\[[^\]]*\]/ : eval('/\[((?:[^\[\]]++|\[\g<1>\])++)\]/') # RegExp Recursion only in Ruby 1.9+
        ROUND_BRACKET_START = /^\(/
        ROUND_BRACKET_END = /^\)/
        ROUND_BRACKET_NESTED = (RUBY_VERSION.to_f < 2) ? /\([^\)]*\)/ : eval('/\(((?:[^\(\)]++|\(\g<1>\))++)\)/') # RegExp Recursion only in Ruby 1.9+
      end

      class << self
        def parse_identifier_string(string, context=MAIN)
          return context if string.empty?
          case string
            when CONSTANT_SEPARATOR
              rest = $'
              return parse_identifier_string(rest, context)
            # Constant
            when CONSTANT
              receiver = (context.is_a?(Module)) ? context.const_get($&) : Kernel.const_get($&) # TODO: binding?
              rest = $'
              return walk_constant(receiver, rest)
            when GLOBAL_VARIABLE
              receiver = eval($&, TOPLEVEL_BINDING)
              rest = $'
              return walk_method(receiver, rest)
            when CLASS_VARIABLE
              if context.is_a?(Module) # or Class
                receiver = context.class_variable_get($&)
              else
                receiver = context.class.__send__(:class_variable_get, $&)
              end
              rest = $'
              return walk_method(receiver, rest)
            when INSTANCE_VARIABLE # or module_variable
              receiver = context.instance_variable_get($&)
              rest = $'
              return walk_method(receiver, rest)
            when SQUARE_BRACKET_START
              # Ignore everything in (eventually nested) brackets.
              string =~ SQUARE_BRACKET_NESTED
              receiver = Array.new
              rest = $'
              return walk_method(receiver, rest)
            when IDENTIFIER # method or local_variable
              name = $&
              # method
              if context.methods.include?(name) || context.methods.include?(name.to_sym) ||
                   Kernel.methods.include?(name) || Kernel.methods.include?(name.to_sym)
                return walk_method(context, string) || walk_method(Kernel, string)
              else # local_variable (works only if context is toplevel)
                # not working
                local = eval(name, context.__send__(:binding)) rescue eval(name, TOPLEVEL_BINDING) rescue nil # NameError
                return local
              end
          end
        #rescue Exception
        #  return nil
        end
        private :parse_identifier_string


        # Takes a reference and walks over a string of chained methods on it.
        # @param [Object] receiver is a reference to an object
        # @param [String] string of what
        def walk_method(receiver, string)
          return receiver if string.empty?
          case string
            when METHOD_SEPARATOR
              rest = $'
              return walk_method(receiver, rest)
            when ROUND_BRACKET_START
              # Ignore everything in (eventually nested) brackets.
              string =~ ROUND_BRACKET_NESTED
              rest = $'
              return walk_method(receiver, rest)
            when SQUARE_BRACKET_START
              # Ignore everything in (eventually nested) brackets
              # We don't know what type of object the accessor [] returns.
              name = :[]
              rest = $'
              mod = (receiver.is_a?(Module)) ? receiver : receiver.class
              if API.include?(mod) &&
                  API[mod].include?(name)
                return_type = API[mod][name]
                # Get an arbitrary instance of that type, so we can walk further
                receiver = return_type.new rescue ObjectSpace.each_object(return_type){ |o| break o }
                return walk_method(receiver, rest)
              else
                # If we assume the accessor has no side effects, we could eval it.
                # string =~ SQUARE_BRACKET_NESTED
                # exp = "self.[](#{$&[1...-1]})"
                # receiver = eval(exp, receiver.__send__(:binding))
                # return walk_method(receiver, rest)
                return nil
              end
            when IDENTIFIER # method
              name = $&.to_sym
              rest = $'
              mod = (receiver.is_a?(Module)) ? receiver : receiver.class
              if API.include?(mod) &&
                  API[mod].include?(name)
                return_type = API[mod][name]
                # Get an arbitrary instance of that type, so we can walk further
                receiver = return_type.new rescue ObjectSpace.each_object(return_type){ |o| break o }
              else
                #receiver = receiver.method(name).call # This could have side effects
                return nil
              end
              return walk_method(receiver, rest)
          end
        # rescue Exception
        #  return nil
        end
        private :walk_method


        # Takes a constant and walks over a string of nested constants.
        # @param [Object] receiver is a constant, and allows to access nested constants
        # @param [String] string of what
        def walk_constant(receiver, string)
          # string starts with Constant (if receiver is constant) or method
          return receiver if string.empty?
          case string
            when CONSTANT_SEPARATOR
              rest = $'
              return walk_constant(receiver, rest)
            # Constant
            when CONSTANT
              receiver = receiver.const_get($&)
              rest = $'
              return walk_constant(receiver, rest)
            when METHOD_SEPARATOR
              rest = $'
              return walk_method(receiver, rest)
            when IDENTIFIER # method
              rest = $'
              return walk_method(receiver, rest)
          end
        #rescue Exception
        #  return nil
        end
        private :walk_constant


        # Since Ruby methods don't declare the types of their arguments and return value,
        # we can not determine what method can be used on the return value of a method.
        # In order to provide better autocompletion for methods/chained methods, we
        # need to provide information about return values.
        # However, this does not work with subclasses. Maybe use array of types?
        # TODO: This be extended to use something like Notepad++'s AutoComplete xml files.
        # Class => { method => return_class }
        API = {
          Array => {
            :[] => Object
          },
          Sketchup => {
            :active_model => Sketchup::Model
          },
          Sketchup::Model => {
            :entities => Sketchup::Entities
          },
          Sketchup::Entities => {
            :add_arc => Array,
            :add_circle => Array,
            :add_cline => Sketchup::ConstructionLine,
            :add_cpoint => Sketchup::ConstructionPoint,
            :add_curve => Array,
            :add_edges => Array,
            :add_face => Sketchup::Face,
            :add_faces_from_mesh => Array,
            :add_group => Sketchup::Group,
            :add_image => Sketchup::Image,
            :add_instance => Sketchup::ComponentInstance,
            :add_line => Sketchup::Edge,
            :add_ngon => Array,
            :add_text => Sketchup::Text,
            :model => Sketchup::Model,
            :parent => Sketchup::Entities,
          },
          Sketchup::Face => {
            :edges => Array,
            :vertices => Array
          },
          Array => {
            :[] => Array,
            :clear => Array,
            :compact => Array,
            :count => Fixnum,
            :flatten => Array,
            :inspect => String,
            :join => String,
            :length => Fixnum,
            :push => Array,
            :reverse => Array,
            :size => Fixnum,
            :sort => Array,
            :sort! => Array,
            :uniq => Array,
          },
          Hash => {
            :clear => Hash,
            :inspect => String,
            :keys => Array,
            :length => Fixnum,
            :to_s => String,
            :values => Array
          },
          String => {
            :inspect => String,
            :intern => Symbol,
            :length => Fixnum,
          }
        }

      end # class << self


    end # Introspection


  end # Console


end # AE
