require "log4r"

require "vagrant/util/safe_puts"

module Vagrant
  module UI
    # Vagrant UIs handle communication with the outside world (typically
    # through a shell). They must respond to the following methods:
    #
    # * `info`
    # * `warn`
    # * `error`
    # * `success`
    class Interface
      attr_accessor :resource

      def initialize(resource)
        @logger   = Log4r::Logger.new("vagrant::ui::interface")
        @resource = resource
      end

      [:ask, :warn, :error, :info, :success].each do |method|
        define_method(method) do |message, *opts|
          # Log normal console messages
          @logger.info { "#{method}: #{message}" }
        end
      end

      [:clear_line, :report_progress].each do |method|
        # By default do nothing, these aren't logged
        define_method(method) { |*args| }
      end
    end

    # This is a UI implementation that does nothing.
    class Silent < Interface
      def ask(*args)
        super

        # Silent can't do this, obviously.
        raise Errors::UIExpectsTTY
      end
    end

    # This is a UI implementation that outputs the text as is. It
    # doesn't add any color.
    class Basic < Interface
      include Util::SafePuts

      # Use some light meta-programming to create the various methods to
      # output text to the UI. These all delegate the real functionality
      # to `say`.
      [:info, :warn, :error, :success].each do |method|
        class_eval <<-CODE
          def #{method}(message, *args)
            super(message)
            say(#{method.inspect}, message, *args)
          end
        CODE
      end

      def ask(message, opts=nil)
        super(message)

        # We can't ask questions when the output isn't a TTY.
        raise Errors::UIExpectsTTY if !$stdin.tty?

        # Setup the options so that the new line is suppressed
        opts ||= {}
        opts[:new_line] = false if !opts.has_key?(:new_line)
        opts[:prefix]   = false if !opts.has_key?(:prefix)

        # Output the data
        say(:info, message, opts)

        # Get the results and chomp off the newline
        $stdin.gets.chomp
      end

      # This is used to output progress reports to the UI.
      # Send this method progress/total and it will output it
      # to the UI. Send `clear_line` to clear the line to show
      # a continuous progress meter.
      def report_progress(progress, total, show_parts=true)
        if total && total > 0
          percent = (progress.to_f / total.to_f) * 100
          line    = "Progress: #{percent.to_i}%"
          line   << " (#{progress} / #{total})" if show_parts
        else
          line    = "Progress: #{progress}"
        end

        info(line, :new_line => false)
      end

      def clear_line
        reset = "\r"
        reset += "\e[0K" unless Util::Platform.windows?
        reset

        info(reset, :new_line => false)
      end

      # This method handles actually outputting a message of a given type
      # to the console.
      def say(type, message, opts=nil)
        defaults = { :new_line => true, :prefix => true }
        opts     = defaults.merge(opts || {})

        # Determine whether we're expecting to output our
        # own new line or not.
        printer = opts[:new_line] ? :puts : :print

        # Determine the proper IO channel to send this message
        # to based on the type of the message
        channel = type == :error || opts[:channel] == :error ? $stderr : $stdout

        # Output!
        safe_puts(format_message(type, message, opts),
                  :io => channel, :printer => printer)
      end

      # This is called by `say` to format the message for output.
      def format_message(type, message, opts=nil)
        opts ||= {}
        message = "[#{@resource}] #{message}" if opts[:prefix]
        message
      end
    end

    # This is a UI implementation that outputs color for various types
    # of messages. This should only be used with a TTY that supports color,
    # but is up to the user of the class to verify this is the case.
    class Colored < Basic
      # Terminal colors
      COLORS = {
        :clear  => "\e[0m",
        :red    => "\e[31m",
        :green  => "\e[32m",
        :yellow => "\e[33m"
      }

      # Mapping between type of message and the color to output
      COLOR_MAP = {
        :warn    => COLORS[:yellow],
        :error   => COLORS[:red],
        :success => COLORS[:green]
      }

      # This is called by `say` to format the message for output.
      def format_message(type, message, opts=nil)
        # Get the format of the message before adding color.
        message = super

        # Colorize the message if there is a color for this type of message,
        # either specified by the options or via the default color map.
        if opts.has_key?(:color)
          color   = COLORS[opts[:color]]
          message = "#{color}#{message}#{COLORS[:clear]}"
        else
          message = "#{COLOR_MAP[type]}#{message}#{COLORS[:clear]}" if COLOR_MAP[type]
        end

        message
      end
    end
  end
end
