const std = @import("std");
const xev = @import("xev");

const RotInt = @import("./main.zig").RotInt;

pub const Server = struct {
    rotint: *RotInt = undefined,
    listen_socket: xev.TCP = undefined,
    client_socket: ?xev.TCP = null,
    read_completion: xev.Completion = undefined,
    write_completion: xev.Completion = undefined,
    // our messages are small. We may be able to get away with an even smaller buffer
    // size than this.
    read_buffer: [128]u8 = undefined,
    write_buffer: [128]u8 = undefined,

    pub fn listen(self: *Server, loop: *xev.Loop) !void {
        const address = try std.net.Address.parseIp4("127.0.0.1", 5435);
        self.listen_socket = try xev.TCP.init(address);
        try self.listen_socket.bind(address);
        try self.listen_socket.listen(1);
        self.listen_socket.accept(loop, &self.read_completion, Server, self, acceptCallback);
    }

    fn acceptCallback(
        self_: ?*Server,
        loop: *xev.Loop,
        completion: *xev.Completion,
        result: xev.TCP.AcceptError!xev.TCP,
    ) xev.CallbackAction {
        _ = completion;

        const self = self_.?;

        self.client_socket = result catch {
            // different error sets for different backends makes handling individual
            // errors here odd. Either way, if this fails, I think we just want to
            // retry anyway.
            return .rearm;
        };

        // this is already using read_completion, but pass it explicitly here to be clear
        self.client_socket.?.read(
            loop,
            &self.read_completion,
            .{ .slice = &self.read_buffer },
            Server,
            self,
            readCallback,
        );
        return .disarm;
    }

    fn readCallback(
        self_: ?*Server,
        loop: *xev.Loop,
        completion: *xev.Completion,
        socket: xev.TCP,
        buf: xev.ReadBuffer,
        result: xev.TCP.ReadError!usize,
    ) xev.CallbackAction {
        const self = self_.?;
        const n = result catch |err| switch (err) {
            // we can reuse the completion here in either of these error cases since we
            // disarm in both of them
            error.EOF => {
                socket.shutdown(loop, completion, Server, self, shutdownCallback);
                self.client_socket = null;
                return .disarm;
            },
            else => {
                self.rotint.warn("server read unexpected err={}", .{err});
                // TODO: if read failed unexpectedly, will shutting down the socket like
                // this work?
                socket.shutdown(loop, completion, Server, self, shutdownCallback);
                self.client_socket = null;
                return .disarm;
            },
        };

        // this is a request from gpredict to rotctl. We get rotint to take care of it
        // for us (it stores a copy of this buffer)
        self.rotint.forwardRequest(buf.slice[0..n]);
        return .rearm;
    }

    pub fn writeResponse(
        self: *Server,
        loop: *xev.Loop,
        response: []const u8,
    ) void {
        @memcpy(self.write_buffer[0..response.len], response);
        if (self.client_socket) |sock|
            sock.write(
                loop,
                &self.write_completion,
                .{ .slice = self.write_buffer[0..response.len] },
                Server,
                self,
                writeCallback,
            );
    }

    fn writeCallback(
        self_: ?*Server,
        loop: *xev.Loop,
        completion: *xev.Completion,
        socket: xev.TCP,
        buf: xev.WriteBuffer,
        result: xev.TCP.WriteError!usize,
    ) xev.CallbackAction {
        _ = buf;

        const self = self_.?;
        _ = result catch |err| {
            // we're in trouble. Probably this is due to an unexpected client disconnect?
            self.rotint.warn("server write unexpected err={}", .{err});
            socket.shutdown(loop, completion, Server, self, shutdownCallback);
            self.client_socket = null;
            return .disarm;
        };

        // inform rotint of successful write?

        return .disarm;
    }

    fn shutdownCallback(
        self_: ?*Server,
        loop: *xev.Loop,
        completion: *xev.Completion,
        socket: xev.TCP,
        result: xev.TCP.ShutdownError!void,
    ) xev.CallbackAction {
        _ = result catch {};
        // shutdown only closes the write side of the socket. It's not entirely clear to
        // me if shutdown actually needs to be called before close.
        socket.close(loop, completion, Server, self_.?, closeCallback);
        return .disarm;
    }

    fn closeCallback(
        self_: ?*Server,
        loop: *xev.Loop,
        completion: *xev.Completion,
        socket: xev.TCP,
        result: xev.TCP.CloseError!void,
    ) xev.CallbackAction {
        _ = completion;
        _ = socket;
        _ = result catch unreachable;

        // listen for a new connection
        const self = self_.?;
        self.listen_socket.accept(loop, &self.read_completion, Server, self, acceptCallback);

        return .disarm;
    }
};
