const std = @import("std");
const builtin = @import("builtin");
const microzig = @import("microzig");
const pico = microzig.hal;
const peripherals = microzig.chip.peripherals;
const interrupt = microzig.cpu.interrupt;
const BAUD_RATE = 115200;
const UART_DEV = 0;
const UART_TX_PIN = 0;
const uart = pico.uart.instance.num(UART_DEV);
const uart_tx_pin = pico.gpio.num(UART_TX_PIN);
const chip = pico.compatibility.chip;
pub const microzig_options: microzig.Options = .{
    .log_level = .debug,
    .logFn = pico.uart.log,
    .interrupts = .{
        .TIMER_IRQ_0 = .{ .c = timerInterrupt },
        .IO_IRQ_BANK0 = .{ .c = gpioInterrupt },
    },
};
const DDC = struct {
    const PREAMBLE_MASK = 0b11111111110;
    const TIME_ONEMAX = 87 * 1000;
    // zig fmt: off
    const MASK_3BYTES =    0b1111111111_1_00000000_1_00000000_1_00000000_1;
    const COMPARE_3BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_1;
    const MASK_4BYTES =    0b1111111111_1_00000000_1_00000000_1_00000000_1_00000000_1;
    const COMPARE_4BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_0_00000000_1;
    const MASK_5BYTES =    0b1111111111_1_00000000_1_00000000_1_00000000_1_00000000_1_00000000_1;
    const COMPARE_5BYTES = 0b1111111111_0_00000000_0_00000000_0_00000000_0_00000000_0_00000000_1;
    // zig fmt: on
    const PacketStateEnum = enum {
        preamble,
        package,
    };
    const BitStateEnum = enum(u1) {
        zero,
        one,
    };
    const BUFFER_TYPE = u32; // 10+1 + 5x(8+1)
    state: PacketStateEnum = .preamble,
    time: microzig.drivers.time.Absolute = @enumFromInt(0),
    buffer: BUFFER_TYPE = 0,
    fn writeToBuffer(self: *@This(), bit: BitStateEnum) void {
        self.buffer <<= 1;
        self.buffer |= @intFromEnum(bit);
    }
    const Test = struct {
        fn sendOne() void {
            peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 });
            pico.time.sleep_us(DDC.TIME_ONEMAX / 2);
            peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 });
        }
        fn sendZero() void {
            peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_HIGH = 1 });
            pico.time.sleep_us(DDC.TIME_ONEMAX * 2);
            peripherals.IO_BANK0.PROC0_INTF1.modify(.{ .GPIO15_EDGE_LOW = 1 });
        }
        fn sendByte(bits: u8) void {
            for (0..8) |i| {
                const msb = bits >> (7 - @as(u3, @intCast(i)));
                if (@as(u1, @truncate(msb)) == 0) {
                    sendZero();
                } else {
                    sendOne();
                }
            }
        }
    };
};
var ddc = DDC{};
fn timerInterrupt() callconv(.c) void {
    const cs = microzig.interrupt.enter_critical_section();
    defer cs.leave();
    std.log.info("timer_interrupt called!", .{});
    peripherals.TIMER.INTR.modify(.{ .ALARM_0 = 1 });
    setAlarm(1_000);
}
fn gpioInterrupt() callconv(.c) void {
    // const cs = microzig.interrupt.enter_critical_section();
    // defer cs.leave();
    // XXX: this could be const without the .Debug code...
    var state = peripherals.IO_BANK0.PROC0_INTS1.read();
    if (builtin.mode == .Debug) {
        const fstate = peripherals.IO_BANK0.PROC0_INTF1.read();
        if (fstate.GPIO15_LEVEL_LOW == 1) state.GPIO15_EDGE_LOW = 1;
        if (fstate.GPIO15_LEVEL_HIGH == 1) state.GPIO15_EDGE_HIGH = 1;
        // disable forcing
        peripherals.IO_BANK0.PROC0_INTF1.modify(.{
            .GPIO15_EDGE_HIGH = 0,
            .GPIO15_EDGE_LOW = 0,
        });
    }
    // aknowledge interrupt
    peripherals.IO_BANK0.INTR1.modify(.{ .GPIO15_EDGE_LOW = 1, .GPIO15_EDGE_HIGH = 1 });
    // std.log.debug("gpioInterrupt LOW: {d} HIGH: {d}", .{ state.GPIO15_EDGE_LOW, state.GPIO15_EDGE_HIGH });
    // rising DDC signal
    if (state.GPIO15_EDGE_HIGH == 1) {
        ddc.time = pico.time.get_time_since_boot();
        return;
    }
    // falling DDC signal
    if (state.GPIO15_EDGE_LOW == 1) {
        const time_end = pico.time.get_time_since_boot();
        const diff = time_end.diff(ddc.time);
        const bit: DDC.BitStateEnum = if (diff.less_than(@enumFromInt(DDC.TIME_ONEMAX))) .one else .zero;
        switch (ddc.state) {
            .preamble => {
                switch (bit) {
                    .one => {
                        ddc.writeToBuffer(bit);
                    },
                    .zero => {
                        ddc.writeToBuffer(bit);
                        std.log.debug("DDC buffer: {b}", .{ddc.buffer});
                        if (ddc.buffer & DDC.PREAMBLE_MASK == DDC.PREAMBLE_MASK) {
                            ddc.state = .package;
                            std.log.debug("DDC state switched to: {}", .{ddc.state});
                        } else {
                            std.log.info("DDC preamble is invalid: {b}", .{ddc.buffer});
                            ddc.buffer = 0;
                        }
                    },
                }
            },
            .package => {
                ddc.writeToBuffer(bit);
                std.log.debug("DDC buffer: {b}", .{ddc.buffer});
                if (ddc.buffer & @as(DDC.BUFFER_TYPE, @truncate(DDC.MASK_3BYTES)) == @as(DDC.BUFFER_TYPE, @truncate(DDC.COMPARE_3BYTES))) {
                    std.log.debug("3 byte long DCC package detected!", .{});
                }
            },
        }
    }
}
fn setAlarm(ms: u32) void {
    const current = pico.time.get_time_since_boot();
    const target = current.add_duration(microzig.drivers.time.Duration.from_ms(ms));
    // peripherals.TIMER.ALARM0.write_raw(@intCast(@intFromEnum(target) & 0xffffffff));
    peripherals.TIMER.ALARM0.write_raw(@truncate(@intFromEnum(target)));
}
pub fn main() !void {
    // init uart logging
    uart_tx_pin.set_function(.uart);
    uart.apply(.{
        .baud_rate = BAUD_RATE,
        .clock_config = pico.clock_config,
    });
    pico.uart.init_logger(uart);
    // TODO: could be useful for watchdog updating
    // timer interrupt
    // setAlarm(1_000);
    // peripherals.TIMER.INTE.toggle(.{ .ALARM_0 = 1 });
    // interrupt.enable(.TIMER_IRQ_0);
    // gpio interrupt
    const pin = pico.gpio.num(15);
    pin.set_direction(.in);
    peripherals.IO_BANK0.PROC0_INTE1.modify(.{ .GPIO15_EDGE_LOW = 1, .GPIO15_EDGE_HIGH = 1 });
    interrupt.enable(.IO_IRQ_BANK0);
    microzig.cpu.interrupt.enable_interrupts();
    // while (true) {
    //     asm volatile ("wfi");
    // }
    // preamble
    for (0..10) |_| {
        DDC.Test.sendOne();
    }
    DDC.Test.sendZero();
    // DDC.Test.send(0b00110111_0_01110100_0_01000011_1);
    // address
    DDC.Test.sendByte(0b00110111);
    DDC.Test.sendZero();
    DDC.Test.sendByte(0b01110100);
    DDC.Test.sendZero();
    DDC.Test.sendByte(0b01000011);
    DDC.Test.sendOne();
    std.log.info("DDC.buffer: {b}", .{ddc.buffer});
}