require 'digest'
require 'fileutils'
require 'tempfile'
require 'sym'
require 'sym/errors'
require 'sym/app/commands/base_command'
module Sym
  module App
    module Commands
      class OpenEditor < BaseCommand
        include Sym

        required_options [ :key, :interactive ],
                           :edit,
                           :file

        try_after :generate_key, :encrypt, :decrypt

        attr_accessor :tempfile

        def execute
          begin
            self.tempfile = ::Tempfile.new(::Base64.urlsafe_encode64(opts[:file]))
            decrypt_content(self.tempfile)

            result = process launch_editor
          ensure
            self.tempfile.close if tempfile
            self.tempfile.unlink rescue nil
          end
          result
        end

        def launch_editor
          system("#{application.editor} #{tempfile.path}")
        end

        private

        def decrypt_content(file)
          file.open
          file.write(content)
          file.flush
        end

        def content
          @content ||= decr(File.read(opts[:file]), key)
        end

        def timestamp
          @timestamp ||= Time.now.to_a.select { |d| d.is_a?(Integer) }.map { |d| '%02d' % d }[0..-3].reverse.join
        end

        def process(code)
          if code == true
            content_edited = File.read(tempfile.path)
            md5            = ::Base64.encode64(Digest::MD5.new.digest(content))
            md5_edited     = ::Base64.encode64(Digest::MD5.new.digest(content_edited))
            return 'No changes have been made.' if md5 == md5_edited

            FileUtils.cp opts[:file], "#{opts[:file]}.#{timestamp}" if opts[:backup]

            diff = compute_diff

            File.write(opts[:file], encr(content_edited, key))

            out = ''
            if opts[:verbose]
              out << ("Saved encrypted/compressed content to #{opts[:file].bold.blue}" +
                      " (#{File.size(opts[:file]) / 1024}Kb), unencrypted size #{content.length / 1024}Kb.")
              out << (opts[:backup] ? ",\nbacked up the last version to #{backup_file.bold.blue}." : '.')
            end
            out << "\n\nDiff:\n#{diff}"
            out
          else
            raise Sym::Errors::EditorExitedAbnormally.new("#{application.editor} exited with #{$<}")
          end
        end

        # Computes the diff between two unencrypted versions
        def compute_diff
          original_content_file = Tempfile.new(rand(1024).to_s)
          original_content_file.open
          original_content_file.write(content)
          original_content_file.flush
          diff = `diff #{original_content_file.path} #{tempfile.path}`
          diff.gsub!(/> (.*\n)/m, '\1'.green)
          diff.gsub!(/< (.*\n)/m, '\1'.red)
        ensure
          original_content_file.close
          original_content_file.unlink
          diff
        end
      end
    end
  end
end
