# frozen_string_literal: true

## --- BEGIN LICENSE BLOCK ---
# Copyright (c) 2016-present WeWantToKnow AS
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
## --- END LICENSE BLOCK ---

require 'u3d/utils'
require 'u3d_core/core_ext/string'
require 'fileutils'
require 'file-tail'

module U3d
  # Launches Unity with given arguments
  class Runner
    def run(installation, args, raw_logs: false)
      log_file = find_and_prepare_logfile(installation, args)

      if raw_logs
        output_callback = proc do |line|
          UI.command_output(line.rstrip)
        end
      else
        analyzer = LogAnalyzer.new
        output_callback = proc do |line|
          analyzer.parse_line(line)
        end
      end

      if log_file
        tail_thread = start_tail_thread(log_file, output_callback)
        return unless tail_thread.status

        tail_thread.run
      end

      begin
        args.unshift(installation.exe_path)
        args.map!(&:argescape)

        U3dCore::CommandExecutor.execute_command(command: args, output_callback: output_callback)
      ensure
        if tail_thread
          sleep 1
          Thread.kill(tail_thread)
        end
      end
    end

    def find_and_prepare_logfile(installation, args)
      log_file = Runner.find_logFile_in_args(args)

      return nil if log_file == '/dev/stdout'

      if log_file # we wouldn't want to do that for the default log file.
        File.delete(log_file) if File.file?(log_file) # We only delete real files
      else
        log_file = installation.default_log_file
      end

      Utils.ensure_dir File.dirname(log_file)
      FileUtils.touch(log_file) unless File.exist? log_file
      log_file
    end

    class << self
      # rubocop:disable Naming/MethodName
      def find_logFile_in_args(args)
        # rubocop:enable Naming/MethodName
        find_arg_in_args('-logFile', args)
      end

      def find_projectpath_in_args(args)
        find_arg_in_args('-projectPath', args)
      end

      def find_arg_in_args(arg_to_find, args)
        raise 'Only arguments of type array supported right now' unless args.is_a?(Array)

        args.each_with_index do |arg, index|
          return args[index + 1] if arg == arg_to_find && index < args.count - 1
        end
        nil
      end
    end

    private

    def start_tail_thread(log_file, output_callback)
      tail_thread = Thread.new do
        pipe(log_file) { |line| output_callback.call(line) }
      rescue StandardError => e
        UI.error "Failure while trying to pipe #{log_file}: #{e.message}"
        e.backtrace.each { |l| UI.error "  #{l}" }
      end

      # Wait for tail_thread setup to be complete
      sleep 0.5 while tail_thread.status == 'run'
      tail_thread
    end

    def pipe(file, &block)
      File.open(file, 'r') do |f|
        f.extend File::Tail
        f.interval = 0.1
        f.max_interval = 0.4
        f.backward 0
        Thread.stop
        f.tail(&block)
      end
    end
  end
end
