const std = @import("std");
const mem = std.mem;
const net = std.net;
const http = std.http;
const heap = std.heap;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const address = try net.Address.parseIp("0.0.0.0", 8080);
    var listener = Listener.init(address, allocator, &handler);
    defer listener.deinit();
    try listener.listen();
}

pub fn handler(event: *Listener.Event) anyerror!void {
    try event.res.body.appendSlice("hello there");
}

pub const Router = struct {};

pub const Listener = struct {
    address: net.Address,
    arena: heap.ArenaAllocator,
    handler: *const fn (event: *Event) anyerror!void,

    pub fn init(address: net.Address, allocator: mem.Allocator, hand: *const fn (event: *Event) anyerror!void) Listener {
        return .{
            .address = address,
            .arena = heap.ArenaAllocator.init(allocator),
            .handler = hand,
        };
    }

    pub fn deinit(self: *Listener) void {
        self.arena.deinit();
    }

    pub fn listen(self: *Listener) !void {
        var tcp = try self.address.listen(.{});
        defer tcp.deinit();
        std.debug.print("listening at: {any}\n", .{self.address});

        while (true) {
            const read_buf = try self.arena.allocator().alloc(u8, 1024 * 32);
            const connection = try tcp.accept();
            var server = http.Server.init(connection, read_buf);
            try self.handle(&server);
            _ = self.arena.reset(.retain_capacity);
        }
    }

    fn handle(self: *Listener, server: *http.Server) !void {
        handler: while (true) {
            var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;

            var event = Event{
                .req = .{
                    .uri = try std.Uri.parseWithoutScheme(req.head.target),
                    .headers = std.ArrayList(*const http.Header).init(self.arena.allocator()),
                },
                .res = .{
                    .status = .ok,
                    .headers = std.StringHashMap(*http.Header).init(self.arena.allocator()),
                    .body = std.ArrayList(u8).init(self.arena.allocator()),
                },
            };

            var header_it = req.iterateHeaders();
            while (header_it.next()) |header| try event.req.headers.append(&header);

            try self.handler(&event);

            try self.respondFromEvent(&event, &req);
        }
    }

    fn respondFromEvent(self: *Listener, event: *Event, req: *http.Server.Request) !void {
        const res_body = try event.res.body.toOwnedSlice();
        const res_headers = try self.arena.allocator().alloc(http.Header, event.res.headers.count());
        var i: usize = 0;
        var header_it = event.res.headers.iterator();
        while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*;
        try req.respond(res_body, .{
            .status = event.res.status,
            .extra_headers = res_headers,
        });
    }

    pub const Event = struct {
        req: Request,
        res: Response,

        pub const Request = struct {
            uri: std.Uri,
            // head: http.Server.Request.Head,
            headers: std.ArrayList(*const http.Header),
        };
        pub const Response = struct {
            status: http.Status,
            headers: std.StringHashMap(*http.Header),
            body: std.ArrayList(u8),
        };
    };
};