class DEBUGGER__::UI_ServerBase

def accept

def accept
  if @sock_for_fork
    begin
      yield @sock_for_fork, already_connected: true
    ensure
      @sock_for_fork.close
      @sock_for_fork = nil
    end
  end
end

def activate session, on_fork: false

def activate session, on_fork: false
  @session = session
  @reader_thread = Thread.new do
    # An error on this thread should break the system.
    Thread.current.abort_on_exception = true
    Thread.current.name = 'DEBUGGER__::Server::reader'
    accept do |server, already_connected: false|
      DEBUGGER__.warn "Connected."
      greeting_done = false
      @need_pause_at_first = true
      @accept_m.synchronize{
        @sock = server
        greeting
        greeting_done = true
        @accept_cv.signal
        # flush unsent messages
        @unsent_messages.each{|m|
          @sock.puts m
        } if @repl
        @unsent_messages.clear
        @q_msg = Queue.new
        @q_ans = Queue.new
      } unless already_connected
      setup_interrupt do
        pause if !already_connected && @need_pause_at_first
        process
      end
    rescue GreetingError => e
      DEBUGGER__.warn "GreetingError: #{e.message}"
      next
    rescue Terminate
      raise # should catch at outer scope
    rescue RetryConnection
      next
    rescue => e
      DEBUGGER__.warn "ReaderThreadError: #{e}"
      pp e.backtrace
    ensure
      DEBUGGER__.warn "Disconnected."
      cleanup_reader if greeting_done
    end # accept
  rescue Terminate
    # ignore
  end
end

def after_fork_parent

def after_fork_parent
  # do nothing
end

def ask prompt

def ask prompt
  sock do |s|
    s.puts "ask #{Process.pid} #{prompt}"
    @q_ans.pop
  end
end

def check_cookie c

def check_cookie c
  cookie = CONFIG[:cookie]
  if cookie && cookie != c
    raise GreetingError, "Cookie mismatch (#{$2.inspect} was sent)"
  end
end

def cleanup_reader

def cleanup_reader
  @sock.close if @sock
  @sock = nil
  @q_msg.close
  @q_msg = nil
  @q_ans.close
  @q_ans = nil
end

def deactivate

def deactivate
  @reader_thread.raise Terminate
  @reader_thread.join
end

def greeting

def greeting
  case g = @sock.gets
  when /^info cookie:\s+(.*)$/
    require 'etc'
    check_cookie $1
    @sock.puts "PID: #{Process.pid}, $0: #{$0}, session_name: #{CONFIG[:session_name]}"
    @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
    @sock.puts "uname: #{Etc.uname.inspect}"
    @sock.close
    raise GreetingError, "HEAD request"
  when /^version:\s+(\S+)\s+(.+)$/
    v, params = $1, $2
    # TODO: protocol version
    if v != VERSION
      @sock.puts msg = "out DEBUGGER: Incompatible version (server:#{VERSION} and client:#{$1})"
      raise GreetingError, msg
    end
    parse_option(params)
    session_name = CONFIG[:session_name]
    session_name_str = ", session_name:#{session_name}" if session_name
    puts "DEBUGGER (client): Connected. PID:#{Process.pid}, $0:#{$0}#{session_name_str}"
    puts "DEBUGGER (client): Type `Ctrl-C` to enter the debug console." unless @need_pause_at_first
    puts
  when /^Content-Length: (\d+)/
    require_relative 'server_dap'
    raise unless @sock.read(2) == "\r\n"
    self.extend(UI_DAP)
    @repl = false
    @need_pause_at_first = false
    dap_setup @sock.read($1.to_i)
  when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
    # The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.
    require_relative 'server_cdp'
    self.extend(UI_CDP)
    send_chrome_response g
  else
    raise GreetingError, "Unknown greeting message: #{g}"
  end
end

def initialize

def initialize
  @sock = @sock_for_fork = nil
  @accept_m = Mutex.new
  @accept_cv = ConditionVariable.new
  @client_addr = nil
  @q_msg = nil
  @q_ans = nil
  @unsent_messages = []
  @width = 80
  @repl = true
  @session = nil
end

def parse_option params

def parse_option params
  case params.strip
  when /width:\s+(\d+)/
    @width = $1.to_i
    parse_option $~.post_match
  when /cookie:\s+(\S+)/
    check_cookie $1 if $1 != '-'
    parse_option $~.post_match
  when /nonstop: (true|false)/
    @need_pause_at_first = false if $1 == 'true'
    parse_option $~.post_match
  when /(.+):(.+)/
    raise GreetingError, "Unkown option: #{params}"
  else
    # OK
  end
end

def pause

def pause
  # $stderr.puts "DEBUG: pause request"
  Process.kill(TRAP_SIGNAL, Process.pid)
end

def process

def process
  while true
    DEBUGGER__.debug{ "sleep IO.select" }
    _r = IO.select([@sock])
    DEBUGGER__.debug{ "wakeup IO.select" }
    line = @session.process_group.sync do
      unless IO.select([@sock], nil, nil, 0)
        DEBUGGER__.debug{ "UI_Server can not read" }
        break :can_not_read
      end
      @sock.gets&.chomp.tap{|line|
        DEBUGGER__.debug{ "UI_Server received: #{line}" }
      }
    end
    return unless line
    next if line == :can_not_read
    case line
    when /\Apause/
      pause
    when /\Acommand (\d+) (\d+) ?(.+)/
      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
      if $1.to_i == Process.pid
        @width = $2.to_i
        @q_msg << $3
      else
        raise "pid:#{Process.pid} but get #{line}"
      end
    when /\Aanswer (\d+) (.*)/
      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
      if $1.to_i == Process.pid
        @q_ans << $2
      else
        raise "pid:#{Process.pid} but get #{line}"
      end
    else
      STDERR.puts "unsupported: #{line.inspect}"
      exit!
    end
  end
end

def puts str = nil

def puts str = nil
  case str
  when Array
    enum = str.each
  when String
    enum = str.each_line
  when nil
    enum = [''].each
  end
  sock skip: true do |s|
    enum.each do |line|
      msg = "out #{line.chomp}"
      if s
        s.puts msg
      else
        @unsent_messages << msg
      end
    end
  end
end

def quit n, &_b

def quit n, &_b
  # ignore n
  sock do |s|
    s.puts "quit"
  end
end

def readline prompt

def readline prompt
  input = (sock(skip: CONFIG[:skip_bp]) do |s|
    next unless s
    if @repl
      raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
      line = "input #{Process.pid}"
      DEBUGGER__.debug{ "send: #{line}" }
      s.puts line
    end
    sleep 0.01 until @q_msg
    @q_msg.pop.tap{|msg|
      DEBUGGER__.debug{ "readline: #{msg.inspect}" }
    }
  end || 'continue')
  if input.is_a?(String)
    input.strip
  else
    input
  end
end

def remote?

def remote?
  true
end

def setup_interrupt

def setup_interrupt
  prev_handler = trap(TRAP_SIGNAL) do
    # $stderr.puts "trapped SIGINT"
    ThreadClient.current.on_trap TRAP_SIGNAL
    case prev_handler
    when Proc
      prev_handler.call
    else
      # ignore
    end
  end
  if sigurg_overridden?(prev_handler)
    DEBUGGER__.warn "SIGURG handler is overridden by the debugger."
  end
  yield
ensure
  trap(TRAP_SIGNAL, prev_handler)
end

def sigurg_overridden? prev_handler

def sigurg_overridden? prev_handler
  case prev_handler
  when "SYSTEM_DEFAULT", "DEFAULT"
    false
  when Proc
    if prev_handler.source_location[0] == __FILE__
      false
    else
      true
    end
  else
    true
  end
end

def sock skip: false

def sock skip: false
  if s = @sock         # already connection
    # ok
  elsif skip == true   # skip process
    no_sock = true
    r = @accept_m.synchronize do
      if @sock
        no_sock = false
      else
        yield nil
      end
    end
    return r if no_sock
  else                 # wait for connection
    until s = @sock
      @accept_m.synchronize{
        unless @sock
          DEBUGGER__.warn "wait for debugger connection..."
          @accept_cv.wait(@accept_m)
        end
      }
    end
  end
  yield s
rescue Errno::EPIPE
  # ignore
end

def vscode_setup debug_port

def vscode_setup debug_port
  require_relative 'server_dap'
  UI_DAP.setup debug_port
end

def width

def width
  @width
end