require 'singleton'
require 'active_record'
require 'action_controller'
require 'action_mailer'
require 'action_view'
require 'yaml'
require 'needle'

require File.join(File.dirname(__FILE__), 'base-enhance')

module Rails

  # A simple subclass of the Needle registry class, to make it easier to add
  # component-specific namespaces.
  class Registry < Needle::Registry

    # If a namespace does not exist with the given name, create it. Regardless,
    # return the namespace.
    def component_space( name )
      unless has_key?( name )
        namespace name
      end

      self[ name ]
    end

  end

  # A singleton class encapsulating the management of environments in Rails.
  class Environment
    include Singleton

    # The array of additional load paths to add.
    ADDITIONAL_LOAD_PATHS =
      %w{app/models app/controllers app/helpers config lib vendor}

    # A reference to the registry to use.
    attr_reader :registry

    # Create a new Environment. Since this is a singleton class, this cannot
    # be called directly, and will only be invoked once when the singleton
    # instance is created.
    def initialize
      ADDITIONAL_LOAD_PATHS.each do |dir|
        $:.unshift "#{File.dirname(__FILE__)}/../../#{dir}"
      end

      ActionController::Base.template_root =
        ActionMailer::Base.template_root =
          File.dirname(__FILE__) + '/../../app/views/'

      @registry = Rails::Registry.new :logs => { :device => STDOUT }

      # Load the database configurations into a service, so they can be
      # referenced and rereferenced without reloading the file. This also allows
      # clients to override how database configurations are defined, simply by
      # redefining the database_configurations service.
      @registry.register :database_configurations do
        YAML::load(
          File.open(
            File.dirname(__FILE__) + "/../../config/database.yml"))
      end

      # return the current database configuration. Use the 'prototype' model,
      # so that the block is executed on every request. Otherwise, if the
      # current_environment changed, the database_configuration service would
      # never reflect the change.
      @registry.register( :database_configuration, :model => :prototype ) do
        @registry.database_configurations[ @registry.current_environment ]
      end

      # return the current system log file location. Use the 'prototype' model,
      # so that the block is executed on every request. Otherwise, if the
      # current_environment changed, the system_log_file service would
      # never reflect the change.
      #
      # this allows clients to change where logs are written to, simply by
      # registering a system_log_file service that replaces this one.
      @registry.register( :system_log_file, :model => :prototype ) do
        File.dirname(__FILE__) + "/../../log/#{@registry.current_environment}.log"
      end

      ActiveRecord::Base.registry     = @registry.component_space :model
      ActionController::Base.registry = @registry.component_space :controller
      ActionMailer::Base.registry     = @registry.component_space :mailer
      ActionView::Base.registry       = @registry.component_space :view

      ActiveRecord::Base.logger = @registry.logs.get "[active-record]"
      ActionController::Base.logger = @registry.logs.get "[action-controller]"
      ActionMailer::Base.logger = @registry.logs.get "[action-mailer]"
    end

    # Select a new environment, by name. The only restriction is that, by default,
    # there must be an identically named database configuration, or this will
    # fail.
    def set( environment_name )
      @registry.register( :current_environment ) { environment_name }
      @registry.logs.write_to(@registry.system_log_file)
      ActiveRecord::Base.establish_connection(@registry.database_configuration)
      true
    end

    # A convenience method for setting the environment. This allows you to do:
    #
    #   Environment.set "production"
    #
    # instead of
    # 
    #   Environment.instance.set "production"
    def self.set( environment_name )
      instance.set( environment_name )
    end

    # A convenience accessor for accessing the Rails registry. This allows you
    # to do:
    #
    #   Environment.registry
    #
    # instead of
    # 
    #   Environment.instance.registry
    def self.registry
      instance.registry
    end

  end

end
