# frozen_string_literal: true

require_relative "helper"

options = { port: 8080 }
OptionParser.new do |opts|
  opts.banner = "Usage: server.rb [options]"

  opts.on("-s", "--secure", "HTTPS mode") do |v|
    options[:secure] = v
  end

  opts.on("-p", "--port [Integer]", "listen port") do |v|
    options[:port] = v
  end

  opts.on("-u", "--push", "Push message") do |_v|
    options[:push] = true
  end
end.parse!

puts "Starting server on port #{options[:port]}"
server = TCPServer.new(options[:port])

if options[:secure]
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.cert = OpenSSL::X509::Certificate.new(File.open("keys/server.crt"))
  ctx.key = OpenSSL::PKey::RSA.new(File.open("keys/server.key"))

  ctx.ssl_version = :TLSv1_2
  ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
  ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]

  ctx.alpn_protocols = ["h2"]

  ctx.alpn_select_cb = lambda do |protocols|
    raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?

    DRAFT
  end

  ctx.ecdh_curves = "P-256"

  server = OpenSSL::SSL::SSLServer.new(server, ctx)
end

loop do
  sock = server.accept
  sock.setsockopt(:SOCKET, :RCVBUF, 2048)
  puts "New TCP connection!"

  conn = HTTP2::Server.new
  conn.on(:frame) do |bytes|
    # puts "Writing bytes: #{bytes.unpack("H*").first}"
    sock.write(bytes) unless sock.closed?
  end
  conn.on(:frame_sent) do |frame|
    puts "Sent frame: #{frame.inspect}"
  end
  conn.on(:frame_received) do |frame|
    puts "Received frame: #{frame.inspect}"
  end

  conn.on(:goaway) do
    Thread.start do
      sleep(1)
      sock.close
    end
  end

  conn.on(:stream) do |stream|
    log = Logger.new(stream.id)
    req = {}
    buffer = "".b

    stream.on(:active) { log.info "client opened new stream" }
    stream.on(:close)  { log.info "stream closed" }

    stream.on(:headers) do |h|
      req = Hash[*h.flatten]
      log.info "request headers: #{h}"
    end

    stream.on(:data) do |d|
      log.info "payload chunk: <<#{d}>>"
      buffer << d
    end

    stream.on(:half_close) do
      log.info "client closed its end of the stream"

      response = nil
      if req[":method"] == "POST"
        log.info "Received POST request, payload: #{buffer}"
        response = "Hello HTTP 2.0! POST payload: #{buffer}"
      else
        log.info "Received GET request"
        response = "Hello HTTP 2.0! GET request"
      end

      stream.headers({
                       ":status" => "200",
                       "content-length" => response.bytesize.to_s,
                       "content-type" => "text/plain"
                     }, end_stream: false)

      if options[:push]
        push_streams = []

        # send 10 promises
        10.times do |i|
          puts "sending push"

          head = { ":method" => "GET",
                   ":authority" => "localhost",
                   ":scheme" => "https",
                   ":path" => "/other_resource/#{i}" }

          stream.promise(head) do |push|
            push.headers({ ":status" => "200", "content-type" => "text/plain", "content-length" => "11" })
            push_streams << push
          end
        end
      end

      # split response into multiple DATA frames
      stream.data(response[0, 5], end_stream: false)
      stream.data(response[5, -1] || "")

      if options[:push]
        push_streams.each_with_index do |push, i|
          sleep 1
          push.data("push_data #{i}")
        end
      end
    end
  end

  while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
    # puts "Received bytes: #{data.unpack("H*").first}"
    data = sock.readpartial(16_384)

    begin
      conn << data
    rescue StandardError => e
      puts "#{e.class} exception: #{e.message} - closing socket."
      puts e.backtrace
      sock.close
    end
  end
end
