module Locomotive
  module API
    module Helpers

      # Sets up model/repository methods.
      # @example
      #
      # def translation_params
      #   permitted_params[:translation]
      # end
      #
      # def auth(meth)
      #   authorize Translation, meth
      # end
      #
      # def translations
      #   current_site.translations
      # end
      #
      # def translation
      #   @translation ||= translations.find(params[:id])
      # end
      #
      module PersistenceHelper

        attr_accessor :resource_name, :use_form_object

        def setup_resource_methods_for(resource_name)
          self.resource_name = resource_name.to_s

          setup_plural_method
          setup_singular_getter_method
          setup_singular_setter_method
          setup_params_method

          setup_object_auth_method
        end

        # @return [Class] a class constant for the matching form class.
        def form_klass
          namespaced_klass("#{singular}_form")
        end

        # @param [Object] the object to send to the policy class.  Leave empty for
        #  the singular (default) object to be sent.
        # @return [Class] a class constant for the current Pundit policy on the object.
        def current_policy(obj = nil)
          obj = obj ? obj : send(singular)
          policy_klass.new(pundit_user, obj)
        end

        # @param [Object] an object that responds to #serializable_hash is fed into
        #  the persistence class and save is called.
        # @note if the object exists it will be updated
        def persist_from_form(form_object)
          send(singular).assign_attributes(form_object.serializable_hash)
          send(singular).save!
        end

        def auth(meth)
          authorize persistence_klass, meth
        end

        private

        def policy_klass
          namespaced_klass("#{singular}_policy")
        end

        def persistence_klass
          # Change unscoped class to whatever repository/persistence class needed.
          namespaced_klass(singular)
        end

        def namespaced_klass(klass_name)
          un_namespaced_klass = klass_name.classify
          if un_namespaced_klass.ends_with?('Form')
            "Locomotive::API::Forms::#{un_namespaced_klass}"
          else
            "Locomotive::#{un_namespaced_klass}"
          end.constantize
        end

        def use_form_object?
          use_form_object
        end

        def singular
          @singular ||= plural.singularize
        end

        def plural
          @plural ||= resource_name
        end

        def setup_params_method
          self.class.send(:define_method, "#{singular}_params") do
            permitted_params[singular.to_sym]
          end
        end

        # Authenticate an action against an instance
        def setup_object_auth_method
          self.class.send(:define_method, :object_auth) do |meth|
            authorize send(singular), meth
          end
        end

        def setup_plural_method
          # is it already defined by the resource itself?
          return if self.respond_to?(plural)

          self.class.send(:define_method, plural) do
            current_site.send(plural)
          end
        end

        def setup_singular_getter_method
          self.class.send(:define_method, singular) do
            instance_variable = "@#{singular}"
            if instance_variable_defined?(instance_variable)              
              instance_variable_get(instance_variable)
            else
              if params[:id]
                instance_variable_set(instance_variable, send(plural).find(params[:id]))
              else
                instance_variable_set(instance_variable, send(plural).new)
              end
            end
          end
        end

        def setup_singular_setter_method
          self.class.send(:define_method, "#{singular}=") do |var|
            instance_variable_set("@#{singular}", var)
          end
        end

      end

    end
  end
end
