require 'fileutils'
require 'bluepill/system'

module Bluepill
  class Controller
    attr_accessor :base_dir, :log_file, :sockets_dir, :pids_dir

    def initialize(options = {})
      self.log_file = options[:log_file]
      self.base_dir = options[:base_dir]
      self.sockets_dir = File.join(base_dir, 'socks')
      self.pids_dir = File.join(base_dir, 'pids')

      setup_dir_structure
      cleanup_bluepill_directory
    end

    def running_applications
      Dir[File.join(sockets_dir, '*.sock')].collect { |x| File.basename(x, '.sock') }
    end

    def handle_command(application, command, *args)
      case command.to_sym
      when :status
        puts send_to_daemon(application, :status, *args)
      when :quit
        pid = pid_for(application)
        if System.pid_alive?(pid)
          ::Process.kill('TERM', pid)
          puts "Killing bluepilld[#{pid}]"
        else
          puts "bluepilld[#{pid}] not running"
        end
      when :log
        log_file_location = send_to_daemon(application, :log_file)
        log_file_location = log_file if log_file_location.to_s.strip.empty?

        requested_pattern = args.first
        grep_pattern = self.grep_pattern(application, requested_pattern)

        tail = "tail -n 100 -f #{log_file_location} | grep -E '#{grep_pattern}'"
        puts "Tailing log for #{requested_pattern}..."
        Kernel.exec(tail)
      when *Application::PROCESS_COMMANDS
        # these need to be sent to the daemon and the results printed out
        affected = send_to_daemon(application, command, *args)
        if affected.empty?
          puts 'No processes effected'
        else
          puts "Sent #{command} to:"
          affected.each do |process|
            puts "  #{process}"
          end
        end
      else
        $stderr.puts(format('Unknown command `%s` (or application `%s` has not been loaded yet)', command, command))
        exit(1)
      end
    end

    def send_to_daemon(application, command, *args)
      verify_version!(application)

      command = [command, *args].join(':')
      response = Socket.client_command(base_dir, application, command)
      if response.is_a?(Exception)
        $stderr.puts 'Received error from server:'
        $stderr.puts response.inspect
        $stderr.puts response.backtrace.join("\n")
        exit(8)
      else
        response
      end

    rescue Errno::ECONNREFUSED
      abort('Connection Refused: Server is not running')
    end

    def grep_pattern(application, query = nil)
      pattern = [application, query].compact.join(':')
      ['\[.*', Regexp.escape(pattern), '.*'].compact.join
    end

  private

    def cleanup_bluepill_directory
      running_applications.each do |app|
        pid = pid_for(app)
        next if pid && System.pid_alive?(pid)
        pid_file = File.join(pids_dir, "#{app}.pid")
        sock_file = File.join(sockets_dir, "#{app}.sock")
        System.delete_if_exists(pid_file)
        System.delete_if_exists(sock_file)
      end
    end

    def pid_for(app)
      pid_file = File.join(pids_dir, "#{app}.pid")
      File.exist?(pid_file) && File.read(pid_file).to_i
    end

    def setup_dir_structure
      [@sockets_dir, @pids_dir].each do |dir|
        FileUtils.mkdir_p(dir) unless File.exist?(dir)
      end
    end

    def verify_version!(application)
      version = Socket.client_command(base_dir, application, 'version')
      if version != Bluepill::Version
        abort("The running version of your daemon seems to be out of date.\nDaemon Version: #{version}, CLI Version: #{Bluepill::Version}")
      end
    rescue ArgumentError
      abort('The running version of your daemon seems to be out of date.')
    end
  end
end
