const std = @import("std");

const microzig = @import("microzig");
const rp2xxx = microzig.hal;
const chip = rp2xxx.compatibility.chip;
const interrupt = microzig.cpu.interrupt;
const peripherals = microzig.chip.peripherals;

const build_options = @import("build_options");
const board = @import("board.zig").boardConfig(build_options.board);

const CV = @import("cv.zig").CV;
const DCC = @import("dcc.zig");
const PID = @import("pid.zig");
const gpio = @import("gpio.zig");
const motor = @import("motor.zig");

const log = std.log.scoped(.RP2040);

pub const microzig_options: microzig.Options = .{
    .log_level = .debug,
    .logFn = rp2xxx.uart.log_threadsafe,
    .interrupts = .{
        .TIMER_IRQ_0 = .{ .c = timerInterrupt },
        .IO_IRQ_BANK0 = .{ .c = gpioInterrupt },
    },
};

pub const pins = board.pin_conf.pins();

var dcc = DCC.Parser{};
var pid: PID = undefined;
pub var cv = CV.default;

pub fn main() !void {
    board.pin_conf.apply();

    { // UART log setup
        if (board.uart) |uart| {
            uart.apply(.{
                .baud_rate = 115200,
                .clock_config = rp2xxx.clock_config,
            });
            rp2xxx.uart.init_logger(uart);
        }
    }

    rp2xxx.irq.globally_disable();
    gpio.enable_interrupt(pins.ddc, .{ .io_bank0 = .{ .proc0 = 1 } }, .{ .fall = 1, .rise = 1 });

    { // PWM setup
        // 125MHz -> 25kHz
        pins.speed.slice().set_wrap(@intCast(125_000_000 / (@as(u32, cv.PWM_period) * 100 + 10_000) - 1));
        pins.speed.slice().enable();
        pins.speed.slice().set_phase_correct(false);
        pins.speed.slice().set_clk_div(1, 0);
    }

    { // ADC setup
        rp2xxx.adc.apply(.{
            .sample_frequency = null,
            .round_robin = null,
            .temp_sensor_enabled = false,
            .fifo = .{ .dreq_enabled = false, .irq_enabled = false, .shift = false },
        }); // apply calls enable too
    }

    rp2xxx.multicore.launch_core1(core1);

    // XXX: waiting for core1 should be more reliable
    rp2xxx.time.sleep_ms(1000);

    // const foo = CV.genSpeedTable(8, 60, 100);

    for (0..29) |i| {
        const val = cv.getCV(67 - 1 + @as(u8, @intCast(i)));
        // const val: u8 = @truncate(foo >> @truncate(i * 8));
        log.debug("Step{d} expected EMF: {d}", .{ i, val });
    }

    // try motor.tester(board.md, @intCast(125_000_000 / (@as(usize, cv.PWM_period) * 100 + 10_000)));

    rp2xxx.irq.globally_enable();

    while (dcc.packets.readItem()) |packet| {
        if (packet.address == 55) {
            log.info("packet: {}", .{packet});
            rp2xxx.multicore.fifo.write_blocking(@as(u12, @intCast(@intFromEnum(packet.speed()))) * 100);
        }
    }

    unreachable;

    // { // DDC testing
    //     // preamble
    //     for (0..10) |_| {
    //         DCC.Test.sendOne();
    //     }
    //     DCC.Test.sendZero();

    //     // 3 byte packet
    //     DCC.Test.sendByte(0b00110111);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01110100);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendOne();

    //     // preamble
    //     for (0..10) |_| {
    //         DCC.Test.sendOne();
    //     }
    //     DCC.Test.sendZero();
    //     // 4 byte packet
    //     DCC.Test.sendByte(0b00110111);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01110100);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendOne();

    //     // preamble
    //     for (0..10) |_| {
    //         DCC.Test.sendOne();
    //     }
    //     DCC.Test.sendZero();
    //     // 5 byte packet
    //     DCC.Test.sendByte(0b00110111);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01110100);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendZero();
    //     DCC.Test.sendByte(0b01000011);
    //     DCC.Test.sendOne();

    //     if (dcc.packets.readItem()) |packet| {
    //         log.debug(
    //             "packet address: {d} speed: {} instruction: {}",
    //             .{ packet.address, packet.speed(), packet.instruction },
    //         );
    //     }

    //     cv.validate();

    //     // Expected to be 8
    //     log.debug("vstart CV: {d}", .{cv.getCV(2 - 1)});

    //     dcc.listen(&cv);

    //     rp2xxx.multicore.fifo.write_blocking(12);
    //     rp2xxx.time.sleep_ms(5000);
    //     rp2xxx.multicore.fifo.write_blocking(20);
    //     rp2xxx.time.sleep_ms(5000);
    // }

    // while (true) {
    //     microzig.cpu.wfi();
    // }
}

fn core1() void {
    // initialize PID
    pid = PID.init(board, PID.PID_INTERRUPT_MS, 2.1, 0.03, 100);

    // timer interrupt for PID
    PID.setAlarm(PID.PID_INTERRUPT_MS);
    peripherals.TIMER.INTE.modify(.{ .ALARM_0 = 1 });
    interrupt.set_priority(.TIMER_IRQ_0, .lowest);
    interrupt.enable(.TIMER_IRQ_0);

    while (true) {
        if (rp2xxx.multicore.fifo.read()) |raw_speed| {
            const speed: DCC.Speed = @enumFromInt(@as(u5, @truncate(raw_speed)));
            log.info("CPU{d}, speed: {}", .{ rp2xxx.get_cpu_id(), speed });
            pid.sp = speed;
        }
    }
}

fn timerInterrupt() callconv(.c) void {
    // const cs = microzig.interrupt.enter_critical_section();
    // defer cs.leave();
    // log.debug("CPU{d} timerInterrupt start...", .{pico.get_cpu_id()});

    // acknowledge interrupt
    peripherals.TIMER.INTR.modify(.{ .ALARM_0 = 1 });

    const input: f32 = 5;
    _ = pid.compute(input);

    // FIXME: printing float kills everything...
    // log.debug("SP: {} in: {d} out: {d}", .{
    //     pid.sp,
    //     @as(i32, @intFromFloat(input)),
    //     @as(i32, @intFromFloat(output)),
    // });

    PID.setAlarm(PID.PID_INTERRUPT_MS);

    // log.debug("CPU{d} timerInterrupt done.", .{pico.get_cpu_id()});
}

fn gpioInterrupt() callconv(.c) void {
    // const cs = microzig.interrupt.enter_critical_section();
    // defer cs.leave();
    // log.debug("CPU{d} gpioInterrupt start...", .{pico.get_cpu_id()});

    // XXX: this could be const without the testing code...
    var state = peripherals.IO_BANK0.PROC0_INTS1.read();

    { // Handle forced state - only for testing purposes
        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 });

    // log.debug(
    //     "CPU{d} gpioInterrupt LOW: {d} HIGH: {d}",
    //     .{ rp2xxx.get_cpu_id(), state.GPIO15_EDGE_LOW, state.GPIO15_EDGE_HIGH },
    // );

    if (state.GPIO15_EDGE_HIGH == 1) {
        dcc.signalRising();
    } else if (state.GPIO15_EDGE_LOW == 1) {
        dcc.signalFalling();
    }
    // log.debug("CPU{d} gpioInterrupt stop.", .{pico.get_cpu_id()});
}