require 'hooks/inheritable_attribute'
require 'roar/rails/responder'

module Roar::Rails
  module ControllerAdditions
    extend ActiveSupport::Concern

    included do
      extend Hooks::InheritableAttribute
      inheritable_attr :represents_options
      self.represents_options ||= RepresenterComputer.new
    end


    module ClassMethods
      def responder
        Class.new(super).send :include, Roar::Rails::Responder
      end

      def represents(format, options)
        represents_options.add(format,options)
        respond_to format
      end
    end


    def consume!(model, options={})
      format      = formats.first  # FIXME: i expected request.content_mime_type to do the job. copied from responder.rb. this will return the wrong format when the controller responds to :json and :xml and the Content-type is :xml (?)
      representer = prepare_model_for(format, model, options)

      representer.send(compute_parsing_method(format), incoming_string, options) # e.g. from_json("...")
      model
    end

    def prepare_model_for(format, model, options)
      representer = representer_for(format, model, options)
      representer.prepare(model)
    end

    # Central entry-point for finding the appropriate representer.
    def representer_for(format, model, options={})
      options.delete(:represent_with) || self.class.represents_options.for(format, model, controller_path)
    end

  private
    def compute_parsing_method(format)
      "from_#{format}"
    end

    def incoming_string
      request.body.read
    end

    def render_to_body(options)
      if res = options[formats.first] and res.is_a?(Roar::Rails::Responder::Response)
        response.content_type = res.content_type
        return res.body
      end

      super
    end


    class RepresenterComputer < Hash
      def add(format, opts)
        # FIXME: use controller_path here as well!
        # by pre-computing the representer name we allow "one-step inheritance": if B doesn't call ::represents it "inherits" A's settings.
        unless opts.is_a?(Hash)
          model_name = opts.name.underscore
          opts = {
            :entity     => add_representer_suffix(model_name),
            :collection => add_representer_suffix(model_name.pluralize)
          }
        end

        self[format] = opts
      end

      def for(*args)
        name = name_for(*args) or return

        return name if name.is_a?(Module) # i hate is_a? but this is really handy here.
        name.camelize.constantize
      end

    private
      def [](format)
        super(format.to_sym) or {}
      end

      def name_for(format, model, controller_path) # DISCUSS: should we pass and process options here?
        controller_path = Path.new(controller_path) # DISCUSS: could that be done in the initialization, maybe?
        options         = self[format]

        if detect_collection(model)
          options[:collection] or collection_representer(format, model, controller_path)
        else
          options[:entity] or entity_representer(format, model, controller_path)
        end
      end

      def collection_representer(format, model, controller_path)
        infer_representer(controller_path)
      end

      def entity_representer(format, model, controller_path)
        model_name = model.class.name.underscore

        if namespace = controller_path.namespace
          model_name = "#{namespace}/#{model_name}"
        end

        infer_representer(model_name)
      end

      def infer_representer(model_name)
        add_representer_suffix(model_name).camelize.constantize
      end

      def add_representer_suffix(prefix)
        "#{prefix}_representer"
      end

      def detect_collection(model)
        return true if model.kind_of?(Array)
        return true if Object.const_defined?("ActiveRecord") and model.kind_of?(ActiveRecord::Relation)
      end

      class Path < String
        def namespace
          return unless ns = self.match(/(.+)\/\w+$/)
          ns[1]
        end
      end
    end
  end
end