# coding: utf-8

module UzuUzu
  #
  #
  #
  class Application
    #
    attr_accessor :name
    #
    attr_accessor :config_file
    #
    attr_accessor :environments
    #
    attr_accessor :controllers
    #
    attr_accessor :before_filters
    #
    attr_accessor :after_filters
    #
    attr_accessor :helpers
    #
    attr_accessor :views
    #
    attr_accessor :servicies
    
    #
    #
    #
    def self.current
      ::Thread.current[:application]
    end
        
    #
    #
    #
    def initialize(options={})
      ::Thread.current[:application] = self
      options.each do |key, value|
        case(key)
        when :name, 'name'
          @name = value
        when :config, 'config'
          @config_file = value
        end
      end if options.kind_of?(Hash)
      @name ||= :uzuuzu
      @environments = Environments.new(@config_file)
      ::UzuUzu.apps[@name] = self
      yield self if block_given?
      ::Thread.current[:application] = nil
    end # initialize

    def self.controllers
      @@controllers ||= [Controller]
    end

    def controllers
      @controllers ||= self.class.controllers.clone
    end

    def self.before_filters
      @@before_filters ||= [Controller.method(:before_all)]
    end

    def before_filters
      @before_filters ||= self.class.before_filters.clone
    end

    def self.after_filters
      @@after_filters ||= [Controller.method(:after_all)]
    end
    
    def after_filters
      @after_filters ||= self.class.after_filters.clone
    end

    def self.helpers
      @@helpers ||= [
                     Helper::Controller,
                     Helper::Form,
                     Helper::Jquery,
                     Helper::Localize,
                     Helper::Renderer,
                     Helper::Route,
                     Helper::XHtml
                   ]
    end

    def helpers
      @helpers ||= self.class.helpers.clone
    end
    
    def self.views
      @@views ||= ['./view']
    end

    def views
      @views ||= self.class.views.clone
    end
    
    def self.services
      @@services ||= []
    end

    def services
      @services ||= self.class.services.clone
    end

    #
    #
    #
    def call(env)
      Thread.current[:application] = self
      request = UzuUzu::Request.new(env)
      response = UzuUzu::Response.new
      clazz = find_controller(env)
      if clazz.blank?
        di_thread(request, response, nil, nil,  nil, nil, nil)
        return not_found(response)
      end

      is_send = false
      action_return = nil
      render = nil
      clazz.reverse.each do |map|
        controller_class = map[:clazz]
        query = map[:query]
        route = map[:route]
        controller = controller_class.new
        helper = Helper::Helper.new
        include_helper(helper)

        action, query = find_action(controller, query)
        di_thread(request, response, controller, helper,  action, query, route)
        next if action.blank?
        is_send = true
        action_return = send_action(request, response, controller, action, query)
        if response.body.blank?
          render = helper.render(action)
        end
        break
      end
      
      unless is_send
        return not_found(response)
      end
      if response.status == 200 and response.body.blank?
        response.write render || action_return
      end

      response.finish
    end # call

    #
    #
    #
    def find_controller(env)
      classies = []
      request_paths = env['PATH_INFO'].split('/')
      request_paths.shift if request_paths[0].blank?
      
      controllers.each do |c|
        if request_paths.size == 0
          add_classies(classies, c, 'Index', env['SCRIPT_NAME'], request_paths)
        end
        request_paths.each_with_index do |request_path, index|
          next if request_path.blank?
          c_str = request_path.camel_case
          route = "#{request_paths[0...index].join('/')}"
          if route.blank?
            route = "#{env['SCRIPT_NAME']}"
          else
            route = "#{env['SCRIPT_NAME']}/#{route}"
          end
          query = request_paths[index..-1]
          add_classies(classies, c, 'Index', route, query)
          next if c_str == 'Index'
          route = "#{env['SCRIPT_NAME']}/#{request_paths[0..index].join('/')}"
          query = request_paths[(index+1)..-1]
          _c = add_classies(classies, c, c_str, route, query)
          if _c
            c = _c
          else
            break
          end
        end
      end
      classies
    end # find_controller

    def add_classies(classies, c, c_str, route, query)
      _c = nil
      begin
        if c.const_defined?(c_str)
          _c = c.const_get(c_str)
          classies << {:clazz => _c,
                       :route => route,
                       :query => query}
        elsif c.const_defined?("#{c_str}Controller")
          classies << {:clazz => c.const_get("#{c_str}Controller"),
                       :route => route,
                       :query => query}
        end
      rescue NameError => e
      end
      _c
    end
    
    #
    #
    #
    def include_helper(helper)
      helpers.each do |h|
        helper.extend(h)
      end
    end # include_helper
    
    #
    #
    #
    def di_thread(request, response, controller, helper, action, query, route)
      current = ::Thread.current
      current[:request] = request
      current[:response] = response
      current[:controller] = controller
      current[:helper] = helper
      current[:action] = action
      current[:query] = query
      current[:route] = route
    end # di_thread

    #
    #
    #
    def find_action(controller, query)
      action = nil
      index_flag = false
      query[-1] = query[-1].sub(/\.\w+$/, '') if query && query[-1]
      if query[0] && controller.respond_to?(query[0])
        action = query.shift
      end
      if action.nil? && controller.respond_to?(:index)
        action = 'index'
      end
      
      unless action.nil?
        arity = controller.method(action).arity
        owner = controller.method(action).owner
        unless controller.method(action).owner == controller.class
          index_flag = true
          if controller.respond_to?(:use_actions)
            controller.actions.each do |use_action|
              if use_action.to_s == action.to_s
                index_flag = false
              end
            end
          end
        end
        unless (arity >= 0 && query.size == arity) ||
               (arity < 0 && query.size >= ((-1 * arity)-1))
          index_flag = true
        end
        if index_flag && !(action == 'index') && controller.respond_to?(:index)
          query.unshift(action)
          action = 'index'
        end
        if controller.respond_to?(action)
          arity = controller.method(action).arity
          unless (arity >= 0 && query.size == arity) ||
                 (arity < 0 && query.size >= ((-1 * arity)-1))
            action = nil
          end
        else
          action = nil
        end
      end
      [action, query]
    end # find_action

    #
    #
    #
    def send_action(request, response, controller, action, query)
      value = nil
      catch(:finish) do
        before_filters.each do |filter|
          filter.call if filter.respond_to?(:call)
        end
        controller.send(:before) if controller.respond_to?(:before)
        controller.send("before_#{action}") if controller.respond_to?("before_#{action}")
        value = controller.send(action, *query)
      end
      catch(:finish) do
        controller.send("after_#{action}") if controller.respond_to?("after_#{action}")
      end
      catch(:finish) do
        controller.send(:after) if controller.respond_to?(:after)
      end
      after_filters.reverse.each do |filter|
        catch(:finish) do
          filter.call if filter.respond_to?(:call)
        end
      end
      value
    end # send_action

    def not_found(response)
      catch(:finish) do
        response.redirect("/error/404")
      end
      response.finish
    end
  end # Application
end # UzuUzu