require "sprockets/environment"
require "teaspoon/coverage"

module Teaspoon
  class Instrumentation
    extend Teaspoon::Utility

    def self.add_to(response, env)
      return response unless add?(response, env)
      Teaspoon::Instrumentation.new(response).instrumented_response
    end

    def self.add?(response, env)
      executable &&                                                     # we have an executable
        env["QUERY_STRING"].to_s =~ /instrument=(1|true)/ &&            # the instrument param was provided
        response[0] == 200 &&                                           # the status is 200 (304 maybe?)
        response[1]["Content-Type"].to_s == "application/javascript" && # the format is something that we care about
        response[2].respond_to?(:source) &&                             # it looks like an asset
        !ignored?(response[2])                                          # it is not ignored
    end

    def self.executable
      return @executable if @executable_checked
      @executable_checked = true
      @executable = which("istanbul")
    end

    def initialize(response)
      @response = response
    end

    def instrumented_response
      status, headers, asset = @response
      headers, asset = [headers.clone, asset.clone]

      result = add_instrumentation(asset)

      headers["Content-Length"] = result.bytesize.to_s
      [status, headers, [result]]
    end

    protected

      def self.ignored?(asset)
        Array(Teaspoon::Coverage.configuration.ignore).any? do |ignore|
          asset.filename.match(ignore)
        end
      rescue Teaspoon::UnknownCoverage
        false
      end

      def add_instrumentation(asset)
        source_path = asset.filename
        Dir.mktmpdir do |temp_path|
          input_path = File.join(temp_path, File.basename(source_path)).sub(/\.js.+/, ".js")
          File.open(input_path, "w") { |f| f.write(asset.source) }
          instrument(input_path).gsub(input_path, source_path)
        end
      end

      def instrument(input)
        result = %x{#{self.class.executable} instrument --embed-source #{input.shellescape}}
        return result if $?.exitstatus == 0
        raise Teaspoon::DependencyError.new("Unable to add instrumentation to #{File.basename(input)}.")
      end
  end

  module SprocketsInstrumentation
    def call(env)
      Teaspoon::Instrumentation.add_to(super, env)
    end
  end
end
