module EasyPatch
  module AttachmentPatch

    def self.included(base)
      base.extend(ClassMethods)
      base.send(:include, InstanceMethods)

      base.class_eval do

        validates :description, :presence => true, :if => :description_required?
        attr_writer :skip_description_required

        acts_as_versioned :association_options => {:dependent => :destroy}, :if => Proc.new { |p| !p.container_id.nil? }

        acts_as_restricted :restricted_object => :container, :easy_permission_name => [:read, :manage],
                           :if => Proc.new { |att| att.container_type == 'Document' }

        acts_as_user_readable
        acts_as_customizable

        skip_callback :validate, :before, :validate_custom_field_values, :if => :new_record?
        skip_callback :save, :after, :save_custom_field_values, :if => :not_attached?

        self.non_versioned_columns << 'downloads'
        self.non_versioned_columns << 'category'

        alias_method_chain :sanitize_filename, :easy_extensions
        alias_method_chain :validate_max_file_size, :easy_extensions
        alias_method_chain :increment_download, :easy_extensions


        class << self

          def sanitize_filename(value)
            # get only the filename, not the whole path
            just_filename = value.gsub(/\A.*(\\|\/)/m, '')

            # Finally, replace invalid characters with underscore
            just_filename.gsub(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_')
          end

          def attachment_reminder_words
            '(^|\\W)(' + EasySetting.value('attachment_reminder_words').gsub(/[\n,;]/, '|').tr(" \t\r", '').gsub(/\?/, '.?').gsub(/\*/, '.*').tr_s('|', '|').chomp('|') + ')($|\\W)'
          end

        end

        def current_version
          return @current_version if @current_version
          @current_version ||= self.versions.detect { |v| v.version == self.version }
          @current_version ||= self.versions.last
          @current_version ||= Attachment::Version.new
          return @current_version
        end

        def description_required?
          !!EasySetting.value('attachment_description_required') && !@skip_description_required
        end

        def editable?(user=User.current)
          if container_id
            container && container.attachments_editable?(user)
          else
            author == user
          end
        end

        def is_message?
          Redmine::MimeType.is_type?('message', filename)
        end

        def not_attached?
          container.nil?
        end

      end
    end

    module InstanceMethods

      def validate_max_file_size_with_easy_extensions
        if self.filesize > Setting.attachment_max_size.to_i.kilobytes
          errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes, :message => self.filename + ' - ' + l(:error_validates_max_size) + " (#{(self.filesize.kilobytes / 1000).round} kB)")
        end
      end

      def increment_download_with_easy_extensions
        self.without_revision do
          increment_download_without_easy_extensions
        end
      end

      private

      def sanitize_filename_with_easy_extensions(value)
        @filename = self.class.sanitize_filename(value)
      end

    end

    module ClassMethods

      def easy_activity_custom_project_scope(scope, options, event_type)
        scope.where(
          "CASE #{Attachment.table_name}.container_type
            WHEN 'Issue' THEN EXISTS(SELECT i.id FROM #{Issue.table_name} i WHERE i.id = #{Attachment.table_name}.container_id AND i.project_id in (?))
            WHEN 'Document' THEN EXISTS(SELECT d.id FROM #{Document.table_name} d WHERE d.id = #{Attachment.table_name}.container_id AND d.project_id in (?))
           END", options[:project_ids], options[:project_ids])
      end

    end

  end

  module AttachmentVersionPatch

    def self.included(base)

      base.class_eval do

        belongs_to :container, :polymorphic => true
        #belongs_to :attachment, :polymorphic => true
        belongs_to :author, :class_name => 'User'

        after_rollback :delete_from_disk, :on => :create
        after_commit :delete_from_disk, :on => :destroy

        acts_as_user_readable

        delegate :project, :visible?, :editable?, :deletable?, :readable?, :image?, :is_text?, :is_diff?, :is_message?, :is_image?, :is_pdf?, :increment_download, :to => :attachment

        def diskfile
          File.join(Attachment.storage_path, self.disk_directory.to_s, self.disk_filename.to_s)
        end

        def delete_from_disk
          if Attachment::Version.where('disk_filename = ? AND id <> ?', self.disk_filename, self.id).empty?
            delete_from_disk!
          end

          if self.attachment
            if self.attachment.versions.empty?
              self.attachment.destroy
            elsif self.attachment.version == self.version
              self.attachment.revert_to!(self.previous || self.attachment.versions.latest)
            end
          end
        end

        private

        def delete_from_disk!
          if self.disk_filename.present? && File.exist?(self.diskfile)
            File.delete(self.diskfile)
          end
        end
      end

    end

  end
end
EasyExtensions::PatchManager.register_model_patch 'Attachment', 'EasyPatch::AttachmentPatch'
EasyExtensions::PatchManager.register_model_patch 'Attachment::Version', 'EasyPatch::AttachmentVersionPatch'
