const std = @import("std");

const VSTART = 8;
// Theoretical max is 4095 (u12), but CV is only u8, so we have to divide by 16
const VHIGH = 63 * 16 / 16; // ~1000
const VMID = VHIGH * 60 / 100;

pub const CV = packed struct {
    primary_address: u8,
    vstart: u8,
    acceleration_rate: u8 = 0,
    deceleration_rate: u8 = 0,
    vhigh: u8,
    vmid: u8,
    manufacturer_version: u8 = 0,
    manufacturer_id: u8 = 13,
    PWM_period: u8,
    EMF_feedback_cutout: u8, // CV10
    packet_timeout: u8,
    power_source_conversion: u8,
    AMFS_F1_F8: u8 = 0,
    AMFS_FL_F9_F12: u8 = 0,
    decoder_lock: u16 = 0,
    extended_address: u16,
    consist_address: u8 = 0,
    NMRA_reserved20: u8 = 0, // CV20
    consist_F1_F8: u8 = 0,
    consist_FL_F9_F12: u8 = 0,
    acceleration_adjustment: u8 = 0,
    deceleration_adjustment: u8 = 0,
    speed_table_select: u8 = 0, // TODO: not sure it worth the effort if speed PID is working...
    NMRA_reserved26: u8 = 0,
    decoder_automatic_stopping: u8 = 0, // TODO: disable setting these values if not implemented
    bidir_comm_config: u8 = 0, // TODO: disable setting these values if not implemented
    configuration_data: Configuration,
    error_information: u8 = 0, // CV30
    index_high_byte: u8,
    index_low_byte: u8,
    output_loc: u112 = 0, // 33..46 => 14*8=112
    manufacturer: Manufacturer = @bitCast(@as(u144, 0)), // 47..64 => 18*8=144
    kick_start: u8 = 0,
    forward_trim: u8 = 0,
    speed_table: u224 = 0, // 67..94 => 28*8=224
    reverse_trim: u8 = 0,
    NMRA_reserved96_104: u72 = 0, // 96..104 => 9*8=72
    user_identifier1: u8 = 0,
    user_identifier2: u8 = 0,
    NMRA_reserved107_111: u40 = 0, // 107..111 => 5*8=40
    manufacturer2: u1160 = 0, // 112..256 => 145*8=1160

    /// Normally the CV table is 1024*8=8192 bits, but this part is unimplemented
    /// anyway, so we can just drop it for now for quarter size

    // indexed_area: u2048 = 0, // 257..512 => 256*8=2048
    // NMRA_reserved513_879: u2936 = 0, // 513..879 => 367*8=2936
    // NMRA_reserved880_891: u96 = 0, // 880..891 => 12*8=96
    // decoder_load: u8 = 0,
    // dynamic_flags: u8 = 0,
    // fuel_coal: u8 = 0,
    // water: u8 = 0,
    // SUSI: u1032 = 0, // 896..1024 => 129*8=1032

    const Manufacturer = packed struct(u144) {
        reserved: u112 = 0,
        emf: BackEMF = BackEMF{},
    };

    const BackEMF = packed struct {
        iterations: u8 = 100, // Number of measurements to calculate Back-EMF
        delay_us: u8 = 100, // Delay before measurement start
        low_cutoff: u8 = 15, // Number of discarded elements on the low side
        high_cutoff: u8 = 15, // Number of discarded elements on the high side
    };

    const Configuration = packed struct {
        accessory_decoder: bool,
        reserved: u1 = 0,
        extended_addressing: bool,
        speed_table: u1,
        bidirectional_communication: bool,
        power_source_conversion: bool,
        FL_location: u1,
        direction: u1,
    };

    pub const default: CV = .{
        .primary_address = 3,
        .vstart = VSTART,
        .vhigh = VHIGH,
        .vmid = VMID,
        .PWM_period = 150, // 125_000_000 / PWM_period * 100 + 10_000 (125MHz -> 25kHz)
        .EMF_feedback_cutout = 1,
        .packet_timeout = 0, // TODO: S-9.2.4 Section C recommends 20 seconds, not implemented yet
        .power_source_conversion = 0, // TODO Go to analog mode when CV1==0
        .extended_address = 0b11000011_11101000,
        .index_high_byte = 0b00010000,
        .index_low_byte = 0b00000000,
        .configuration_data = .{
            .accessory_decoder = false,
            .extended_addressing = false,
            .speed_table = 0, // vstart-vmid-vhigh
            .bidirectional_communication = false,
            .power_source_conversion = false,
            .FL_location = 0, // TODO not implemented
            .direction = 0, // TODO not implemented
        },
        .manufacturer = Manufacturer{},
        .speed_table = genSpeedTable(VSTART, VMID, VHIGH),
    };

    /// Expects [0..1023], so for CV29 `cv` should be 28
    /// XXX: We cut the CVs, so actually it is [0..255]
    pub fn getCV(self: @This(), cv: u8) u8 {
        std.debug.assert(@bitSizeOf(CV) == 2048);
        std.debug.assert(@sizeOf(CV) == 256);
        return @truncate(@as(u2048, @bitCast(self)) >> cv * 8);
    }

    pub fn validate(self: @This()) void {
        std.debug.assert(self.primary_address < 128);

        std.debug.assert(self.extended_address >> 8 >= 0b11000000 and self.extended_address >> 8 <= 0b11100111);

        // 0b00000000..0b00001111 reserved by NMRA
        std.debug.assert(self.index_high_byte > 0b00001111);
    }

    // XXX: remove pub!
    pub fn genSpeedTable(start: u8, mid: u8, high: u8) u224 {
        _ = high;
        const steps = 28 - 4;
        var ret: u224 = 0;

        for (0..steps / 2) |i| {
            const inc = (mid - start) / (steps / 2);
            const mask: u8 = @intCast(VSTART + i * inc);
            const shift: u8 = @intCast(i * 8 + 4 * 8);
            ret |= @as(u224, mask) << shift;
            // std.log.debug("step: {d} mask: {d} shift: {d}", .{ i, mask, shift });
        }

        // for (steps / 2..steps) |i| {
        //     const inc = (VHIGH - VMID) / (steps / 2);
        //     ret |= (VMID + i * inc) << (i * 8 + 4 * 8);
        // }

        return ret;
    }
};