Documentation
use std::collections::HashMap;

use crate::{
    BsdlError,
    string_helpers::{
        find_first_comma_outside_paren, split_once_at_first_comma_outside_paren, strip_parenthesis,
        strip_quotes,
    },
};

#[derive(Debug, PartialEq, Clone)]
pub enum PortDirection {
    /// An input only pin
    In,
    /// An output only pin but may be 3 state
    Out,
    /// An output only pin that only drives 0 or 1
    Buffer,
    /// A bidirectional pin
    Inout,
    /// Other pin type like power, ground, analog, no connect
    Linkage,
    /// Something unexpected
    Other(String),
}

#[derive(Debug)]
pub struct Port {
    /// the port name (uppercase)
    pub name: String,
    /// the pin name/number (uppercase)
    pub pin: String,
    pub dir: PortDirection,
    /// index of grouped pin (usually opposite polarity of diff pair) in Ports vector
    pub grouped: Option<usize>,
    /// AC/AIO
    pub aio_hp_time: Option<f32>,
    pub aio_lp_time: Option<f32>,
    /// boundary scan cell position for input if any
    pub cell_i: Option<usize>,
    /// boundary scan cell position for output if any
    pub cell_o: Option<usize>,
}

impl Port {}

#[derive(Debug, Clone, Copy)]
struct VecRange {
    begin: usize,
    maxindex: usize,
    reversed: bool,
}

impl VecRange {
    /// construct a VecRange from a string like "bit_vector (3 downto 0)" or "bit_vector (1 to 3)"
    fn from_str(s: &str) -> Result<VecRange, BsdlError> {
        let bv = "bit_vector";
        let pos = s.find(bv).ok_or(BsdlError::VecRangeError)?;
        let range = strip_parenthesis(&s[pos + bv.len()..])?;
        let reversed = range.find("downto").is_some();
        let delimiter = match reversed {
            true => "downto",
            false => "to",
        };
        let (begin, end) = range
            .split_once(delimiter)
            .ok_or(BsdlError::VecRangeError)?;
        let begin = begin.trim().parse::<usize>()?;
        let end = end.trim().parse::<usize>()?;
        let maxindex = match reversed {
            true => begin.checked_sub(end),
            false => end.checked_sub(begin),
        }
        .ok_or(BsdlError::VecRangeError)?;
        Ok(VecRange {
            begin,
            maxindex,
            reversed,
        })
    }
    /// for bit_vector (1 to 3), get_index(0) returns 1
    /// for bit_vector (3 downto 0), get_index(0) returns 3
    fn get_index(&self, i: usize) -> Result<usize, BsdlError> {
        if i > self.maxindex {
            return Err(BsdlError::VecRangeError);
        }
        if self.reversed {
            Ok(self.begin - i)
        } else {
            Ok(self.begin + i)
        }
    }
}

#[cfg(test)]
mod test_vecrange {
    use super::*;
    #[test]
    fn test_vecrange() {
        let x = VecRange::from_str("bit_vector (3 downto 0)").unwrap();
        assert!(x.get_index(0).unwrap() == 3);
        assert!(x.get_index(3).unwrap() == 0);
        assert!(x.get_index(4).is_err());
        let x = VecRange::from_str("bit_vector (1 to 2)").unwrap();
        assert!(x.get_index(0).unwrap() == 1);
        assert!(x.get_index(1).unwrap() == 2);
        assert!(x.get_index(2).is_err());
    }
}

#[derive(Debug)]
pub struct Ports {
    pub v: Vec<Port>,
    pub d_port: HashMap<String, usize>,
    pub d_pin: HashMap<String, usize>,
    d_vecs: HashMap<String, VecRange>,
    pub pinmap_applied: bool,
}

impl std::fmt::Display for Ports {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        for (i, v) in self.v.iter().enumerate() {
            write!(f, "port{}: port = {:?}\n", i, v,)?;
        }
        write!(f, "")
    }
}

impl Ports {
    pub fn new() -> Ports {
        Ports {
            v: Vec::new(),
            d_port: HashMap::new(),
            d_pin: HashMap::new(),
            d_vecs: HashMap::new(),
            pinmap_applied: false,
        }
    }
    pub fn from_str(s: &str) -> Result<Ports, BsdlError> {
        let mut v = Vec::new();
        let s = strip_parenthesis(s)?;
        let mut d_vecs = HashMap::new();
        for chunk in s.split(';') {
            let chunk = chunk.trim();
            let (name, rest) = chunk.split_once(':').ok_or(BsdlError::PortError)?;
            let name = name.trim();
            let (dir_str, rest) = rest
                .trim()
                .split_once(char::is_whitespace)
                .ok_or_else(|| BsdlError::PortError)?;
            let dir = match dir_str {
                "in" => PortDirection::In,
                "out" => PortDirection::Out,
                "buffer" => PortDirection::Buffer,
                "inout" => PortDirection::Inout,
                "linkage" => PortDirection::Linkage,
                _ => PortDirection::Other(dir_str.to_string()),
            };
            let rest = rest.trim();
            if rest == "bit" {
                // name may be a comma separated list of names
                for name in name.split(',') {
                    v.push(Port {
                        name: name.trim().to_uppercase(),
                        pin: String::new(),
                        dir: dir.clone(),
                        grouped: None,
                        aio_hp_time: None,
                        aio_lp_time: None,
                        cell_i: None,
                        cell_o: None,
                    });
                }
            } else {
                // this should be "bit_vector (1 to n)" or "bit_vector(1 to n)"
                // downto in place of to is also valid but does not occur in any of 3100 BSDLs evaluated
                let range = VecRange::from_str(rest)?;
                let name = name.to_uppercase();
                for i in 0..range.maxindex + 1 {
                    v.push(Port {
                        name: format!("{}({})", name, range.get_index(i)?),
                        pin: String::new(),
                        dir: dir.clone(),
                        grouped: None,
                        aio_hp_time: None,
                        aio_lp_time: None,
                        cell_i: None,
                        cell_o: None,
                    });
                }
                d_vecs.insert(name, range);
            };
        }
        let mut d_port = HashMap::with_capacity(v.len());
        let d_pin = HashMap::with_capacity(v.len());
        for (i, val) in v.iter().enumerate() {
            d_port.insert(val.name.clone(), i);
        }
        Ok(Ports {
            v,
            d_port,
            d_pin,
            d_vecs,
            pinmap_applied: false,
        })
    }
    pub fn apply_port_grouping(&mut self, s: &str) -> Result<(), BsdlError> {
        let mut s = strip_quotes(s)?;
        loop {
            if s.len() == 0 {
                return Ok(());
            }
            let comma = find_first_comma_outside_paren(s)?;
            let (chunk, rest) = match comma {
                None => (s, ""),
                Some(comma) => (&s[..comma], &s[comma + 1..]),
            };
            s = rest.trim();
            let openparen = chunk
                .find('(')
                .ok_or(BsdlError::ParseError(format!("PORT_GROUPING: expected (")))?;
            // before "(" we should have either DIFFERENTIAL_VOLTAGE or DIFFERENTIAL_CURRENT
            // currently, we ignore it
            let mut port_groups = strip_parenthesis(&chunk[openparen..])?;
            loop {
                if port_groups.is_empty() {
                    break;
                }
                let (port_group, rest) = split_once_at_first_comma_outside_paren(port_groups)?;
                port_groups = rest;
                let port_group = port_group.trim();
                let port_group = strip_parenthesis(port_group)?;
                let (a, b) = port_group.split_once(',').ok_or(BsdlError::NotFoundError)?;
                let c = self.get_index_by_name_portgroup(&a.to_uppercase().as_str())?;
                let d = self.get_index_by_name_portgroup(&b.to_uppercase().as_str())?;
                self.v[c].grouped = Some(d);
                self.v[d].grouped = Some(c);
            }
        }
    }

    fn get_index_by_name_portgroup(&self, name: &str) -> Result<usize, BsdlError> {
        let name = name.trim();
        let idx = self.d_port.get(name);
        match idx {
            Some(x) => Ok(*x),
            _ => Err(BsdlError::ParseError(format!(
                "PORT_GROUPING: {name} not found"
            ))),
        }
    }
    fn apply_pinmap_scalar(&mut self, port: &str, pin: &str) -> Result<(), BsdlError> {
        let portindex = *self.d_port.get(port).ok_or(BsdlError::ParseError(format!(
            "PIN_MAP: port {port} not found"
        )))?;
        self.v[portindex].pin = pin.to_uppercase();
        Ok(())
    }
    pub fn apply_pinmap(&mut self, s: &str) -> Result<(), BsdlError> {
        // s is like "GND : (A1,B2), IO_A10:A10"
        let mut s = strip_quotes(s)?;
        loop {
            s = s.trim();
            if s.len() == 0 {
                break;
            }
            let (port, rest) = s.split_once(':').ok_or(BsdlError::PinMapErr)?;
            let port = port.trim().to_uppercase();
            let comma = find_first_comma_outside_paren(rest)?;
            let pins = match comma {
                Some(comma) => {
                    s = &rest[comma + 1..];
                    &rest[0..comma]
                }
                None => {
                    s = "";
                    rest
                }
            }
            .trim();
            // see if we have a vector, pins will be like (A1, A2)
            if pins.ends_with(')') {
                let pins = strip_parenthesis(pins)?;
                let vecport = self
                    .d_vecs
                    .get(port.as_str())
                    .ok_or(BsdlError::VecRangeError)?;
                let vecport = *vecport;
                for (i, pin) in pins.split(',').enumerate() {
                    let idx = vecport.get_index(i)?;
                    let port = format!("{port}({idx})");
                    self.apply_pinmap_scalar(port.as_str(), pin.trim())?;
                }
            } else {
                // we have a scalar, pins will be like "A1"
                self.apply_pinmap_scalar(port.as_str(), pins)?;
            }
        }
        self.pinmap_applied = true;
        for (i, port) in self.v.iter().enumerate() {
            self.d_pin.insert(port.pin.clone(), i);
        }
        Ok(())
    }
}

#[cfg(test)]
mod test_ports {
    use super::*;
    #[test]
    fn test_ports() {
        let ports = Ports::from_str(
            "(
	VDd: linkage bit_vector (1 to 4);
	GND: linkage bit;
    CLOCK: in bit;
    RESET_N: in bit;
	D: inout bit_vector(0 to 3);
    CSN: out bit;
    SCK: out bit;
	TMS: in bit;
	TCK: in bit;
	TDI: in bit;
	TDO: out bit;
    GT_RXN0: in bit;
    GT_RXP0: in bit;
    GT_TXN0: buffer bit;
    GT_TXP0: buffer bit
 )",
        );
        println!("port = {:?}", ports);
        let mut ports = ports.unwrap();
        let x = ports.apply_port_grouping(
            "\"DIFFERENTIAL_VOLTAGE ((GT_RXP0, GT_RXN0), (GT_TXP0, GT_TXN0))\"",
        );
        ports.apply_pinmap("\"VDD: (1, 2, 18, 19), GND: 21, D: (3, 4, 5, 6), RESET_N: 13, GT_RXP0: 14, GT_RXP0: 15, GT_RXP0: 16, GT_RXP0: 17, CLOCK: 18, SCK: 7, CSN: 8, TMS: 9, TCK: 10, TDI: 11, TDO: 12\"").unwrap();
        println!("x = {:?} port = {:?}", x, ports);
        println!("d_pin: {:?}", ports.d_pin);
    }
}