##
# Stores global variables and global variable aliases.

module Rubinius
  class GlobalVariables
    def initialize
      load_path = []
      loaded_features = LoadedFeatures.new

      @internal = LookupTable.new
      @internal[:$;] = nil
      @internal[:$/] = "\n"             # Input record separator
      @internal[:$\] = nil              # Output record separator
      @internal[:$<] = ARGF
      @internal[:$:] = load_path
      @internal[:$-I] = load_path
      @internal[:$"] = loaded_features
      @internal[:$,] = nil              # Output field separator
      @internal[:$.] = 0
      @internal[:$=] = false            # ignore case, whatever that is
      @internal[:$0] = nil
      @internal[:$CONSOLE]         = STDOUT
      @internal[:$DEBUG]           = false
      @internal[:$LOADED_FEATURES] = loaded_features
      @internal[:$LOAD_PATH]       = load_path
      @internal[:$SAFE]            = 0

      @internal[:$stderr]          = STDERR
      @internal[:$stdin]           = STDIN
      @internal[:$stdout]          = STDOUT

      @alias = LookupTable.new
      @hooks = LookupTable.new
    end

    def key?(key)
      @internal.key?(key) || @alias.key?(key)
    end

    def variables
      @internal.keys + @alias.keys
    end

    def [](key)
      if @internal.key? key
        @internal[key]
      elsif @hooks.key? key
        @hooks[key][0].call(key)
      elsif @alias.key? key
        self[@alias[key]]
      end
    end

    def []=(key, data)
      if alias_key = @alias[key]
        self[alias_key] = data
      elsif @hooks.key? key
        hook = @hooks[key]
        val = hook[1].call(key, data)
        if hook[2]
          @internal[key] = val
        end
        val
      else
        @internal[key] = data
      end
    end

    # Respects internal and alias, but not hooks. Useful to bypass
    # normal read only checks.
    def set!(key, data)
      if alias_key = @alias[key]
        set! alias_key, data
      else
        @internal[key] = data
      end
    end

    def add_alias(current_name, alias_name)
      current_name = current_name.to_sym
      alias_name = alias_name.to_sym

      if @internal.key? alias_name
        @internal.delete alias_name
      end

      if @hooks.key? alias_name
        @hooks.delete alias_name
      end

      @alias[alias_name] = current_name
    end

    def illegal_set(*args)
      raise NameError, "unable to set global"
    end

    def set_hook(var, getter=nil, setter=nil, &block)
      if block
        if getter or setter
          raise ArgumentError, "duplicate procs provided with block"
        end

        @hooks[var] = [block, method(:illegal_set)]
      else
        set_internal = false
        set_internal = true if getter == :[]

        if !getter
          getter = method(:nil_return)
        elsif getter.kind_of? Symbol
          getter = method(getter)
        end

        unless getter.respond_to?(:call)
          raise ArgumentError, "getter must respond to call"
        end

        if !setter
          setter = method(:illegal_set)
        elsif setter.kind_of? Symbol
          setter = method(setter)
        end

        unless setter.respond_to?(:call)
          raise ArgumentError, "setter must respond to call"
        end

        @hooks[var] = [getter, setter, set_internal]
      end
    end

    def remove_hook(var)
      @hooks.delete(var)
    end

    def nil_return
      nil
    end

    def set_filter(var, block)
      @hooks[var] = [method(:nil_return), block, true]
    end

    def raise_readonly(key, data)
      raise NameError, "#{key} is a read-only global variable"
    end

    def read_only(*vars)
      mo = method(:raise_readonly)
      vars.each do |v|
        set_filter v, mo
      end
    end
  end

  Globals = GlobalVariables.new
end
