#!/usr/bin/env ruby

#
# Main entry point: Load and orchestrate functions, handle global vars
#

min_ruby = '3.1.0'
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(min_ruby)
  puts "\nERROR: Your Version of ruby #{RUBY_VERSION} is lower than minimum required version #{min_ruby}\n\n"
  exit 1
end

require 'set'
require 'yaml'
require 'json'
require 'fileutils'
require 'open3'
require 'tmpdir'
require 'erb'
require 'timeout'
require 'tempfile'
require 'strscan'
require 'pp'  ## for pretty_inspect
require 'csv'
require_relative 'libexec/utils'
require_relative 'libexec/arguments'
require_relative 'libexec/config'
require_relative 'libexec/interaction'
require_relative 'libexec/players'
require_relative 'libexec/sound_driver'
require_relative 'libexec/analyze'
require_relative 'libexec/licks'
require_relative 'libexec/handle_holes'
require_relative 'libexec/mode_listen'
require_relative 'libexec/mode_licks'
require_relative 'libexec/mode_play'
require_relative 'libexec/mode_print'
require_relative 'libexec/mode_samples'
require_relative 'libexec/mode_tools'
require_relative 'libexec/mode_quiz'
require_relative 'libexec/mode_develop'
require_relative 'libexec/mode_jamming'

$program_start = $mode_start = Time.now.to_f
$initialized = false
$on_error_raise = false
# grep .rb-files for 'debug' to see choices, e.g. :check_screen. Still
# need to give '--debug' to switch it on
$debug_what = Set.new([])
# store (ad-hoc) state for debugging
$debug_state = {}
$full_commandline = $0 + ' ' + ARGV.join(' ')

$term_height, $term_width = prepare_screen 

# set these early, because they are used during initialization
$testing, $testing_what = set_testing_vars_mb

# while testing, additionally make sure, that we do not use relative
# paths; do this early, before we even have parsed options
Dir.chdir(Dir.home) if $testing

set_global_vars_early

check_installation

$conf = read_technical_config

# needed for usage
initialize_extra_vars

# get debug info early to help e.g. for error messages
$opts = {debug: true} if ARGV.include?('--debug')
$mode, $type, $key, $scale, $opts = parse_arguments_early
if $testing_what == :argv
  puts JSON.pretty_generate({mode: $mode, type: $type, key: $key, scale: $scale, argv: ARGV})
  exit
end

mostly_avoid_double_invocations


# we need to have mode for this, so calculate it late
$lines = calculate_screen_layout
# Count things and print them on --debug; supplements info from ruby -rprofile
$perfctr = Hash.new {|h,k| h[k] = 0}
  
set_global_vars_late

set_global_musical_vars

$initialized = true

to_handle = parse_arguments_for_mode

check_screen(hint_on_large_term: true) if [:listen, :quiz, :licks].include?($mode)

write_dump 'start' if $testing

# We need this to be true, because otherwise exceptions in started
# threads go unnoticed and may stall the hole program
Thread.report_on_exception = true

Signal.trap('SIGINT') do
  end_all_other_threads  
  print "\e[#{$lines[:message_bottom]}H" if $move_down_on_exit
  fail "\n\n\n\e[0mexit on ctrl-c" if $opts[:debug]
  puts "\n\n\n\e[0mexit on ctrl-c\e[K"
  puts "\e[K"
  print "\e[K"
  exit 1
end

%W(TSTP QUIT).each do |sig|
  end_all_other_threads  
  Signal.trap(sig) do
    print "\e[#{$lines[:message2]}H" if $move_down_on_exit
    puts "\n\n\n\e[0mThis signal (#{sig}) is handled in modes quiz and jamming only; exiting ..."
    puts "\e[K"
    print "\e[K"
    exit 1
  end
end

if [:listen, :quiz, :licks].include?($mode)
  Signal.trap('WINCH') do
    $ctl_sig_winch = true
  end
  Signal.trap('CONT') do
    system('clear')
    prepare_term
    $ctl_redraw = true
  end
end

at_exit do
  end_all_other_threads
  # give processes started by threads some time to terminate
  sleep 0.1
  # does not apply to 'tools spread-keys' which can be used in a subshell
  if STDIN.isatty && STDOUT.isatty
    sane_term
    print "\e[?25h"  ## show cursor
    print "\e[0m"
  end

  # take care to not remove too much, if something happens and $dirs
  # would not be initialized completetly
  FileUtils.remove_dir($dirs[:tmp]) if $dirs && $dirs[:tmp] && File.directory?($dirs[:tmp])

  write_dump 'end' if $testing

  # a user recording could still be going on
  $ulrec&.stop_rec
  
  # notice for lagging or jitter if appropriate
  print_afterthought if $initialized

  if $opts && $opts[:debug] 
    print_debug_info
    puts "Note: A debug log has been written: #{$debug_log}" if File.exist?($debug_log)
  end
    
  if $pers_file && $pers_data.keys.length > 0 && $pers_fingerprint != $pers_data.hash
    File.write($pers_file, JSON.pretty_generate($pers_data))
  end
end

report_name_collisions_mb

write_invocation

$other_mode_saved = Hash.new
begin

  $ulrec&.stop_rec
  switch_modes if $ctl_mic[:switch_modes]

  case $mode
  when :quiz
    do_quiz to_handle
  when :licks
    do_licks_or_quiz to_handle: to_handle
  when :listen
    do_listen
  when :play
    do_play to_handle
  when :print
    do_print to_handle
  when :samples
    do_samples to_handle
  when :tools
    do_tools to_handle
  when :jamming
    do_jamming to_handle
  when :develop
    do_develop to_handle
  end
end while $ctl_mic[:switch_modes]

