module Birdwatcher
  class Console
    include Singleton

    DEFAULT_AUTO_COMPLETION_STRINGS = [].freeze
    DB_MIGRATIONS_PATH              = File.expand_path("../../../db/migrations", __FILE__).freeze
    LINE_SEPARATOR                  = ("=" * 80).freeze
    HISTORY_FILE_NAME               = ".birdwatcher_history".freeze
    HISTORY_FILE_LOCATION           = File.join(Dir.home, HISTORY_FILE_NAME).freeze

    attr_accessor :current_workspace, :current_module, :spool
    attr_reader :database

    def initialize
      @output_mutex = Mutex.new
      @spool_mutex  = Mutex.new
    end

    def start!
      print_banner
      bootstrap!
      Readline.completion_proc = proc do |s|
        expanded_s = File.expand_path(s)
        Birdwatcher::Console.instance.auto_completion_strings.grep(/\A#{Regexp.escape(s)}/) + Dir["#{expanded_s}*"].grep(/^#{Regexp.escape(expanded_s)}/)
      end
      Readline.completion_append_character = ""
      load_command_history
      while input = Readline.readline(prompt_line, true)
        save_to_spool(prompt_line)
        input = input.to_s.strip
        handle_input(input) unless input.empty?
      end
    end

    def handle_input(input)
      input.strip!
      save_command_to_history(input)
      save_to_spool("#{input}\n")
      command_name, argument_line = input.split(" ", 2).map(&:strip)
      command_name.downcase
      commands.each do |command|
        next unless command.has_name?(command_name)
        command.new.execute(argument_line)
        return true
      end
      error("Unknown command: #{command_name.bold}")
      false
    end

    def auto_completion_strings
      if !@auto_completion_strings
        @auto_completion_strings = DEFAULT_AUTO_COMPLETION_STRINGS
        commands.each { |c| @auto_completion_strings += c.auto_completion_strings }
        @auto_completion_strings += Birdwatcher::Module.module_paths
      end
      @auto_completion_strings
    end

    def output(data, newline = true)
      data = "#{data}\n" if newline
      with_output_mutex { print data }
      save_to_spool(data)
    end

    def output_formatted(*args)
      output(sprintf(*args), false)
    end

    def newline
      output ""
    end

    def line_separator
      output LINE_SEPARATOR
    end

    def info(message)
      output "[+] ".bold.light_blue + message
    end

    def task(message, fatal = false, &block)
      output("[+] ".bold.light_blue + message, false)
      yield block
      output " done".bold.light_green
    rescue => e
      output " failed".bold.light_red
      error "#{e.class}: ".bold + e.message
      e.backtrace.each { |l| error l } if debugging_enabled?
      exit(1) if fatal
    end

    def error(message)
      output "[-] ".bold.light_red + message
    end

    def warn(message)
      output "[!] ".bold.light_yellow + message
    end

    def fatal(message)
      output "[-]".white.bold.on_red + " #{message}"
    end

    def confirm(question)
      question = "#{question} (y/n) "
      save_to_spool(question)
      if HighLine.agree("#{question}")
        save_to_spool("y\n")
        true
      else
        save_to_spool("n\n")
        false
      end
    end

    def page_text(text)
      save_to_spool(text)
      ::TTY::Pager::SystemPager.new.page(text)
    rescue Errno::EPIPE
    end

    def twitter_client
      if !@twitter_clients
        @twitter_clients = create_twitter_clients!
      end
      @twitter_clients.sample
    end

    def klout_client
      if !@klout_clients
        @klout_clients = create_klout_clients!
      end
      @klout_clients.sample
    end

    private

    def print_banner
      output " ___ _        _             _      _\n" \
             "| _ |_)_ _ __| |_ __ ____ _| |_ __| |_  ___ _ _\n" \
             "| _ \\ | '_/ _` \\ V  V / _` |  _/ _| ' \\/ -_) '_|\n" \
             "|___/_|_| \\__,_|\\_/\\_/\\__,_|\\__\\__|_||_\\___|_|\n".bold.light_blue +
             "                       v#{Birdwatcher::VERSION} by " + "@michenriksen\n".bold
    end

    def bootstrap!
      bootstrap_configuration!
      bootstrap_database!
      newline
    end

    def bootstrap_configuration!
      if !Birdwatcher::Configuration.configured?
        Birdwatcher::ConfigurationWizard.new.start!
      end
      task "Loading configuration...", true do
        Birdwatcher::Configuration.load!
      end
    end

    def bootstrap_database!
      task "Preparing database...", true do
        Sequel.extension :migration, :core_extensions
        @database = Sequel.connect(configuration.get!(:database_connection_uri))
        Sequel::Migrator.run(@database, DB_MIGRATIONS_PATH)
        Sequel::Model.db = @database
        Sequel::Model.plugin :timestamps
        bootstrap_models!
        load_default_workspace!
      end
    end

    def bootstrap_models!
      Dir[File.join(File.dirname(__FILE__), "..", "..", "models", "*.rb")].each do |file|
        require file
      end
    end

    def prompt_line
      prompt = "birdwatcher[".bold + "#{current_workspace.name}" + "]".bold
      if current_module
        prompt += "[".bold + current_module.path.light_red + "]> ".bold
      else
        prompt += "> ".bold
      end
      prompt
    end

    def load_default_workspace!
      @current_workspace = Birdwatcher::Models::Workspace.find_or_create(
        :name => Birdwatcher::Models::Workspace::DEFAULT_WORKSPACE_NAME
      ) do |w|
        w.description = Birdwatcher::Models::Workspace::DEFAULT_WORKSPACE_DESCRIPTION
      end
    end

    def commands
      @commands ||= Birdwatcher::Command.descendants
    end

    def configuration
      Birdwatcher::Configuration
    end

    def with_output_mutex
      @output_mutex.synchronize { yield }
    end

    def with_spool_mutex
      @spool_mutex.synchronize { yield }
    end

    def create_twitter_clients!
      clients = []
      configuration.get!(:twitter).each do |keypair|
        clients << Twitter::REST::Client.new do |config|
          config.consumer_key    = keypair["consumer_key"]
          config.consumer_secret = keypair["consumer_secret"]
        end
      end
      clients
    end

    def create_klout_clients!
      clients = []
      configuration.get(:klout).each do |key|
        clients << Birdwatcher::KloutClient.new(key)
      end
      clients
    end

    def load_command_history
      if File.exist?(HISTORY_FILE_LOCATION)
        if File.readable?(HISTORY_FILE_LOCATION)
          File.open(HISTORY_FILE_LOCATION).each_line do |command|
            Readline::HISTORY << command.strip
          end
        else
          warn("Cannot load command history: #{HISTORY_FILE_LOCATION} is not readable")
        end
      end
    end

    def save_command_to_history(command)
      if File.exist?(HISTORY_FILE_LOCATION) && !File.writable?(HISTORY_FILE_LOCATION)
        warn("Cannot save command to history: #{HISTORY_FILE_LOCATION} is not writable")
        return
      end
      File.open(HISTORY_FILE_LOCATION, "a") do |file|
        file.puts(command)
      end
    end

    def save_to_spool(string)
      return unless spool_enabled?
      string = string.to_s.uncolorize
      with_spool_mutex { self.spool.write(string) }
    end

    def spool_enabled?
      self.spool && self.spool.is_a?(File)
    end

    def debugging_enabled?
      ENV.key?("BIRDWATCHER_DEBUG")
    end
  end
end
