require 'forwardable'
require 'params_verification'
require 'json'

class WeaselDiesel

  class RequestHandler
    extend Forwardable

    # @return [WeaselDiesel] The service served by this controller
    # @api public
    attr_reader :service

    # @return [Sinatra::Application]
    # @api public
    attr_reader :app

    # @return [Hash]
    # @api public
    attr_reader :env

    # @return [Sinatra::Request]
    # @see http://rubydoc.info/github/sinatra/sinatra/Sinatra/Request
    # @api public
    attr_reader :request

    # @return [Sinatra::Response]
    # @see http://rubydoc.info/github/sinatra/sinatra/Sinatra/Response
    # @api public
    attr_reader :response

    # @return [Hash]
    # @api public
    attr_accessor :params

    attr_accessor :current_user

    # The service controller might be loaded outside of a Sinatra App
    # in this case, we don't need to load the helpers
    if Object.const_defined?(:Sinatra)
      include Sinatra::Helpers
    end

    def initialize(service, &block)
      @service = service
      @implementation = block
    end

    def dispatch(app)
      @app      = app
      @env      = app.env
      @request  = app.request
      @response = app.response
      @service  = service
  
      begin
        # raises an exception if the params are not valid
        # otherwise update the app params with potentially new params (using default values)   
        # note that if a type is mentioned for a params, the object will be cast to this object type 
        #
        # removing the fake sinatra params since v1.3 added this. (should be eventually removed)
        if app.params['splat']
          processed_params = app.params.dup
          processed_params.delete('splat')
          processed_params.delete('captures')
        end
        @params = (processed_params || app.params)
        @params = params_preprocessor_hook(@params) if self.respond_to?(:params_preprocessor_hook)
        @params = ParamsVerification.validate!(@params, service.defined_params)
        @params = params_postrocessor_hook(@params) if self.respond_to?(:params_postprocessor_hook)
      rescue Exception => e
        LOGGER.error e.message
        LOGGER.error "passed params: #{app.params.inspect}"
        halt 400, {:error => e.message}.to_json
      end

      # Define WeaselDiesel::RequestHandler#authorization_check in your app if
      # you want to use an auth check.
      pre_dispatch_hook if self.respond_to?(:pre_dispatch_hook)
      service_dispatch
    end

    # Forwarding some methods to the underlying app object
    def_delegators :app, :settings, :halt, :compile_template, :session
 
    private ##################################################

  end # of RequestHandler

  attr_reader :handler

  def implementation(&block)
    if block_given?
      @handler = RequestHandler.new(self, &block)
      @handler.define_singleton_method(:service_dispatch, block)
    end
    @handler
  end

  def load_sinatra_route
    service     = self
    upcase_verb = service.verb.to_s.upcase
    unless ENV['DONT_PRINT_ROUTES']
      LOGGER.info "Available endpoint: #{self.http_verb.upcase} /#{self.url}" 
    end
    raise "DSL is missing the implementation block" unless self.handler && self.handler.respond_to?(:service_dispatch)

    # Define the route directly to save some object allocations on the critical path
    # Note that we are using a private API to define the route and that unlike sinatra usual DSL
    # we do NOT define a HEAD route for every GET route.
    Sinatra::Base.send(:route, upcase_verb, "/#{self.url}") do
      service.handler.dispatch(self)
    end
    
  end

end
