//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
use commands::isl68224::*;
use pmbus::renesas::*;
use pmbus::*;

fn mode() -> VOutModeCommandData {
    VOutModeCommandData::from_slice(&[0x40]).unwrap()
}

fn dump_data(
    val: u32,
    width: Bitwidth,
    v: &mut std::vec::Vec<((Bitpos, Bitwidth), &str, std::string::String)>,
) {
    let width = width.0 as usize;
    let nibble = 4;
    let maxwidth = 32;

    if width > maxwidth {
        std::println!("{:?}", v);
        return;
    }

    let indent = (maxwidth - width) + ((maxwidth - width) / nibble);

    std::print!("{:indent$}", "", indent = indent);
    std::print!("0b");

    for v in (0..width).step_by(nibble) {
        std::print!(
            "{:04b}{}",
            (val >> ((width - nibble) - v)) & 0xf,
            if v + nibble < width { "_" } else { "\n" }
        )
    }

    while v.len() > 0 {
        let mut cur = width - 1;

        std::print!("{:indent$}", "", indent = indent);
        std::print!("  ");

        for i in 0..v.len() {
            while cur > v[i].0 .0 .0 as usize {
                if cur % nibble == 0 {
                    std::print!(" ");
                }

                std::print!(" ");
                cur -= 1;
            }

            if i < v.len() - 1 {
                std::print!("|");

                if cur % nibble == 0 {
                    std::print!(" ");
                }

                cur -= 1;
            } else {
                std::print!("+--");

                while cur > 0 {
                    std::print!("-");

                    if cur % nibble == 0 {
                        std::print!("-");
                    }

                    cur -= 1;
                }

                std::println!(" {} = {}", v[i].1, v[i].2);
            }
        }

        v.pop();
    }
}

fn dump(data: &impl CommandData) {
    let (val, width) = data.raw();
    let mut v = std::vec![];

    data.interpret(mode, |field, value| {
        if value.raw() == 0 {
            return;
        }

        v.push((field.bits(), field.desc(), std::format!("{}", value)));
    })
    .unwrap();

    dump_data(val, width, &mut v);
}

#[test]
fn vin() {
    let mode = || VOutModeCommandData::from_slice(&[0x40]).unwrap();

    let data = [(0x04a9u16, 11.930001), (0xffff, -0.010000001)];

    for d in &data {
        let raw = d.0.to_le_bytes();
        let vin = READ_VIN::CommandData::from_slice(&raw).unwrap();
        assert_eq!(vin.get(), Ok(units::Volts(d.1)));

        vin.interpret(mode, |f, v| {
            assert_eq!(f.bitfield(), false);
            std::println!("{} 0x{:04x} = {}", f.name(), d.0, v);
        })
        .unwrap();
    }
}

#[test]
fn ton_rise() {
    use TON_RISE::*;

    let mut data = CommandData::from_slice(&[0xf4, 0x01]).unwrap();
    assert_eq!(data.get(), Ok(units::Milliseconds(0.5)));

    data.set(units::Milliseconds(0.75)).unwrap();
    assert_eq!(data.get(), Ok(units::Milliseconds(0.75000006)));

    data.mutate(mode, |field, _| {
        assert_eq!(field.bitfield(), false);
        assert_eq!(field.bits(), (Bitpos(0), Bitwidth(16)));
        Some(Replacement::Float(0.25))
    })
    .unwrap();

    assert_eq!(data.get(), Ok(units::Milliseconds(0.25)));
}

#[test]
fn raw() {
    use DMAFIX::*;

    let input = [0xef, 0xbe, 0xad, 0xde];

    let mut data = CommandData::from_slice(&input).unwrap();
    assert_eq!(data.get(), Ok(0xdeadbeef));

    let rval = data.mutate(mode, |field, _| {
        assert_eq!(field.bitfield(), false);
        Some(Replacement::Integer(0xbaddcafe))
    });

    assert_eq!(rval, Ok(()));
    assert_eq!(data.0, 0xbaddcafe);

    let rval = data.mutate(mode, |_, _| Some(Replacement::Boolean(true)));

    assert_eq!(rval, Err(Error::InvalidReplacement));

    let rval = data.mutate(mode, |_, _| Some(Replacement::Float(1.2)));

    assert_eq!(rval, Err(Error::InvalidReplacement));
}

#[test]
fn status_mfr_specific() {
    use STATUS_MFR_SPECIFIC::*;
    let status = CommandData::from_slice(&[0x08]).unwrap();
    assert_eq!(status.get_bb_event(), Some(BBEvent::Event));
    dump(&status);
}

fn bb_dump(bb: &Blackbox) {
    println!("=== Controller wide");
    dump(&bb.controller_first_fault);
    dump(&bb.cml_status);
    dump(&bb.mfr_specific);

    for (index, rail) in bb.rails.iter().enumerate() {
        println!("--- Rail {}", index);
        dump(&rail.uptime);
        dump(&rail.first_fault);
        dump(&rail.status);
        dump(&rail.vout_status);
        dump(&rail.iout_status);
        dump(&rail.temp_status);
        dump(&rail.input_status);
        dump(&rail.vin);
        dump(&rail.vout);
        dump(&rail.iin);
        dump(&rail.iout);
    }
}

#[test]
fn blackbox_test4() {
    let raw = [
        0x00, 0x00, 0x00, 0x00, 0x83, 0x48, 0x01, 0x00, 0x33, 0x4c, 0x01, 0x00,
        0x33, 0x4c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x58, 0x00, 0x00,
        0x03, 0x10, 0x03, 0x10, 0x00, 0x00, 0x08, 0xa2, 0x00, 0x00, 0x80, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xab, 0x04, 0x00, 0x00, 0xab, 0x04, 0xab, 0x04,
        0xe7, 0x03, 0x84, 0x03, 0x04, 0x00, 0xb0, 0x04, 0x02, 0x00, 0x03, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x0f,
        0x00, 0x00, 0x00, 0x00, 0xe9, 0x01, 0xac, 0x04, 0x0f, 0x00, 0x00, 0x00,
        0x0e, 0x0e, 0x0e, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x42, 0xff, 0x80,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ];

    let bb = Blackbox::from_slice(&raw);
    bb_dump(&bb);
}

#[test]
fn blackbox_test7() {
    let raw = [
        0x00, 0x00, 0x00, 0x00, 0x0c, 0x05, 0x00, 0x00, 0x93, 0x0a, 0x00, 0x00,
        0x93, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x58, 0x00, 0x00,
        0x01, 0x10, 0x01, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xab, 0x04, 0x00, 0x00, 0xaa, 0x04, 0xaa, 0x04,
        0xe8, 0x03, 0x84, 0x03, 0x04, 0x00, 0xaf, 0x04, 0x02, 0x00, 0x03, 0x00,
        0x02, 0x00, 0x02, 0x00, 0x0e, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x0f,
        0x00, 0x00, 0x00, 0x00, 0xe9, 0x01, 0xae, 0x04, 0x0f, 0x00, 0x00, 0x00,
        0x0e, 0x0e, 0x0e, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xaa, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ];
    let bb = Blackbox::from_slice(&raw);
    bb_dump(&bb);
}

#[test]
fn blackbox_test8() {
    let raw = [
        0x00, 0x00, 0x00, 0x00, 0x0c, 0x05, 0x00, 0x00, 0x2f, 0x1f, 0x00, 0x00,
        0x2f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x58, 0x00, 0x00,
        0x01, 0x10, 0x01, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xab, 0x04, 0x00, 0x00, 0xaa, 0x04, 0xaa, 0x04,
        0xe8, 0x03, 0x84, 0x03, 0x04, 0x00, 0xaf, 0x04, 0x02, 0x00, 0x03, 0x00,
        0x02, 0x00, 0x02, 0x00, 0x0e, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x0f,
        0x00, 0x00, 0x00, 0x00, 0xe9, 0x01, 0xae, 0x04, 0x0f, 0x00, 0x00, 0x00,
        0x0e, 0x0e, 0x0e, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xaa, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ];

    let bb = Blackbox::from_slice(&raw);

    bb_dump(&bb);
    println!("{:?}", bb.rails[0].vin.get().unwrap());
    assert_eq!(bb.rails[0].vin.get(), Ok(units::Volts(11.950001)));
}
