require 'happy/request'
require 'happy/response'

require 'happy/controller/routing'
require 'happy/controller/actions'
require 'happy/controller/rackable'
require 'happy/controller/configurable'
require 'happy/controller/permissions'

module Happy
  # Base class for Happy controllers. A controller's primary job is to act
  # upon an incoming request, navigating the request URL's path, and finally
  # deciding on a course of action (eg. rendering something, redirecting the
  # client, passing control over to another controller, and so on.)
  #
  class Controller
    include Routing
    include Actions
    include Rackable
    include Configurable
    include Permissions
    include Happy::Helpers

    attr_reader :env

    CASCADING_SETTINGS = [:views]

    # Creates a new instance of {Controller}. When a block is provided,
    # it is run against the new instance, allowing custom controller classes
    # to provide DSL-like configuration.
    #
    # @param env_or_parent [Hash,Controller]
    #   Rack environment hash _or_ parent controller.
    # @param opts [Hash]
    #   Options to be merged with the controller's default (class-level) settings.
    #
    def initialize(env_or_parent = {}, opts = {}, &blk)
      if env_or_parent.is_a?(Happy::Controller)
        @parent_controller = env_or_parent
        @env = @parent_controller.env
        @unprocessed_path = env_or_parent.unprocessed_path
        @processed_path = env_or_parent.processed_path
      else
        @env = env_or_parent
        @unprocessed_path = request.path.split('/').reject {|s| s.empty? }
        @processed_path  = []
      end

      # Augment this instance's settings hash with the hash given to this constructor
      settings.merge!(opts)

      # Copy missing settings from our parent
      if @parent_controller
        CASCADING_SETTINGS.each do |name|
          settings[name] ||= @parent_controller.settings[name]
        end
      end

      # Save a copy of the current path as this controller's root path.
      @root_url = processed_path.join('/')

      # Execute block against this instance, allowing the controller to
      # provide a DSL for configuration.
      instance_exec(&blk) if blk
    end

    def request
      @env['happy.request'] ||= Happy::Request.new(@env)
    end

    def response
      @env['happy.response'] ||= Happy::Response.new
    end

  protected

    attr_reader :unprocessed_path, :processed_path

    def root_url(*extras)
      url_for(@root_url, extras)
    end

    # Returns the application controller (ie, the root controller running this
    # application.)
    #
    def app
      @parent_controller ? @parent_controller.app : self
    end

  private

    # Adds helper methods to the base class for all controllers. Use this
    # to define application-level helper methods that you want to have available
    # in all views, even if they're begin rendered by a controller different from
    # your application controller.
    #
    # Modules passed to this method will be included into the base controller class.
    # Example:
    #
    #     helpers MyApp::Helpers
    #
    # Alternatively, you can specify a block that will be executed against the
    # base controller class like this:
    #
    #     helpers do
    #       def my_little_helper
    #         "something useful"
    #       end
    #     end
    #
    def self.helpers(*args, &blk)
      args.flatten.each do |arg|
        case arg
          when Module then Happy::Controller.send(:include, arg)
          else raise "Invalid helper specified."
        end
      end

      Happy::Controller.class_exec(&blk) if blk
    end

    def current_url(*extras)
      url_for(processed_path, extras)
    end

    def route
      # override this in subclasses
    end

    def params; request.params; end
    def session; request.session; end

  end
end
