module Teaspoon
  module Formatter
    class Base
      RESERVED_PARAMS = ["body", "instrument"]

      attr_accessor :total_count, :run_count, :passes, :pendings, :failures, :errors

      def initialize(suite_name = :default, output_file = nil)
        @suite_name  = suite_name.to_s
        @output_file = parse_output_file(output_file)
        @stdout      = ""
        @suite       = nil
        @last_suite  = nil

        @total_count = 0
        @run_count   = 0
        @passes      = []
        @pendings    = []
        @failures    = []
        @errors      = []
        File.open(@output_file, "w") { |f| f.write("") } if @output_file
      end

      # beginning of the run
      def runner(result, log = true)
        @total_count = result.total
        log_runner(result) if log
      end

      # each suite, before any specs
      def suite(result, log = true)
        @suite = result
        log_suite(result) if log
        @last_suite = result
      end

      # each spec, after the spec has reported to the client runner
      def spec(result, log = true)
        @run_count += 1
        if result.passing?
          @passes << result
        elsif result.pending?
          @pendings << result
        else
          @failures << result
        end
        log_spec(result) if log
        @stdout = ""
      end

      # errors are reported from the onError handler in phantomjs, so they're not linked to a result
      def error(result, log = true)
        @errors << result
        log_error(result) if log
      end

      # exception came from startup errors in the server (will exit after logging)
      def exception(result = {}, log = true)
        log_exception(result) if log
      end

      # console message come from console.log/debug/error
      def console(message, log = true)
        @stdout << message
        log_console(message) if log
      end

      # final report
      def result(result, log = true)
        log_result(result) if log
      end

      # called with the text versions of coverage if configured to do so
      def coverage(message, log = true)
        log_coverage(message) if log
      end

      # called with an array of strings which explain which coverage thresholds failed
      def threshold_failure(message, log = true)
        log_threshold_failure(message) if log
      end

      def complete(failure_count, log = true)
        log_complete(failure_count) if log
      end

      protected

        def log_runner(_result); end

        def log_suite(_result); end

        def log_spec(result)
          return log_passing_spec(result) if result.passing?
          return log_pending_spec(result) if result.pending?
          log_failing_spec(result)
        end

        def log_passing_spec(_result); end

        def log_pending_spec(_result); end

        def log_failing_spec(_result); end

        def log_error(_result); end

        def log_exception(_result); end

        def log_console(_message); end

        def log_result(_result); end

        def log_coverage(_message); end

        def log_threshold_failure(_message); end

        def log_complete(_failure_count); end

      private

        def log_str(str, color_code = nil)
          return log_to_file(str, @output_file) if @output_file
          STDOUT.print(color_code ? colorize(str, color_code) : str)
        end

        def log_line(str = "", color_code = nil)
          return log_to_file("#{str}\n", @output_file) if @output_file
          STDOUT.print("#{color_code ? colorize(str, color_code) : str}\n")
        end

        def log_to_file(str, output_file)
          @_output_file = File.open(output_file, "a") { |f| f.write(str) }
        rescue IOError => e
          raise Teaspoon::FileWriteError.new(e.message)
        end

        def colorize(str, color_code)
          return str unless Teaspoon.configuration.color || @output_file
          "\e[#{color_code}m#{str}\e[0m"
        end

        def pluralize(str, value)
          value == 1 ? "#{value} #{str}" : "#{value} #{str}s"
        end

        def filename(file)
          uri = URI(file)

          params = String(uri.query).split("&").reject do |param|
            RESERVED_PARAMS.include?(param.split("=").first)
          end

          filename = uri.path.sub(%r(^/assets/), "")
          filename += "?#{params.join("&")}" if params.any?
          filename
        end

        def parse_output_file(output)
          return output unless output
          output.gsub(/%{([^}]*)}/) { parse_output_capture($1) }
        end

        def parse_output_capture(cap)
          case cap
          when "suite_name"
            @suite_name
          when "date"
            Time.now.to_i
          else
            warn ["Teaspoon::Formatter - Output File can only contain the placeholders %{suite_name} or %{date}.",
                  "%{#{cap}} is not supported and will be ignored."].join("\n")
            ""
          end
        end
    end
  end
end
