# cherry picking active_support stuff
require 'active_support/core_ext/array'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/module/attribute_accessors'

require 'simple_navigation/version'
require 'simple_navigation/configuration'
require 'simple_navigation/item_adapter'
require 'simple_navigation/item'
require 'simple_navigation/item_container'
require 'simple_navigation/items_provider'
require 'simple_navigation/renderer'
require 'simple_navigation/adapters'
require 'simple_navigation/config_file_finder'
require 'simple_navigation/railtie' if defined?(::Rails)

require 'forwardable'

# A plugin for generating a simple navigation. See README for resources on
# usage instructions.
module SimpleNavigation
  mattr_accessor :adapter,
                 :adapter_class,
                 :config_files,
                 :config_file_paths,
                 :default_renderer,
                 :environment,
                 :registered_renderers,
                 :root

  # Cache for loaded config files
  self.config_files = {}

  # Allows for multiple config_file_paths. Needed if a plugin itself uses
  # simple-navigation and therefore has its own config file
  self.config_file_paths = []

  # Maps renderer keys to classes. The keys serve as shortcut in the
  # render_navigation calls (renderer: :list)
  self.registered_renderers = {
    list:        SimpleNavigation::Renderer::List,
    links:       SimpleNavigation::Renderer::Links,
    breadcrumbs: SimpleNavigation::Renderer::Breadcrumbs,
    text:        SimpleNavigation::Renderer::Text,
    json:        SimpleNavigation::Renderer::Json
  }

  class << self
    extend Forwardable

    def_delegators :adapter, :context_for_eval,
                             :current_page?,
                             :request,
                             :request_path,
                             :request_uri

    def_delegators :adapter_class, :register

    # Sets the root path and current environment as specified. Also sets the
    # default config_file_path.
    def set_env(root, environment)
      self.root = root
      self.environment = environment
      config_file_paths << default_config_file_path
    end

    # Returns the current framework in which the plugin is running.
    def framework
      return :rails if defined?(Rails)
      return :padrino if defined?(Padrino)
      return :sinatra if defined?(Sinatra)
      return :nanoc if defined?(Nanoc3)
      fail 'simple_navigation currently only works for Rails, Sinatra and ' \
           'Padrino apps'
    end

    # Loads the adapter for the current framework
    def load_adapter
      self.adapter_class =
        case framework
        when :rails then SimpleNavigation::Adapters::Rails
        when :sinatra then SimpleNavigation::Adapters::Sinatra
        when :padrino then SimpleNavigation::Adapters::Padrino
        when :nanoc then SimpleNavigation::Adapters::Nanoc
        end
    end

    # Creates a new adapter instance based on the context in which
    # render_navigation has been called.
    def init_adapter_from(context)
      self.adapter = adapter_class.new(context)
    end

    def default_config_file_path
      File.join(root, 'config')
    end

    # Resets the list of config_file_paths to the specified path
    def config_file_path=(path)
      self.config_file_paths = [path]
    end

    # Reads the config_file for the specified navigation_context and stores it
    # for later evaluation.
    def load_config(navigation_context = :default)
      if environment == 'production'
        update_config(navigation_context)
      else
        update_config!(navigation_context)
      end
    end

    # Returns the singleton instance of the SimpleNavigation::Configuration
    def config
      SimpleNavigation::Configuration.instance
    end

    # Returns the ItemContainer that contains the items for the
    # primary navigation
    def primary_navigation
      config.primary_navigation
    end

    # Returns the active item container for the specified level.
    # Valid levels are
    # * :all - in this case the primary_navigation is returned.
    # * :leaves - the 'deepest' active item_container will be returned
    # * a specific level - the active item_container for the specified level
    #   will be returned
    # * a range of levels - the active item_container for the range's minimum
    #   will be returned
    #
    # Returns nil if there is no active item_container for the specified level.
    def active_item_container_for(level)
      case level
      when :all then primary_navigation
      when :leaves then primary_navigation.active_leaf_container
      when Integer then primary_navigation.active_item_container_for(level)
      when Range then primary_navigation.active_item_container_for(level.min)
      else
        fail ArgumentError, "Invalid navigation level: #{level}"
      end
    end

    # Registers a renderer.
    #
    # === Example
    # To register your own renderer:
    #
    #   SimpleNavigation.register_renderer my_renderer: My::RendererClass
    #
    # Then in the view you can call:
    #
    #   render_navigation(renderer: :my_renderer)
    def register_renderer(renderer_hash)
      registered_renderers.merge!(renderer_hash)
    end

    private

    def config_file(navigation_context)
      ConfigFileFinder.new(config_file_paths).find(navigation_context)
    end

    def read_config(navigation_context)
      File.read config_file(navigation_context)
    end

    def update_config(navigation_context)
      config_files[navigation_context] ||= read_config(navigation_context)
    end

    def update_config!(navigation_context)
      config_files[navigation_context] = read_config(navigation_context)
    end
  end
end

SimpleNavigation.load_adapter
