module EasyPatch
  module AttachmentsControllerPatch

    def self.included(base)

      base.class_eval do
        base.send(:include, InstanceMethods)

        helper CustomFieldsHelper

        before_action :find_attachment, :only => [:show, :download, :thumbnail, :destroy, :bulk_download_as_zip, :attachment_custom_fields]
        before_action :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
        before_action :delete_authorize, :only => :destroy
        before_action :authorize_global, :only => :upload
        before_action :mark_as_read, :only => [:show, :download]

        skip_before_action [:ensure_easy_attendance, :clear_used_stylesheets, :add_my_page_tabs_to_top_menu_if_needed], :only => [:thumbnail, :download, :bulk_download_as_zip]

        #        cache_sweeper :my_page_others_documents_sweeper

        alias_method_chain :destroy, :easy_extensions
        alias_method_chain :find_attachment, :easy_extensions
        alias_method_chain :upload, :easy_extensions
        alias_method_chain :show, :easy_extensions
        alias_method_chain :download, :easy_extensions
        alias_method_chain :thumbnail, :easy_extensions

        accept_api_auth_actions << :attach

        def add_my_page_tabs_to_top_menu_if_needed
          Rails.logger.error('This place is corrupted and unholy  !!!')
          true
        end

        # Destroy version of attachment
        def destroy_version
          av = Attachment::Version.find(params[:id])
          @attachment = av.attachment
          if @attachment.container
            if @attachment.container.respond_to?(:init_journal)
              @attachment.container.init_journal(User.current)
            end
            if @attachment.versions.count <= 1
              @attachment.container.attachments.delete(@attachment)
            elsif @attachment.container.respond_to?(:current_journal)
              @attachment.container.current_journal.details << JournalDetail.new(
                :property => 'attachment_version',
                :prop_key => av.id,
                :old_value => av.filename
              )
              @attachment.container.current_journal.save
            end
          end
          # Make sure association callbacks are called
          av.destroy
          flash[:notice] = l(:notice_successful_delete)
          redirect_back_or_default(home_path)
        rescue ActiveRecord::RecordNotFound
          render_404
        end

        # Revert attachment version to select version
        def revert_to_version
          if Attachment.find(params[:id]).revert_to!(params[:version_num].to_i)
            flash[:notice] = l('attachments.revert_to.successfully', :version => params[:version_num])
          else
            flash[:error] = l('attachments.revert_to.failed', :version => params[:version_num], :current_v => @attachment.version)
          end
          redirect_back_or_default(home_path)
        end

        def new
          @container = params[:entity_type].classify.constantize.find(params[:entity_id])
          respond_to do |format|
            format.js
          end
        end

        def new_version
          @attachment = Attachment.find(params[:id])
          @container = @attachment.container
          params[:custom_version_for_attachment_id] = @attachment.id

          respond_to do |format|
            format.js { render(:action => 'new') }
          end
        end

        def attach
          if params[:attach].present?
            attachments_params = params[:attach]
          else
            attachments_params = params
          end

          entity_type = attachments_params.delete(:entity_type)
          entity_id = attachments_params.delete(:entity_id)
          @container = entity_type.classify.constantize.find(entity_id)
          @container.init_journal(User.current) if @container.respond_to?(:init_journal)
          @attached = Attachment.attach_files(@container, attachments_params[:attachments])
          @container.current_journal.save if @container.respond_to?(:current_journal) && @container.current_journal
          files = @attached[:files] + @attached[:new_versions]
          Mailer.attachments_added(files).deliver if @container.is_a?(Document) && files.present? && Setting.notified_events.include?('file_added')
          render_attachment_warning_if_needed(@container) unless request.xhr?

          respond_to do |format|
            format.html {
              redirect_back_or_default(begin
                                         polymorphic_path(@container);
                                       rescue;
                                         home_path
                                       end)
            }
            format.js
            format.api
          end
        rescue ActiveRecord::RecordNotFound, NameError
          render_404
        end

        def webdav_modal
          return false if find_attachment == false

          respond_to do |format|
            format.js
          end
        end

        private

        def mark_as_read
          @attachment.mark_as_read(User.current) if @attachment
        end

        def message
          return if @attachment.nil?
          if @attachment.is_message?
            @email = Mail.new(File.binread(@attachment.diskfile))
          elsif @attachment.content_type == 'application/vnd.ms-outlook' && (eml = EasyExtensions::EasyMsgReader.new(@attachment.diskfile).to_eml)
            @email = Mail.new(eml)
          end
        end

        def message_short_cid(cid)
          i = cid.index('_')
          cid[0, i] if i
        end
      end

    end

    module InstanceMethods

      def destroy_with_easy_extensions
        if @attachment.container.respond_to?(:init_journal)
          @attachment.container.init_journal(User.current)
        end
        if @attachment.container
          # Make sure association callbacks are called
          @attachment.container.attachments.delete(@attachment)
        else
          @attachment.destroy
        end

        respond_to do |format|
          format.html { redirect_to_referer_or(@project.nil? ? home_path : project_path(@project)) }
          format.js
        end
      end

      def thumbnail_with_easy_extensions
        if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size])
          if stale?(:etag => tbnail)
            # fix for the Anti-MIME-Sniffing header X-Content-Type-Options = 'nosniff'
            content_type = if File.extname(tbnail) == '.thumb'
              MIME::Types.type_for('JPG').first.to_s # we always generate JPGs as thumbnails, see generate_with_easy_extensions
            else
              # if the original file is used as thumbnail
              detect_content_type(@attachment)
            end

            send_file tbnail,
              :filename => filename_for_content_disposition(@attachment.filename),
              :type => content_type,
              :disposition => 'inline'
          end
        else
          # No thumbnail for the attachment or thumbnail could not be created
          render :nothing => true, :status => 404
        end
      end

      def upload_with_easy_extensions
        # Make sure that API users get used to set this content type
        # as it won't trigger Rails' automatic parsing of the request body for parameters
        unless request.content_type == 'application/octet-stream'
          render_406
          return
        end
        @attachment = Attachment.new(:file => request.raw_post)
        @attachment.author = User.current
        @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
        @attachment.skip_description_required = true

        saved = @attachment.save

        @attachment_for_custom_fields = Attachment.where(:id => params[:custom_version_for_attachment_id]).first_or_initialize

        respond_to do |format|
          format.js
          format.api {
            if saved
              render :action => 'upload', :status => :created
            else
              render_validation_errors(@attachment)
            end
          }
        end
      end

      def show_with_easy_extensions
        file_size_displayed = @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
        if file_size_displayed && message
          respond_to do |format|
            format.html do
              part = (Setting.text_formatting == 'HTML') ? (@email.html_part || @email.text_part) : (@email.text_part || @email.html_part)
              part ||= @email if @email.text?
              if part
                encoding = Mail::RubyVer.respond_to?(:pick_encoding) ? Mail::RubyVer.pick_encoding(part.charset).to_s : part.charset
                body = part.body.decoded
                @content = begin
                  convert_to_utf8(body, encoding)
                rescue *Redmine::CodesetUtil::ENCODING_EXCEPTIONS
                  Redmine::CodesetUtil.replace_invalid_utf8(body)
                end
              end

              @content ||= ''
              @subject = @email.subject
              @attachments = @email.attachments

              if params[:content_filename] || params[:cid]
                fn = params[:content_filename].to_s
                cid = params[:cid].to_s
                short_cid = message_short_cid(cid)
                attachment = @attachments.detect do |a|
                  a.cid == cid ||
                    (!short_cid.nil? && (message_short_cid(a.cid) == short_cid) && a.filename == fn) ||
                    a.filename == fn
                end
                attachment ||= @attachments.detect { |a| a.filename == fn }
              end

              if attachment
                tmp = Tempfile.new(File.basename(attachment.cid.to_s))
                begin
                  tmp.binmode
                  tmp.write(attachment.body.decoded.to_s)
                  disposition = attachment.inline? ? 'inline' : 'attachment'
                  send_file tmp.path,
                            :filename => filename_for_content_disposition(attachment.filename),
                            :type => attachment.content_type,
                            :disposition => disposition
                ensure
                  tmp.close if tmp
                end
              else
                render :action => 'message'
              end
            end
            format.api
          end
        else
          if @attachment.is_diff? || (@attachment.is_text? && file_size_displayed) || @attachment.is_image? || api_request?
            show_without_easy_extensions
          else
            download
          end
        end
      end

      def download_with_easy_extensions
        @attachment.increment_download
        if stale?(:etag => @attachment.digest)
          # images are sent inline
          send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
                    :type => detect_content_type(@attachment),
                    :disposition => disposition(@attachment)
        end
      end

      def bulk_download_as_zip
        if @attachments.blank?
          flash[:error] = l('attachments.attachments_missing')
          redirect_back_or_default(easy_invoices_path)
        else
          prefix = @attachments.first.container_type.present? ? @attachments.first.container_type.underscore.pluralize : 'attachments'
          zip_filename = prefix+'_'+Time.now.strftime("%Y-%m-%d")+'.zip'
          temp_zip_file = Tempfile.new(zip_filename)
          added_files = 0
          @missing_files = []

          begin
            Zip::OutputStream.open(temp_zip_file)
            Zip::File.open(temp_zip_file.path, Zip::File::CREATE) do |zip|
              @attachments.each do |attachment|
                begin
                  if File.exist?(attachment.diskfile) && File.readable?(attachment.diskfile)
                    zip.add(attachment.filename, attachment.diskfile)
                    added_files += 1
                  else
                    @missing_files << attachment.disk_filename
                  end
                rescue Zip::EntryExistsError
                  zip.add(attachment.disk_filename, attachment.diskfile)
                  added_files += 1
                end
              end
            end

            if added_files != 0
              send_data(File.read(temp_zip_file), :type => 'application/zip', :filename => zip_filename)
            else
              flash[:error] = l('attachments.attachments_missing')
              redirect_back_or_default(easy_invoices_path)
            end
          ensure
            temp_zip_file.close
            temp_zip_file.unlink
          end
        end
      end

      def attachment_custom_fields
        render :layout => !request.xhr?
      end

      private

      def find_attachment_with_easy_extensions
        scope = params[:version] ? Attachment::Version : Attachment
        @attachments = scope.find(params[:ids]) if params[:ids]
        @attachment = @attachments ? @attachments.first : scope.find(params[:id])
        # Show 404 if the filename in the url is wrong
        raise ActiveRecord::RecordNotFound if @attachment && (params[:filename] && params[:filename] != @attachment.filename)
        @project = @attachment.project
      rescue ActiveRecord::RecordNotFound, NameError
        render_404
      end

      def convert_to_utf8(str, encoding)
        if !str.nil? && encoding.to_s.downcase == 'utf-7' && Net::IMAP.respond_to?(:decode_utf7)
          str.force_encoding('UTF-8')
          Redmine::CodesetUtil.to_utf8(Net::IMAP.decode_utf7(str), 'UTF-8')
        else
          Redmine::CodesetUtil.to_utf8(str, encoding)
        end
      end

    end

  end

end
EasyExtensions::PatchManager.register_controller_patch 'AttachmentsController', 'EasyPatch::AttachmentsControllerPatch'
