module Vidibus
  module Permalink
    module Mongoid
      extend ActiveSupport::Concern

      class PermalinkConfigurationError < StandardError; end

      included do
        field :permalink, :type => String
        index :permalink

        before_validation :set_permalink
        validates :permalink, :presence => true

        after_save :store_permalink_object
        after_destroy :destroy_permalink_objects
      end

      module ClassMethods

        # Sets permalink attributes.
        # Usage:
        #   permalink :some, :fields, :scope => {:realm => "rugby"}
        def permalink(*args)
          options = args.extract_options!
          class_eval <<-EOS
            def self.permalink_attributes
              #{args.inspect}
            end

            def self.permalink_options
              #{options.inspect}
            end
          EOS
        end
      end

      # Returns the defined permalink repository object.
      def permalink_repository
        @permalink_repository ||= (self.class.permalink_options[:repository] == false) ? nil : ::Permalink
      end

      # Returns the current permalink object.
      def permalink_object
        @permalink_object || permalink_repository.for_linkable(self).where(:_current => true).first if permalink_repository
      end

      # Returns all permalink objects ordered by time of update.
      def permalink_objects
        permalink_repository.for_linkable(self).asc(:updated_at) if permalink_repository
      end

      # Returns permalink scope.
      def permalink_scope
        @permalink_scope ||= self.class.permalink_options[:scope]
      end

      def find_or_initialize(value, scope)
        permalink_repository.for_linkable(self).for_value(value).for_scope(scope).first ||
          permalink_repository.new(:value => value, :scope => scope, :linkable => self)
      end

      private

      # Initializes a new permalink object and sets permalink attribute.
      def set_permalink
        begin
          attribute_names = self.class.permalink_attributes
        rescue NoMethodError
          raise PermalinkConfigurationError.new("#{self.class}.permalink_attributes have not been assigned! Use #{self.class}.permalink(:my_field) to set it up.")
        end

        changed = false
        values = []
        for a in attribute_names
          changed = send("#{a}_changed?") unless changed == true
          values << send(a)
        end
        return unless permalink.blank? or changed
        value = values.join(" ")
        if permalink_repository
          @permalink_object = find_or_initialize(value, permalink_scope)
          self.permalink = @permalink_object.value
        else
          self.permalink = ::Permalink.sanitize(value)
        end
      end

      # Stores current new permalink object or updates an existing one that matches.
      def store_permalink_object
        return unless @permalink_object
        @permalink_object.updated_at = Time.now
        @permalink_object.save!
      end

      def destroy_permalink_objects
        permalink_repository.delete_all(:conditions => {:linkable_uuid => uuid}) if permalink_repository
      end
    end
  end
end