module ActionDispatch
  module Routing
    class Mapper

      Resource.class_eval do
        def unformat_route(route)
          JSONAPI.configuration.route_formatter.unformat(route.to_s)
        end

        def nested_param
          :"#{unformat_route(singular)}_#{param}"
        end
      end

      Resources.class_eval do
        def format_route(route)
          JSONAPI.configuration.route_formatter.format(route.to_s)
        end

        def jsonapi_resource(*resources, &block)
          @resource_type = resources.first
          res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))

          options = resources.extract_options!.dup
          options[:controller] ||= @resource_type
          options.merge!(res.routing_resource_options)
          options[:path] = format_route(@resource_type)

          resource @resource_type, options do
            @scope[:jsonapi_resource] = @resource_type

            if block_given?
              yield
            else
              jsonapi_relationships
            end
          end
        end

        def jsonapi_relationships
          res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
          res._associations.each do |association_name, association|
            if association.is_a?(JSONAPI::Association::HasMany)
              jsonapi_links(association_name)
              jsonapi_related_resources(association_name)
            else
              jsonapi_link(association_name)
              jsonapi_related_resource(association_name)
            end
          end
        end

        def jsonapi_resources(*resources, &block)
          @resource_type = resources.first
          res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))

          options = resources.extract_options!.dup
          options[:controller] ||= @resource_type
          options.merge!(res.routing_resource_options)

          # Route using the primary_key. Can be overridden using routing_resource_options
          options[:param] ||= res._primary_key

          options[:path] = format_route(@resource_type)

          resources @resource_type, options do
            @scope[:jsonapi_resource] = @resource_type

            if block_given?
              yield
            else
              jsonapi_relationships
            end
          end
        end

        def links_methods(options)
          default_methods = [:show, :create, :destroy, :update]
          if only = options[:only]
            Array(only).map(&:to_sym)
          elsif except = options[:except]
            default_methods - except
          else
            default_methods
          end
        end

        def jsonapi_link(*links)
          link_type = links.first
          formatted_association_name = format_route(link_type)
          options = links.extract_options!.dup

          res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)

          methods = links_methods(options)

          if methods.include?(:show)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                  action: 'show_association', association: link_type.to_s, via: [:get]
          end

          if methods.include?(:update)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                  action: 'update_association', association: link_type.to_s, via: [:put, :patch]
          end

          if methods.include?(:destroy)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                  action: 'destroy_association', association: link_type.to_s, via: [:delete]
          end
        end

        def jsonapi_links(*links)
          link_type = links.first
          formatted_association_name = format_route(link_type)
          options = links.extract_options!.dup

          res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)

          methods = links_methods(options)

          if methods.include?(:show)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                   action: 'show_association', association: link_type.to_s, via: [:get]
          end

          if methods.include?(:create)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                  action: 'create_association', association: link_type.to_s, via: [:post]
          end

          if methods.include?(:update)
            match "links/#{formatted_association_name}", controller: res._type.to_s,
                  action: 'update_association', association: link_type.to_s, via: [:put, :patch]
          end

          if methods.include?(:destroy)
            match "links/#{formatted_association_name}/:keys", controller: res._type.to_s,
                  action: 'destroy_association', association: link_type.to_s, via: [:delete]
          end
        end

        def jsonapi_related_resource(*association)
          source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)

          association_name = association.first
          association = source._associations[association_name]

          formatted_association_name = format_route(association.name)
          related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore.pluralize))

          match "#{formatted_association_name}", controller: related_resource._type.to_s,
                association: association.name, source: resource_type_with_module_prefix(source._type),
                action: 'get_related_resource', via: [:get]
        end

        def jsonapi_related_resources(*association)
          source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)

          association_name = association.first
          association = source._associations[association_name]

          formatted_association_name = format_route(association.name)
          related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore))

          match "#{formatted_association_name}", controller: related_resource._type.to_s,
                association: association.name, source: resource_type_with_module_prefix(source._type),
                action: 'get_related_resources', via: [:get]
        end

        private
        def resource_type_with_module_prefix(resource = nil)
          resource_name = resource || @scope[:jsonapi_resource]
          [@scope[:module], resource_name].compact.collect(&:to_s).join("/")
        end
      end
    end
  end
end