require 'os'
require 'rake'

require_relative 'rake_task'

module Glimmer
  class Launcher
    OPERATING_SYSTEMS_SUPPORTED = ["mac", "windows", "linux"]
    
    TEXT_USAGE_PREFIX = <<~MULTI_LINE_STRING
      Usage: glimmer [--debug] [--log-level=VALUE] [[ENV_VAR=VALUE]...] [[-jruby-option]...] (application.rb or task[task_args]) [[application2.rb]...]
    
      Runs Glimmer applications/tasks.
    
      Either a single task or one or more applications may be specified.
    
      When a task is specified, it runs via rake. Some tasks take arguments in square brackets.
    
      Available tasks are below (you may also lookup by adding `require 'glimmer/rake_task'` in Rakefile and running rake -T):
    MULTI_LINE_STRING

    TEXT_USAGE_SUFFIX = <<~MULTI_LINE_STRING
    
      When applications are specified, they are run using JRuby, 
      automatically preloading the glimmer Ruby gem and SWT jar dependency.
    
      Optionally, extra Glimmer options, JRuby options and environment variables may be passed in.
    
      Glimmer options:
      - "--debug"           : Displays extra debugging information and passes "--debug" to JRuby
      - "--log-level=VALUE" : Sets Glimmer's Ruby logger level ("ERROR" / "WARN" / "INFO" / "DEBUG"; default is "WARN")
    
      Example: glimmer samples/hello_world.rb
    
      This runs the Glimmer application samples/hello_world.rb
    MULTI_LINE_STRING

    GLIMMER_LIB_LOCAL = File.expand_path(File.join(__FILE__, '..', '..', 'glimmer.rb'))
    GLIMMER_LIB_GEM = 'glimmer'
    GLIMMER_OPTIONS = %w[--log-level]
    GLIMMER_OPTION_ENV_VAR_MAPPING = {
      '--log-level' => 'GLIMMER_LOGGER_LEVEL'
    }
    REGEX_RAKE_TASK_WITH_ARGS = /^([^\[]+)\[?([^\]]*)\]?$/

    @@mutex = Mutex.new

    class << self
      def platform_os
        OPERATING_SYSTEMS_SUPPORTED.detect {|os| OS.send("#{os}?")}
      end

      def swt_jar_file
        @swt_jar_file ||= File.expand_path(File.join(__FILE__, '..', '..', '..', 'vendor', 'swt', platform_os, 'swt.jar'))
      end

      def jruby_os_specific_options
        OS.mac? ? "-J-XstartOnFirstThread" : ""
      end

      def jruby_swt_options
        "#{jruby_os_specific_options} -J-classpath \"#{swt_jar_file}\""
      end

      def glimmer_lib
        @@mutex.synchronize do
          unless @glimmer_lib
            @glimmer_lib = GLIMMER_LIB_GEM
            glimmer_gem_listing = `jgem list #{GLIMMER_LIB_GEM}`.split("\n").map {|l| l.split.first}
            if !glimmer_gem_listing.include?(GLIMMER_LIB_GEM) && File.exists?(GLIMMER_LIB_LOCAL)
              @glimmer_lib = GLIMMER_LIB_LOCAL
              puts "[DEVELOPMENT MODE] (detected #{@glimmer_lib})"
            end
          end
        end
        @glimmer_lib
      end

      def glimmer_option_env_vars(glimmer_options)
        glimmer_options.reduce({}) do |hash, pair|
          hash.merge(GLIMMER_OPTION_ENV_VAR_MAPPING[pair.first] => pair.last)
        end
      end

      def load_env_vars(env_vars)
        env_vars.each do |key, value|
          ENV[key] = value
        end
      end

      def launch(application, jruby_options: [], env_vars: {}, glimmer_options: {})
        jruby_options_string = jruby_options.join(' ') + ' ' if jruby_options.any?
        env_vars = env_vars.merge(glimmer_option_env_vars(glimmer_options))
        env_vars_string = env_vars.map {|k,v| "#{k}=#{v}"}.join(' ')
        the_glimmer_lib = glimmer_lib
        devmode_require = nil
        if the_glimmer_lib == GLIMMER_LIB_LOCAL
          devmode_require = '-r puts_debuggerer '
        end
        rake_tasks = Rake.application.tasks.map(&:to_s).map {|t| t.sub('glimmer:', '')}
        potential_rake_task_parts = application.match(REGEX_RAKE_TASK_WITH_ARGS)
        application = potential_rake_task_parts[1]
        rake_task_args = potential_rake_task_parts[2].split(',')
        if rake_tasks.include?(application)
          load_env_vars(glimmer_option_env_vars(glimmer_options))
          rake_task = "glimmer:#{application}"
          puts "Running Glimmer rake task: #{rake_task}" if jruby_options_string.to_s.include?('--debug')
          Rake::Task[rake_task].invoke(*rake_task_args)
        else
          @@mutex.synchronize do
            puts "Launching Glimmer Application: #{application}" unless application.to_s.match(/(irb)|(gladiator)/)
          end
          command = "#{env_vars_string} jruby #{jruby_options_string}#{jruby_os_specific_options} #{devmode_require}-r #{the_glimmer_lib} -S #{application}"
          puts command if jruby_options_string.to_s.include?('--debug')
          system command
        end
      end
    end

    def initialize(raw_options)
      @application_paths = extract_application_paths(raw_options)
      @env_vars = extract_env_vars(raw_options)
      @glimmer_options = extract_glimmer_options(raw_options)
      @jruby_options = raw_options
    end

    def launch
      if @application_paths.empty?
        display_usage
      else
        launch_application
      end
    end

    private

    def launch_application
      load File.expand_path('./Rakefile') if File.exist?(File.expand_path('./Rakefile'))
      threads = @application_paths.map do |application_path|
        Thread.new do
          self.class.launch(
            application_path,
            jruby_options: @jruby_options,
            env_vars: @env_vars,
            glimmer_options: @glimmer_options
          )
        end
      end
      threads.each(&:join)
    end

    def display_usage
      rake_tasks = `rake -T`.gsub('rake glimmer:', 'glimmer ').split("\n").select {|l| l.start_with?('glimmer ')}
      puts TEXT_USAGE_PREFIX
      puts rake_tasks.join("\n")
      puts TEXT_USAGE_SUFFIX
    end

    def extract_application_paths(options)
      options.select do |option|
        !option.start_with?('-') && !option.include?('=')
      end.each do |application_path|
        options.delete(application_path)
      end
    end

    def extract_env_vars(options)
      options.select do |option|
        !option.start_with?('-') && option.include?('=')
      end.each do |env_var|
        options.delete(env_var)
      end.reduce({}) do |hash, env_var_string|
        match = env_var_string.match(/^([^=]+)=(.+)$/)
        hash.merge(match[1] => match[2])
      end
    end

    def extract_glimmer_options(options)
      options.select do |option|
        GLIMMER_OPTIONS.reduce(false) do |result, glimmer_option|
          result || option.include?(glimmer_option)
        end
      end.each do |glimmer_option|
        options.delete(glimmer_option)
      end.reduce({}) do |hash, glimmer_option_string|
        match = glimmer_option_string.match(/^([^=]+)=?(.*)$/)
        hash.merge(match[1] => match[2])
      end
    end
  end
end