# frozen_string_literal: true

require_relative "helper"
require "http_parser"

OptionParser.new do |opts|
  opts.banner = "Usage: upgrade_client.rb [options]"
end.parse!

uri = URI.parse(ARGV[0] || "http://localhost:8080/")
sock = TCPSocket.new(uri.host, uri.port)

conn = HTTP2::Client.new

def request_header_hash
  Hash.new do |hash, key|
    k = key.to_s.downcase
    k.tr! "_", "-"
    _, value = hash.find { |header_key, _| header_key.downcase == k }
    hash[key] = value if value
  end
end

conn.on(:frame) do |bytes|
  sock.print bytes
  sock.flush
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

# upgrader module
class UpgradeHandler
  UPGRADE_REQUEST = <<RESP
GET %s HTTP/1.1
Connection: Upgrade, HTTP2-Settings
HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
Upgrade: h2c
Host: %s
User-Agent: http-2 upgrade
Accept: */*

RESP

  attr_reader :complete, :parsing

  def initialize(conn, sock)
    @conn = conn
    @sock = sock
    @headers = request_header_hash
    @body = "".b
    @complete = false
    @parsing = false
    @parser = ::HTTP::Parser.new(self)
  end

  def request(uri)
    host = "#{uri.hostname}#{":#{uri.port}" if uri.port != uri.default_port}"
    req = format(UPGRADE_REQUEST, uri.request_uri, host)
    puts req
    @sock << req
  end

  def <<(data)
    @parsing ||= true
    @parser << data
    return unless complete

    upgrade
  end

  def complete!
    @complete = true
  end

  def on_headers_complete(headers)
    @headers.merge!(headers)
    puts "received headers: #{headers}"
  end

  def on_body(chunk)
    puts "received chunk: #{chunk}"
    @body << chunk
  end

  def on_message_complete
    raise "could not upgrade to h2c" unless @parser.status_code == 101

    @parsing = false
    complete!
  end

  def upgrade
    stream = @conn.upgrade
    log = Logger.new(stream.id)

    stream.on(:close) do
      log.info "stream closed"
    end

    stream.on(:half_close) do
      log.info "closing client-end of the stream"
    end

    stream.on(:headers) do |h|
      log.info "response headers: #{h}"
    end

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

    stream.on(:altsvc) do |f|
      log.info "received ALTSVC #{f}"
    end

    @conn.on(:promise) do |promise|
      promise.on(:headers) do |h|
        log.info "promise headers: #{h}"
      end

      promise.on(:data) do |d|
        log.info "promise data chunk: <<#{d.size}>>"
      end
    end

    @conn.on(:altsvc) do |f|
      log.info "received ALTSVC #{f}"
    end
  end
end

uh = UpgradeHandler.new(conn, sock)
puts "Sending HTTP/1.1 upgrade request"
uh.request(uri)

while !sock.closed? && !sock.eof?
  data = sock.read_nonblock(1024)

  begin
    if (!uh.parsing && !uh.complete) ||
       (uh.parsing && !uh.complete)
      uh << data
    elsif uh.complete
      conn << data
    end
  rescue StandardError => e
    puts "#{e.class} exception: #{e.message} - closing socket."
    e.backtrace.each { |l| puts "\t#{l}" }
    conn.close
    sock.close
  end
end
