module Johnson
  ###
  # An interface to a JavaScript engine.
  class Runtime

    PRELUDE_PATH = File.expand_path File.dirname(__FILE__) +
      "/js/prelude.js" # :nodoc:
    CORE_PATH = File.expand_path File.dirname(__FILE__) +
      "/js/core.js" # :nodoc:

    PRELUDE = IO.read PRELUDE_PATH # :nodoc:
    CORE = IO.read CORE_PATH # :nodoc:

    ###
    # Deprecated: Previously, returned the underlying JavaScript engine
    # instance. Now returns self.
    def delegate
      self
    end

    ###
    # Create a new Runtime instance, using the default JavaScript
    # engine.
    #
    # Optionally takes a parameter specifying which engine to use, but
    # this is deprecated; instead, just create an instance of that
    # engine's runtime directly.
    #
    # :call-seq:
    #   new(runtime_class=nil)
    #
    def self.new(*args)
      return super if self < Johnson::Runtime

      delegate = args.first
      if delegate.is_a? Class
        delegate.new
      elsif delegate
        delegate
      else
        Johnson::SpiderMonkey::Runtime.new
      end
    end

    ###
    # Install the Johnson prelude into this runtime environment.
    def initialize # :notnew:
      evaluate PRELUDE, PRELUDE_PATH, 1
      global.Johnson.runtime = self
      global['Ruby'] = Object
      evaluate CORE, CORE_PATH, 1
    end

    ###
    # Access the +key+ property of the JavaScript +global+ object.
    def [](key)
      global[key]
    end

    ###
    # Set the +key+ property of the JavaScript +global+ object to
    # +value+.
    def []=(key, value)
      global[key] = value
    end


    ###
    # Execute the JavaScript source in +script+. If supplied, the script
    # is marked as starting on line +linenum+ of +filename+.
    #
    # Equivalent to calling RubyLandScript#execute on the result of
    # Runtime#compile.
    def evaluate(script, filename = nil, linenum = nil)
      return nil if script.nil?
      compiled_script = compile(script, filename, linenum)
      evaluate_compiled_script(compiled_script)
    end


    ###
    # The JavaScript unique Global Object.
    def global
      raise NotImplementedError
    end

    ###
    # Load and execute the named JavaScript +files+.
    #
    # Checks for (and skips) a shebang line at the top of any of them.
    def load(*files)
      files.map { |f|
        evaluate(File.read(f).gsub(/\A#!.*$/, ''), f, 1)
      }.last
    end

    ###
    # Search the Ruby load path for each of the named +files+, and
    # evaluate them *if they have not yet been loaded*.
    #
    # Calls Johnson.require() in JavaScript on each filename in turn.
    def require(*files)
      files.each do |file|
        evaluate("Johnson.require('#{file}');")
      end
    end

    ###
    # Compile the JavaScript source in +script+. If supplied, the script
    # is marked as starting on line +linenum+ of +filename+.
    def compile(script, filename=nil, linenum=nil)
      raise NotImplementedError
    end

    ###
    # Evaluates the given JS script, that should have been returned by a
    # previous call to #compile().
    def evaluate_compiled_script(script)
      raise NotImplementedError
    end
  end
end
