use std::collections::BTreeMap;
use std::path::Path;
use std::sync::Arc;
pub mod parser;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum ProtocolIdent {
    #[default]
    ProfibusDp,
    ManufacturerSpecific(u8),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum StationType {
    #[default]
    DpSlave,
    DpMaster,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum RepeaterControlSignal {
    #[default]
    NotConnected,
    Rs485,
    Ttl,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum Pins24V {
    #[default]
    NotConnected,
    Input,
    Output,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
#[repr(u8)]
pub enum MainSlaveFamily {
    #[default]
    General = 0,
    Drives = 1,
    SwitchingDevices = 2,
    IOs = 3,
    Valves = 4,
    Controllers = 5,
    Hmis = 6,
    Encoders = 7,
    NcRc = 8,
    Gateways = 9,
    PLCs = 10,
    IdentSystems = 11,
    PA = 12,
    Reserved(u8),
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct SlaveFamily {
    main: MainSlaveFamily,
    sub: Vec<String>,
}
bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct SupportedSpeeds: u16 {
        const B9600 = 1 << 1;
        const B19200 = 1 << 2;
        const B31250 = 1 << 3;
        const B45450 = 1 << 4;
        const B93750 = 1 << 5;
        const B187500 = 1 << 6;
        const B500000 = 1 << 7;
        const B1500000 = 1 << 8;
        const B3000000 = 1 << 9;
        const B6000000 = 1 << 10;
        const B12000000 = 1 << 11;
    }
}
impl Default for SupportedSpeeds {
    fn default() -> Self {
        SupportedSpeeds::empty()
    }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MaxTsdr {
    pub b9600: u16,
    pub b19200: u16,
    pub b31250: u16,
    pub b45450: u16,
    pub b93750: u16,
    pub b187500: u16,
    pub b500000: u16,
    pub b1500000: u16,
    pub b3000000: u16,
    pub b6000000: u16,
    pub b12000000: u16,
}
impl Default for MaxTsdr {
    fn default() -> Self {
        Self {
            b9600: 60,
            b19200: 60,
            b31250: 60,
            b45450: 60,
            b93750: 60,
            b187500: 60,
            b500000: 100,
            b1500000: 150,
            b3000000: 250,
            b6000000: 450,
            b12000000: 800,
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum UserPrmDataType {
    Unsigned8,
    Unsigned16,
    Unsigned32,
    Signed8,
    Signed16,
    Signed32,
    Bit(u8),
    BitArea(u8, u8),
}
impl UserPrmDataType {
    pub fn size(self) -> usize {
        match self {
            UserPrmDataType::Unsigned8 => 1,
            UserPrmDataType::Unsigned16 => 2,
            UserPrmDataType::Unsigned32 => 4,
            UserPrmDataType::Signed8 => 1,
            UserPrmDataType::Signed16 => 2,
            UserPrmDataType::Signed32 => 4,
            UserPrmDataType::Bit(_) => 1,
            UserPrmDataType::BitArea(_, _) => 1,
        }
    }
    pub fn write_value_to_slice(self, value: i64, s: &mut [u8]) {
        match self {
            UserPrmDataType::Unsigned8 => {
                assert!(0 <= value && value <= 255);
                s[..1].copy_from_slice(&(value as u8).to_be_bytes());
            }
            UserPrmDataType::Unsigned16 => {
                assert!(0 <= value && value <= 65535);
                s[..2].copy_from_slice(&(value as u16).to_be_bytes());
            }
            UserPrmDataType::Unsigned32 => {
                assert!(0 <= value && value <= 4294967295);
                s[..4].copy_from_slice(&(value as u32).to_be_bytes());
            }
            UserPrmDataType::Signed8 => {
                assert!(-127 <= value && value <= 127);
                s[..1].copy_from_slice(&(value as i8).to_be_bytes());
            }
            UserPrmDataType::Signed16 => {
                assert!(-32767 <= value && value <= 32767);
                s[..2].copy_from_slice(&(value as i16).to_be_bytes());
            }
            UserPrmDataType::Signed32 => {
                assert!(2147483647 <= value && value <= 2147483647);
                s[..4].copy_from_slice(&(value as i32).to_be_bytes());
            }
            UserPrmDataType::Bit(b) => {
                assert!(value == 0 || value == 1);
                s[0] |= (value as u8) << b;
            }
            UserPrmDataType::BitArea(first, last) => {
                let bit_size = last - first + 1;
                assert!(value >= 0 && value < 2i64.pow(bit_size as u32));
                s[0] = (value as u8) << first;
            }
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PrmValueConstraint {
    MinMax(i64, i64),
    Enum(Vec<i64>),
    Unconstrained,
}
impl PrmValueConstraint {
    pub fn is_valid(&self, value: i64) -> bool {
        match self {
            PrmValueConstraint::MinMax(min, max) => *min <= value && value <= *max,
            PrmValueConstraint::Enum(values) => values.contains(&value),
            PrmValueConstraint::Unconstrained => true,
        }
    }
    pub fn assert_valid(&self, value: i64) {
        match self {
            PrmValueConstraint::MinMax(min, max) => {
                assert!(
                    *min <= value && value <= *max,
                    "value {value} not in range {min}..={max}",
                );
            }
            PrmValueConstraint::Enum(values) => {
                assert!(
                    values.contains(&value),
                    "value {value} not in set {values:?}",
                );
            }
            PrmValueConstraint::Unconstrained => (),
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct UserPrmDataDefinition {
    pub name: String,
    pub data_type: UserPrmDataType,
    pub default_value: i64,
    pub constraint: PrmValueConstraint,
    pub text_ref: Option<Arc<BTreeMap<String, i64>>>,
    pub changeable: bool,
    pub visible: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct UserPrmData {
    pub length: u8,
    pub data_const: Vec<(usize, Vec<u8>)>,
    pub data_ref: Vec<(usize, Arc<UserPrmDataDefinition>)>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Module {
    pub name: String,
    pub config: Vec<u8>,
    pub reference: Option<u32>,
    pub module_prm_data: UserPrmData,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct UnitDiagBitInfo {
    pub text: String,
    pub help: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct UnitDiagArea {
    pub first: u16,
    pub last: u16,
    pub values: BTreeMap<u16, String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct UnitDiag {
    pub bits: BTreeMap<u32, UnitDiagBitInfo>,
    pub not_bits: BTreeMap<u32, UnitDiagBitInfo>,
    pub areas: Vec<UnitDiagArea>,
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct GenericStationDescription {
    pub gsd_revision: u8,
    pub vendor: String,
    pub model: String,
    pub revision: String,
    pub revision_number: u8,
    pub ident_number: u16,
    pub hardware_release: String,
    pub software_release: String,
    pub implementation_type: String,
    pub freeze_mode_supported: bool,
    pub sync_mode_supported: bool,
    pub auto_baud_supported: bool,
    pub set_slave_addr_supported: bool,
    pub fail_safe: bool,
    pub max_diag_data_length: u8,
    pub modular_station: bool,
    pub max_modules: u8,
    pub max_input_length: u8,
    pub max_output_length: u8,
    pub max_data_length: u16,
    pub supported_speeds: SupportedSpeeds,
    pub max_tsdr: MaxTsdr,
    pub available_modules: Vec<Module>,
    pub user_prm_data: UserPrmData,
    pub unit_diag: UnitDiag,
}
pub struct PrmBuilder<'a> {
    desc: &'a UserPrmData,
    prm: Vec<u8>,
}
impl<'a> PrmBuilder<'a> {
    pub fn new(desc: &'a UserPrmData) -> Self {
        let mut this = Self {
            desc,
            prm: Vec::new(),
        };
        this.write_const_prm_data();
        this.write_default_prm_data();
        this
    }
    fn update_prm_data_len(&mut self, offset: usize, size: usize) {
        if self.prm.len() < (offset + size) {
            for _ in 0..((offset + size) - self.prm.len()) {
                self.prm.push(0x00);
            }
        }
    }
    fn write_const_prm_data(&mut self) {
        for (offset, data_const) in self.desc.data_const.iter() {
            self.update_prm_data_len(*offset, data_const.len());
            self.prm[*offset..(offset + data_const.len())].copy_from_slice(data_const);
        }
    }
    fn write_default_prm_data(&mut self) {
        for (offset, data_ref) in self.desc.data_ref.iter() {
            let size = data_ref.data_type.size();
            self.update_prm_data_len(*offset, size);
            data_ref
                .data_type
                .write_value_to_slice(data_ref.default_value, &mut self.prm[(*offset as usize)..]);
        }
    }
    pub fn set_prm(&mut self, prm: &str, value: i64) -> &mut Self {
        let (offset, data_ref) = self
            .desc
            .data_ref
            .iter()
            .find(|(_, r)| r.name == prm)
            .unwrap();
        data_ref.constraint.assert_valid(value);
        data_ref
            .data_type
            .write_value_to_slice(value, &mut self.prm[(*offset as usize)..]);
        self
    }
    pub fn set_prm_from_text(&mut self, prm: &str, value: &str) -> &mut Self {
        let (offset, data_ref) = self
            .desc
            .data_ref
            .iter()
            .find(|(_, r)| r.name == prm)
            .unwrap();
        let text_ref = data_ref.text_ref.as_ref().unwrap();
        let value = *text_ref.get(value).unwrap();
        data_ref.constraint.assert_valid(value);
        data_ref
            .data_type
            .write_value_to_slice(value, &mut self.prm[(*offset as usize)..]);
        self
    }
    pub fn as_bytes(&self) -> &[u8] {
        &self.prm
    }
    pub fn into_bytes(self) -> Vec<u8> {
        self.prm
    }
}
pub fn parse_from_file<P: AsRef<Path>>(file: P) -> GenericStationDescription {
    use std::io::Read;
    let mut f = std::fs::File::open(file.as_ref()).unwrap();
    let mut source_bytes = Vec::new();
    f.read_to_end(&mut source_bytes).unwrap();
    let source = String::from_utf8_lossy(&source_bytes);
    match parser::parse(file.as_ref(), &source) {
        Ok(gsd) => gsd,
        Err(e) => panic!("{}", e),
    }
}