#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use core::sync::atomic::*;

use defmt::*;
use embassy_executor::Spawner;
use embassy_executor::_export::StaticCell;
//use embassy_futures::join::join;
//use embassy_futures::select::*;
use embassy_rp::adc::{self, Adc};
use embassy_rp::peripherals::{*, self};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Timer, Instant};
use embassy_rp::gpio::{Input, Level, Output, Pull};
use embassy_rp::i2c;
use embassy_rp::interrupt;
//use embedded_hal_async::i2c::I2c;
use embassy_embedded_hal::SetConfig;
use embassy_rp::uart::{self, Parity};
use embassy_rp::pwm::{self, Pwm};
use {defmt_rtt as _, panic_probe as _};
use heapless::String;

use heizung::i2c_scan::{self, ScanResultForBus};
use heizung::modbus_server::{ModbusServer, ModbusRegisters, ModbusErrorCode, ModbusAdressMatch, U16Pusher};
use heizung::program_info::{ProgramInfoBuilder, PinFunction, ConstString};
use heizung::rs485::RS485;
use heizung::uf2updater::UF2UpdateHandler;
use heizung::watchdog::WatchdogFixed;

macro_rules! singleton {
    ($val:expr) => {{
        type T = impl Sized;
        static STATIC_CELL: StaticCell<T> = StaticCell::new();
        let (x,) = STATIC_CELL.init(($val,));
        x
    }};
}

#[embassy_executor::task]
async fn i2c_task(mut i2c_peri: embassy_rp::peripherals::I2C0,
        mut scl: embassy_rp::peripherals::PIN_13, mut sda: embassy_rp::peripherals::PIN_12,
        mut i2c_irq: embassy_rp::interrupt::I2C0_IRQ, i2c_config: i2c::Config) {
    let mut prev_scan_result = ScanResultForBus::new();
    loop {
        if true {
            let _i2c = i2c::I2c::new_async(&mut i2c_peri, &mut scl, &mut sda, &mut i2c_irq, i2c_config);
        }

        let scan_result = ScanResultForBus::scan(&mut scl, &mut sda).await;
        if prev_scan_result != scan_result {
            prev_scan_result = scan_result;
            const MSG_LEN : usize = i2c_scan::SCAN_RESULT_STRING_LENGTH;
            let mut msg = String::<MSG_LEN>::new();
            match scan_result.format(&mut msg) {
                Ok(()) => info!("I2C scan result:\n{}", msg),
                Err(()) => info!("I2C scan result: too long for our buffer!"),
            }
        }

        Timer::after(Duration::from_secs(1)).await;
    }
}

#[embassy_executor::task]
async fn beeper_task(pwm_channel: embassy_rp::peripherals::PWM_CH3, beeper_pin: embassy_rp::peripherals::PIN_6) {
    let mut c: pwm::Config = Default::default();
    c.top = 0x8000;
    c.compare_a = 8;
    c.divider = fixed::FixedU16::from_num(20);
    let mut pwm = Pwm::new_output_a(pwm_channel, beeper_pin, c.clone());

    let mut toggle = false;
    loop {
        info!("PWM: duty={}/32768, divider={}", c.compare_a, c.divider.to_bits());
        Timer::after(Duration::from_millis(200)).await;
        //c.compare_a = c.compare_a.rotate_left(4);
        c.compare_a = if toggle { c.top/2 } else { 0 };
        //toggle = !toggle;
        c.divider -= fixed::FixedU16::from_num(0.1);
        if c.divider < 1 {
            c.divider = fixed::FixedU16::from_num(20);
        }
        toggle = true;
        pwm.set_config(&c);
    }
}

#[embassy_executor::task]
async fn uart_task(this: RS485<ModbusServer<ModBusRegs<'static>>>) {
    this.run_task().await;
}

// Zero offset for current measurement will be exponential smoothing with alpha=1/ZERO_OFFSET_FACTOR.
// The value in zero_offset_raw will be the smoothed offset times ZERO_OFFSET_FACTOR. This factor
// should be a power of two because we will divide by it.
const ZERO_OFFSET_FACTOR : u32 = 32;

struct AdcCurrentData {
    // Value measured while both MOSFETs are off
    // (multiplied by ZERO_OFFSET_FACTOR)
    zero_offset_raw: AtomicU32,
    // Current measured with 2k resistor (raw value and 1/10 mA)
    current_2k_raw: AtomicU16,
    current_2k_dezi_milliamps: AtomicU16,
    // Current measured while main MOSFET is/was on (raw value and 1/10 mA and when this was measured)
    current_on_raw: AtomicU16,
    current_on_dezi_milliamps: AtomicU16,
    current_on_time: Mutex<CriticalSectionRawMutex, Instant>,
}

impl AdcCurrentData {
    const fn const_default() -> AdcCurrentData {
        AdcCurrentData {
            zero_offset_raw: AtomicU32::new(0),
            current_2k_raw: AtomicU16::new(0),
            current_2k_dezi_milliamps: AtomicU16::new(0),
            current_on_raw: AtomicU16::new(0),
            current_on_dezi_milliamps: AtomicU16::new(0),
            current_on_time: Mutex::new(Instant::from_ticks(0)),
        }
    }
}

struct AdcData {
    analog_in1_raw: AtomicU16,
    analog_in1_millivolt: AtomicU16,
    vcc_raw: AtomicU16,
    vcc: AtomicU16,
    temp_raw: AtomicU16,
    temp_centicelsius: AtomicU16,

    currents: [AdcCurrentData; 2],
}

impl AdcData {
    const fn const_default() -> AdcData {
        AdcData {
            analog_in1_raw: AtomicU16::new(0),
            analog_in1_millivolt: AtomicU16::new(0),
            vcc_raw: AtomicU16::new(0),
            vcc: AtomicU16::new(0),
            temp_raw: AtomicU16::new(0),
            temp_centicelsius: AtomicU16::new(0),
            currents: [AdcCurrentData::const_default(), AdcCurrentData::const_default()],
        }
    }
}

#[embassy_executor::task]
async fn adc_task(shared_data: &'static AdcData, adc: ADC, mut en_measure_current: Output<'static, PIN_3>,
    mut analog_in1: PIN_26, mut current2: PIN_27, mut current1: PIN_28, mut measure_vcc: PIN_29, outputs_active: &'static AtomicU8) {
    let irq = interrupt::take!(ADC_IRQ_FIFO);
    let mut adc = Adc::new(adc, irq, adc::Config::default());

    let debug = false;

    const MEASURE_CYCLES_BEFORE_TOGGLE: u32 = 16;
    let mut en_measure_current_toggle = 0;

    loop {
        if true {
            let level = adc.read(&mut analog_in1).await;
            // 3.05 V for 4096 counts, voltage divider with (10k + 5.1k) over 5.1k, result in mV:
            // level * 3.05/4096. * (10+5.1+5.1)/5.1 * 1000
            // -> level * 305 * (100+51+51) * 1000 / 100 / 4096 / 51
            // -> level * 305 * ((100+51+51)/2) * (1000/100) / 2048 / 51  --> fits in 32 bits for level=4096 (but barely)
            let value = level as u32 * 305 * ((100+51+51)/2) * (1000/100);
            let value = (value + 2048*51/2) / 2048 / 51;
            let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 };
            shared_data.analog_in1_raw.store(level, Ordering::Relaxed);
            shared_data.analog_in1_millivolt.store(value, Ordering::Relaxed);
            if debug {
                let value2 = 3.05*level as f32/4096. * (10.+2.*5.1)/5.1;
                info!("ADC analog_in1: {} cnt -> {} V ({} mV)", level, value2, value);
            }
        }

        if true {
            let level = adc.read(&mut measure_vcc).await;
            // 3.05V for 4096 counts, voltage divider with 100k over 10, result in mV:
            // level * 3.05/4096. * (100 + 10)/10 * 1000
            // -> level * 305 * 11 * 1000 / 100 / 4096
            let value = (level as u32 * 305 * 11 * 1000 / 100 + 2048) / 4096;
            let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 };
            shared_data.vcc_raw.store(level, Ordering::Relaxed);
            shared_data.vcc.store(value, Ordering::Relaxed);
            if debug {
                let vcc = 3.05 * level as f32 / 4096. * (100. + 10.)/10.;
                info!("ADC measure_vcc: {} cnt -> {} V ({} mV)", level, vcc, value);
            }
        }

        let outputs_were_active = outputs_active.load(Ordering::SeqCst);
        let mut measure_currents = true;
        if outputs_were_active != 0 && false {
            // output is active but also en_measure_current -> turn it off
            // -> Actually, don't do that because there is no harm and it can be useful for the other channel.
            if en_measure_current.is_set_high() {
                en_measure_current.set_low();
                measure_currents = false;
            }
            en_measure_current_toggle = 0;
        } else {
            match en_measure_current_toggle {
                0 | MEASURE_CYCLES_BEFORE_TOGGLE => {
                    en_measure_current.set_level(if en_measure_current_toggle >= MEASURE_CYCLES_BEFORE_TOGGLE { Level::High } else { Level::Low });
                    // skip in this loop to give it time to settle
                    measure_currents = false;
                }
                _ => {}
            }
            en_measure_current_toggle = (en_measure_current_toggle + 1) % (2*MEASURE_CYCLES_BEFORE_TOGGLE);
        }

        // read another value between looking at output_active and measuring the currents
        if true {
            let level = adc.read_temperature().await;
            // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet
            let adc_vcc = 3.0522;  // from CJ431
            let celsius = 27.0 - (level as f32 * adc_vcc / 4096.0 - 0.706) / 0.001721;
            let value = celsius * 100.;
            let value = if value > u16::MAX as f32 { u16::MAX } else { value as u16 };
            shared_data.temp_raw.store(level, Ordering::Relaxed);
            shared_data.temp_centicelsius.store(value, Ordering::Relaxed);
            if debug {
                info!("Temp: {} -> {} deg C", level, celsius);
            }
        }

        if measure_currents {
            let levels = [
                adc.read(&mut current1).await,
                adc.read(&mut current2).await,
            ];
            let outputs_are_active = outputs_active.load(Ordering::SeqCst);
            //let en_measure_current_is_active = en_measure_current.is_set_high();  //FIXME I think is_set_high() has a bug.
            let en_measure_current_is_active = en_measure_current_toggle >= MEASURE_CYCLES_BEFORE_TOGGLE;
            for i in 0..2 {
                // 3.05 V for 4096 counts, shunt resistor is 0.1 ohms (i.e. 0.1 V/A), amplifier gain is 56, result should be mA/10
                // (level - offset) * 3.05/4096 / 0.1 * 1/56 * 1e4
                // -> (level - offset) * 305 * 10 * 1e4 / 56 / 4096 / 100
                // -> (level - offset) * 305 * 1000 / 56 / 4096
                // -> (level - offset) * 305 * (1000/8) / 4096 / 7
                let level = levels[i];
                let offset = (shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed)/ZERO_OFFSET_FACTOR) as u16;
                let level2 = if level > offset { level - offset } else { 0 };
                //info!("CUR{}: raw: {}, {}, zero={}", i, level, level2, shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed));
                let value = (level2 as u32 * 305 * (1000/8) + 7*4096/2) / 4096 / 7;
                let value = if value > u16::MAX as u32 { u16::MAX } else { value as u16 };

                let output_was_active = (outputs_were_active>>i) & 1 != 0;
                let output_is_active = (outputs_are_active>>i) & 1 != 0;
                #[allow(unused_variables, unused_assignments)]
                let mode : char;
                match (output_was_active, output_is_active, en_measure_current_is_active) {
                    (true, true, false) => {
                        // output was consistently (hopefully) turned on during our measurement
                        // and en_measure_current is off -> good measurement for current in "on" state
                        shared_data.currents[i].current_on_raw.store(levels[i], Ordering::Relaxed);
                        shared_data.currents[i].current_on_dezi_milliamps.store(value, Ordering::Relaxed);
                        *shared_data.currents[i].current_on_time.lock().await = Instant::now();
                        mode = 'O';
                    }
                    (false, false, false) => {
                        // output and en_measure_current are off -> good measurement for zero level

                        // fetch_update is exactly what we want but it doesn't seem to be available...
                        //shared_data.currents[i].zero_offset_raw.fetch_update(Ordering::Relaxed, Ordering::Relaxed,
                        //    |x| { x*(ZERO_OFFSET_FACTOR-1)/ZERO_OFFSET_FACTOR + levels[i] });
                        //SAFETY: We are the only writers for zero_offset_raw.
                        let x = shared_data.currents[i].zero_offset_raw.load(Ordering::Relaxed);
                        let x2 = x*(ZERO_OFFSET_FACTOR-1)/ZERO_OFFSET_FACTOR + level as u32;
                        shared_data.currents[i].zero_offset_raw.store(x2, Ordering::Relaxed);

                        mode = 'z';
                    }
                    (false, false, true) => {
                        // output is off and en_measure_current is on -> good measurement through 2k resistor
                        shared_data.currents[i].current_2k_raw.store(levels[i], Ordering::Relaxed);
                        shared_data.currents[i].current_2k_dezi_milliamps.store(value, Ordering::Relaxed);
                        mode = 'k';
                    }
                    _ => {
                        // inconsistent state of MOSFETs -> ignore this measurement
                        mode = '!';
                    }
                }
                
                if debug {
                    info!("ADC current{}: {}: {} -> {} [10*mA]", i, mode, level, value);
                }
            }

            if false {
                info!("Currents: 2k={}, {}, {}, zero={}, {} -> {}, {} -> {}, {}",
                    en_measure_current_is_active, levels[0], levels[1],
                    shared_data.currents[0].zero_offset_raw.load(Ordering::Relaxed),
                    shared_data.currents[1].zero_offset_raw.load(Ordering::Relaxed),
                    shared_data.currents[0].current_2k_raw.load(Ordering::Relaxed),
                    shared_data.currents[1].current_2k_raw.load(Ordering::Relaxed),
                    shared_data.currents[0].current_2k_dezi_milliamps.load(Ordering::Relaxed),
                    shared_data.currents[1].current_2k_dezi_milliamps.load(Ordering::Relaxed),
                );
            }
        }

        if debug {
            Timer::after(Duration::from_secs(1)).await;
        } else {
            Timer::after(Duration::from_millis(1)).await;
        }
    }
}


#[embassy_executor::task]
async fn reset_soon(delay: Duration) {
    Timer::after(delay).await;
    cortex_m::peripheral::SCB::sys_reset();
}


const FLASH_SIZE: usize = 2 * 1024 * 1024;

#[derive(Clone, Copy, PartialEq, Eq)]
enum ReadType {
    HoldingRegister,
    InputRegister,
}

const DEVICE_STATE_RESET: u16 = 1;
const DEVICE_STATE_RESPOND_DETECT: u16 = 2;
const DEVICE_STATE_PROCESS_BROADCAST_PROGRAM: u16 = 4;

struct ModBusRegs<'a> {
    led_g: Output<'a, PIN_5>,
    button_boot2: Input<'a, PIN_11>,
    reed3: Input<'a, PIN_19>,
    reed4: Input<'a, PIN_20>,
    reed2: Input<'a, PIN_21>,
    reed1: Input<'a, PIN_22>,
    adc_data: &'a AdcData,
    device_state: u16,
    scratch: u16,
    page_offset_for_flash_read: u16,
    uf2updater: UF2UpdateHandler<FLASH_SIZE>,

    consistent_read_type: ReadType,
    consistent_read_addr: u16,
    consistent_read_length: u16,
    consistent_read_data: [u16; 4],

    spawner: Spawner,
}

impl<'a> ModBusRegs<'a> {
    fn read_reg(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: &[u16]) -> Result<u16, ModbusErrorCode> {
        defmt::assert!(addr >= base_addr);
        defmt::assert!(addr < base_addr + value.len() as u16);
        defmt::assert!(value.len() <= self.consistent_read_data.len());

        self.consistent_read_type = read_type;
        self.consistent_read_addr = base_addr;
        self.consistent_read_length = value.len() as u16;
        self.consistent_read_data[0..value.len()].copy_from_slice(value);

        Ok(self.consistent_read_data[(addr - base_addr) as usize])
    }

    fn read_reg_2x_u32(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: [u32; 2]) -> Result<u16, ModbusErrorCode> {
        self.read_reg(read_type, base_addr, addr, &[
            ((value[0] >> 16) & 0xffff) as u16,
            ((value[0] >> 0) & 0xffff) as u16,
            ((value[1] >> 16) & 0xffff) as u16,
            ((value[1] >> 0) & 0xffff) as u16,
        ])
    }

    fn read_reg_u64(self: &mut Self, read_type: ReadType, base_addr: u16, addr: u16, value: u64) -> Result<u16, ModbusErrorCode> {
        self.read_reg(read_type, base_addr, addr, &[
            ((value >> 48) & 0xffff) as u16,
            ((value >> 32) & 0xffff) as u16,
            ((value >> 16) & 0xffff) as u16,
            ((value >> 0) & 0xffff) as u16,
        ])
    }
}

impl<'a> ModbusRegisters for ModBusRegs<'a> {
    fn is_address_match(self: &mut Self, device_addr: u8) -> ModbusAdressMatch {
        use ModbusAdressMatch::*;

        if device_addr == 0x01 {
            OurAddress
        } else if device_addr == 0x55 {
            BroadcastNoReply
        } else {
            NotOurAddress
        }
    }

    fn read_discrete_input(self: &mut Self, _device_addr: u8, addr: u16) -> Result<bool, ModbusErrorCode> {
        match addr {
            0 => Ok(self.reed1.is_high()),
            1 => Ok(self.reed2.is_high()),
            2 => Ok(self.reed3.is_high()),
            3 => Ok(self.reed4.is_high()),
            4 => Ok(self.button_boot2.is_low()),
            _ => Err(ModbusErrorCode::IllegalDataAddress),
        }
    }

    fn read_input_register(self: &mut Self, _device_addr: u8, addr: u16) -> Result<u16, ModbusErrorCode> {
        if self.consistent_read_type == ReadType::InputRegister
                && addr >= self.consistent_read_addr && addr < self.consistent_read_addr + self.consistent_read_length {
            return Ok(self.consistent_read_data[(addr - self.consistent_read_addr) as usize]);
        }

        match addr {
            0 => Ok(42),

            1 => Ok(self.adc_data.analog_in1_raw.load(Ordering::Relaxed)),
            2 => Ok(self.adc_data.analog_in1_millivolt.load(Ordering::Relaxed)),
            3 => Ok(self.adc_data.vcc_raw.load(Ordering::Relaxed)),
            4 => Ok(self.adc_data.vcc.load(Ordering::Relaxed)),
            5 => Ok(self.adc_data.temp_raw.load(Ordering::Relaxed)),
            6 => Ok(self.adc_data.temp_centicelsius.load(Ordering::Relaxed)),

            7 => Ok((self.adc_data.currents[0].zero_offset_raw.load(Ordering::Relaxed) / ZERO_OFFSET_FACTOR) as u16),
            8 => Ok(self.adc_data.currents[0].current_2k_raw.load(Ordering::Relaxed)),
            9 => Ok(self.adc_data.currents[0].current_2k_dezi_milliamps.load(Ordering::Relaxed)),
            10 => Ok(self.adc_data.currents[0].current_on_raw.load(Ordering::Relaxed)),
            11 => Ok(self.adc_data.currents[0].current_on_dezi_milliamps.load(Ordering::Relaxed)),
            12 | 13 | 14 | 15 => self.read_reg_u64(ReadType::InputRegister, 12, addr,
                self.adc_data.currents[0].current_on_time.try_lock().map(
                    |x| x.as_micros()).unwrap_or(0)),

            16 => Ok((self.adc_data.currents[1].zero_offset_raw.load(Ordering::Relaxed) / ZERO_OFFSET_FACTOR) as u16),
            17 => Ok(self.adc_data.currents[1].current_2k_raw.load(Ordering::Relaxed)),
            18 => Ok(self.adc_data.currents[1].current_2k_dezi_milliamps.load(Ordering::Relaxed)),
            19 => Ok(self.adc_data.currents[1].current_on_raw.load(Ordering::Relaxed)),
            20 => Ok(self.adc_data.currents[1].current_on_dezi_milliamps.load(Ordering::Relaxed)),
            21 | 22 | 23 | 24 => self.read_reg_u64(ReadType::InputRegister, 21, addr,
                self.adc_data.currents[1].current_on_time.try_lock().map(
                    |x| x.as_micros()).unwrap_or(0)),

            25 | 26 | 27 | 28 => {
                let (from, to) = self.uf2updater.get_missing_block_info();
                self.read_reg_2x_u32(ReadType::InputRegister, 25, addr, [from, to])
            },

            _ => Err(ModbusErrorCode::IllegalDataAddress)
        }
    }

    fn write_coil(self: &mut Self, _device_addr: u8, addr: u16, value: bool) -> Result<(), ModbusErrorCode> {
        match addr {
            0 => { self.led_g.set_level(Level::from(value)); Ok(()) },
            _ => Err(ModbusErrorCode::IllegalDataAddress),
        }
    }

    fn read_holding_register(self: &mut Self, _device_addr: u8, addr: u16) -> Result<u16, ModbusErrorCode> {
        if self.consistent_read_type == ReadType::HoldingRegister
                && addr >= self.consistent_read_addr && addr < self.consistent_read_addr + self.consistent_read_length {
            return Ok(self.consistent_read_data[(addr - self.consistent_read_addr) as usize]);
        }

        match addr {
            0 => Ok(self.device_state),
            1 => Ok(42),
            2 => Ok(self.scratch),
            3 => Ok(self.page_offset_for_flash_read),
            _ => Err(ModbusErrorCode::IllegalDataAddress),
        }
    }

    fn write_register(self: &mut Self, _device_addr: u8, addr: u16, value: u16) -> Result<u16, ModbusErrorCode> {
        match addr {
            0 => { self.device_state = value; Ok(self.device_state) },
            1 => {
                const fn be(s: &str) -> u16 {
                    core::assert!(s.len() == 2);
                    core::assert!(s.as_bytes().len() == 2);
                    let x = s.as_bytes()[0];
                    let y = s.as_bytes()[1];
                    u16::from_be_bytes([x, y])
                }
                const OK: u16 = be("OK");  // mark booted
                const UP: u16 = be("UP");  // mark updated -> swap to DFU on next reset
                const PI: u16 = be("PI");  // ping / no-op
                const RE: u16 = be("RE");  // reset
                match value {
                    OK => {
                        self.uf2updater.mark_booted().map(|_| value)
                    },
                    UP if self.uf2updater.successfully_programmed() => {
                        self.uf2updater.mark_updated().map(|_| value)
                    },
                    UP => {
                        Ok(0)
                    },
                    PI => {
                        Ok(value)
                    },
                    RE => {
                        match self.spawner.spawn(reset_soon(Duration::from_millis(100))) {
                            Ok(()) => Ok(value),
                            Err(err) => {
                                error!("Couldn't spawn reset_soon: {:?}", err);
                                Err(ModbusErrorCode::ServerDeviceFailure)
                            }
                        }
                    },
                    _ => Err(ModbusErrorCode::IllegalDataValue),
                }
            },
            2 => { self.scratch = value; Ok(value) },
            3 => { self.page_offset_for_flash_read = value; Ok(value) },
            _ => Err(ModbusErrorCode::IllegalDataAddress),
        }
    }

    fn read_file_record(self: &mut Self, _device_addr: u8,
            ref_type: u8, file_number: u16, record_number: u16, record_length: u16, mut pusher: U16Pusher<'_, 256>)
            -> Result<(), ModbusErrorCode> {
        match (ref_type, file_number) {
            (6, 0) => {
                let value = "Hello, World!!".as_bytes();
                if record_number as usize >= value.len()/2 {
                    return Ok(())  // or should that be an error?
                }
                let start = 2 * record_number as usize;
                let end = start + 2 * record_length as usize;
                let end = core::cmp::min(end, value.len());  // or should too large length be an error?
                pusher.push_bytes(&value[start .. end])?;
                Ok(())
            },
            (6, 1) => {
                pusher.push_fn(record_length as usize * 2, |buf| {
                    let flash_addr = self.page_offset_for_flash_read as u32 * 512 + record_number as u32 * 2;
                    self.uf2updater.flash.read(flash_addr, buf)
                        .map_err(|err| {
                            info!("Error reading flash: {:?}", err);
                            ModbusErrorCode::ServerDeviceFailure
                        })
                })
            },
            _ => {
                Err(ModbusErrorCode::IllegalDataAddress)
            }
        }
    }

    fn write_file_record(self: &mut Self, device_addr: u8,
            ref_type: u8, file_number: u16, record_number: u16, data: &[u8])
            -> Result<(), ModbusErrorCode> {
        match (ref_type, file_number) {
            (6, 0) => {
                Ok(())
            },
            (6, 1) => {
                if device_addr != 0x55 || (self.device_state & DEVICE_STATE_PROCESS_BROADCAST_PROGRAM) != 0 {
                    //NOTE This will use blocking flash operations and we won't be able to respond to Modbus
                    //     during that time. There isn't much that we can do about this because the flash won't
                    //     be in XIP mode so we cannot continue running our program from flash.
                    self.uf2updater.write(record_number as u32 * 2, data)
                } else {
                    Ok(())
                }
            },
            _ => Err(ModbusErrorCode::IllegalDataAddress),
        }
    }
}

#[embassy_executor::task]
async fn dogfeeder(watchdog: peripherals::WATCHDOG) {
    WatchdogFixed::new(watchdog).start_and_feed_continuously().await;
}

// use 19200 baud in 8E1 mode - not great but it's the Modbus default
const DEFAULT_MODBUS_CONFIG_BAUDRATE: u32 = 19200;
//const DEFAULT_MODBUS_CONFIG_BAUD = 115200*2;
const DEFAULT_MODBUS_CONFIG_PARITY: Parity = Parity::ParityEven;
const DEFAULT_MODBUS_CONFIG_STRING: ConstString<30> = {
    let mut x = ConstString::new();
    x = x.append_u32(DEFAULT_MODBUS_CONFIG_BAUDRATE);
    x = x.push_str(" baud, ");
    x = x.push_str("8");
    x = x.push(match DEFAULT_MODBUS_CONFIG_PARITY {
        Parity::ParityEven => 'E',
        Parity::ParityOdd => 'O',
        Parity::ParityNone => 'N',
    } as u8);
    x = x.push_str("1");
    x
};
fn default_modbus_config() -> uart::Config {
    let mut uart_config = uart::Config::default();
    uart_config.baudrate = DEFAULT_MODBUS_CONFIG_BAUDRATE;
    //uart_config.baudrate = 115200*2;
    uart_config.parity = DEFAULT_MODBUS_CONFIG_PARITY;
    uart_config
}

//#[embassy_executor::main]
async fn main2(spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    info!("starting");

    // pinout:
    let mut _drive2 = Output::new(p.PIN_0, Level::Low);
    let mut _drive1 = Output::new(p.PIN_1, Level::Low);
    //let LED_W = p.PIN_2;
    let _ws2812_display = p.PIN_2;
    //let LED_Y = p.PIN_3;
    let en_measure_current = Output::new(p.PIN_3, Level::Low);
    let mut led_b = Output::new(p.PIN_4, Level::Low);
    let led_g = Output::new(p.PIN_5, Level::Low);
    let led_r = p.PIN_6;
    let _matrix_in1 = Input::new(p.PIN_7, Pull::Up);
    let _sbu1 = p.PIN_8;
    let _sbu2 = p.PIN_9;
    let _vbus_det = p.PIN_10;
    let button_boot2 = Input::new(p.PIN_11, Pull::Up);
    let sda = p.PIN_12;
    let scl = p.PIN_13;
    let _ws2811_red_in = Input::new(p.PIN_14, Pull::None);
    let tx_en = p.PIN_15;
    let tx = p.PIN_16;
    let rx = p.PIN_17;
    let _matrix_in2 = Input::new(p.PIN_18, Pull::Up);
    let reed3 = Input::new(p.PIN_19, Pull::Up);
    let reed4 = Input::new(p.PIN_20, Pull::Up);
    let reed2 = Input::new(p.PIN_21, Pull::Up);
    let reed1 = Input::new(p.PIN_22, Pull::Up);
    let _matrix_in3 = Input::new(p.PIN_23, Pull::Up);
    let _digout1 = p.PIN_24;
    let _digout2 = p.PIN_25;
    let analog_in1 = p.PIN_26;
    let current2 = p.PIN_27;
    let current1 = p.PIN_28;
    let measure_vcc = p.PIN_29;

    let i2c_irq = interrupt::take!(I2C0_IRQ);
    let mut i2c_config = i2c::Config::default();
    i2c_config.frequency = 400_000;
    unwrap!(spawner.spawn(i2c_task(p.I2C0, scl, sda, i2c_irq, i2c_config)));

    static ADC_DATA: AdcData = AdcData::const_default();
    static OUTPUTS_ACTIVE: AtomicU8 = AtomicU8::new(0);
    unwrap!(spawner.spawn(adc_task(&ADC_DATA, p.ADC, en_measure_current, analog_in1, current2, current1, measure_vcc, &OUTPUTS_ACTIVE)));

    let rx_buf = singleton!([0u8; 256]);
    let rs485 = RS485::new(
        p.UART0, rx, tx, tx_en, interrupt::take!(UART0_IRQ),
        p.DMA_CH1, default_modbus_config(), rx_buf,
        p.PIO0, p.DMA_CH0, p.DMA_CH2,
        ModbusServer::new(ModBusRegs {
            led_g,
            button_boot2,
            reed1, reed2, reed3, reed4,
            adc_data: &ADC_DATA,
            device_state: DEVICE_STATE_RESET | DEVICE_STATE_RESPOND_DETECT,
            scratch: 0,
            page_offset_for_flash_read: 0,
            uf2updater: UF2UpdateHandler::new(p.FLASH),
            consistent_read_type: ReadType::InputRegister,
            consistent_read_addr: u16::default(),
            consistent_read_length: u16::default(),
            consistent_read_data: [0; 4],
            spawner: spawner.clone(),
        }),
    );
    unwrap!(spawner.spawn(uart_task(rs485)));

    if false {
        unwrap!(spawner.spawn(beeper_task(p.PWM_CH3, led_r)));
    }

    unwrap!(spawner.spawn(dogfeeder(p.WATCHDOG)));

    loop {
        led_b.set_high();
        Timer::after(Duration::from_secs(1)).await;

        led_b.set_low();
        Timer::after(Duration::from_secs(1)).await;
    }
}


/*
#[embassy_executor::main]
async fn main(spawner: Spawner) {
    main2(spawner).await;
}
*/

#[::embassy_executor::task()]
async fn __embassy_main(spawner: Spawner) {
    main2(spawner).await;
}

unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
    ::core::mem::transmute(t)
}

#[cortex_m_rt::entry]
fn main() -> ! {
    init_early();

    let mut executor = ::embassy_executor::Executor::new();
    let executor = unsafe { __make_static(&mut executor) };
    executor.run(|spawner| {
        spawner.must_spawn(__embassy_main(spawner));
    })
}

fn init_early() {
    use embassy_rp::pac;
    // release spinlock 31 because we sometimes block on this in the init code
    unsafe { pac::SIO.spinlock(31).write_value(1); }
}



// Add program info.
// Show with: picotool info heizung-release.uf2 -a
// or `picotool info` if actual hardware is connected in bootloader mode
#[used]
#[link_section = ".program_info"]
pub static PROGRAM_INFO: [u8; 4096] = {
    // all of these must match the values in the bootloader
    const PROGRAM_INFO_FLASH_OFFSET: u32 = 0x10087000 - PROGRAM_INFO_TOTAL_SIZE;  // last 4k of active partition
    const PROGRAM_INFO_TOTAL_SIZE: u32 = 4096;
    const PROGRAM_INFO_TABLE_SIZE: u32 = 128;  // one pointer (4 bytes) for each piece of data, all must be valid pointers
    const PROGRAM_INFO_COPY_TABLE_SIZE: u32 = 128;  // 3*u32 for each entry, picotool stops after 10 entries

    #[cfg(debug_assertions)]
    let build_type = "Debug";
    #[cfg(not(debug_assertions))]
    let build_type = "Release";

    // We can fill in some values from info that is provided by Cargo.
    // see https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates

    let mut info =
        ProgramInfoBuilder::new(PROGRAM_INFO_FLASH_OFFSET, PROGRAM_INFO_TABLE_SIZE, PROGRAM_INFO_COPY_TABLE_SIZE)
        .program_name("subraum-heizung")
        .program_description(env!("CARGO_PKG_DESCRIPTION"))
        .program_version_string(env!("CARGO_PKG_VERSION"))
        .program_url(env!("CARGO_PKG_HOMEPAGE"))
        .board("subraum-heizung base v1.0")
        .sdk_version("Embassy")
        .program_build_attribute(build_type);

    //FIXME should be based on what is selected for embassy-rp crate
    info = info.boot2_name("boot2_w25q080");

    let build_timestamp = build_time::build_time_utc!("%s");
    if build_timestamp.len() == 1 && build_timestamp.as_bytes()[0] == '0' as u8 {
        // We are building under NixOS or some other system that doesn't let us get the real time so omit it.
    } else {
        let local_build_time = build_time::build_time_local!("%Y-%m-%d %H:%M:%S %:z");
        info = info.program_build_date_string(local_build_time);
    }

    info = info
        .pins_with_func(PinFunction::UART, &[16, 17])
        .pins_with_func(PinFunction::USB, &[10])
        .pins_with_func(PinFunction::I2C, &[12, 13])
        .pins_with_names(&[
            (0, "DRIVE2"),
            (1, "DRIVE1"),
            (2, "WS2812 Ring around Display, white LED"),
            (3, "Enable current measurement, yellow LED"),
            (4, "blue LED"),
            (5, "green LED"),
            (6, "buzzer, red LED"),
            (7, "MATRIX_IN1"),
            (8, "USB type-c connector, SBU1"),
            (9, "USB type-c connector, SBU2"),
            (11, "Button ADDR"),
            (14, "Red channel of WS2811"),
            (15, "RS485 TX Enable"),
        ])  // picotool doesn't support too many pins at once so add a break here
        .pins_with_names(&[
            (18, "MATRIX_IN2"),
            (19, "REED Input 3"),
            (20, "REED Input 4"),
            (21, "REED Input 2"),
            (22, "REED Input 1"),
            (23, "MATRIX_IN3"),
            (24, "DIGOUT1"),
            (25, "DIGOUT2"),
            (26, "Analog IN 1"),
            (27, "Current Measurement Channel 2"),
            (28, "Current Measurement Channel 1"),
            (29, "VCC Measurement"),
        ])
        .program_feature_group(['C', '3'], 0x27a7c59b, "Modbus")
        .feature_utf8(&DEFAULT_MODBUS_CONFIG_STRING.to_slice::<{DEFAULT_MODBUS_CONFIG_STRING.len()}>())
        .group_done();

    info.build()
};