use crate::{value::Value, EResult, Error, OID};
use std::{
    borrow::Borrow,
    collections::{BTreeMap, BTreeSet},
    fmt,
    io::Cursor,
    ops::Deref,
};

use binrw::prelude::*;

use serde::{Deserialize, Deserializer, Serialize};

impl Borrow<str> for Name {
    fn borrow(&self) -> &str {
        &self.0
    }
}

#[derive(Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum Endianess {
    Big,
    #[default]
    Little,
}

impl From<Endianess> for binrw::Endian {
    #[inline]
    fn from(e: Endianess) -> binrw::Endian {
        match e {
            Endianess::Big => binrw::Endian::Big,
            Endianess::Little => binrw::Endian::Little,
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct ObjectMap {
    #[serde(rename = "data_objects")]
    pub objects: BTreeMap<Name, DataObject>,
}

fn parse_value_by_kind(
    buf: &mut Cursor<&[u8]>,
    kind: &Kind,
    map: &BTreeMap<Name, DataObject>,
    endianess: Endianess,
) -> EResult<Value> {
    let value = match kind {
        Kind::Bool => {
            let v = u8::read(buf).map_err(Error::invalid_data)?;
            Value::Bool(v > 0)
        }
        Kind::U8 => {
            let v = u8::read(buf).map_err(Error::invalid_data)?;
            Value::U8(v)
        }
        Kind::I8 => {
            let v = i8::read(buf).map_err(Error::invalid_data)?;
            Value::I8(v)
        }
        Kind::U16 => {
            let v = u16::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::U16(v)
        }
        Kind::I16 => {
            let v = i16::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::I16(v)
        }
        Kind::U32 => {
            let v = u32::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::U32(v)
        }
        Kind::I32 => {
            let v = i32::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::I32(v)
        }
        Kind::U64 => {
            let v = u64::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::U64(v)
        }
        Kind::I64 => {
            let v = i64::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::I64(v)
        }
        Kind::F32 => {
            let v = f32::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::F32(v)
        }
        Kind::F64 => {
            let v = f64::read_options(buf, endianess.into(), ()).map_err(Error::invalid_data)?;
            Value::F64(v)
        }
        Kind::Array(k, n) => {
            let mut values = Vec::with_capacity(*k);
            for _ in 0..*k {
                let value = parse_value_by_kind(buf, n, map, endianess)?;
                values.push(value);
            }
            Value::Seq(values)
        }
        Kind::DataObject(ref s) => {
            if let Some(object) = map.get(s) {
                let mut values = BTreeMap::new();
                for field in &object.fields {
                    let kind = &field.kind;
                    let value = parse_value_by_kind(buf, kind, map, endianess)?;
                    values.insert(Value::String(field.name.to_string()), value);
                }
                Value::Map(values)
            } else {
                return Err(Error::not_found(s));
            }
        }
    };
    Ok(value)
}

impl ObjectMap {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn merge(&mut self, other: Self) {
        for (k, v) in other.objects {
            self.objects.insert(k, v);
        }
    }
    pub fn extend(&mut self, objects: Vec<DataObject>) {
        for object in objects {
            self.objects.insert(object.name.clone(), object);
        }
    }
    pub fn mapped_oids(&self, name: &Name) -> BTreeSet<OID> {
        let mut oids = BTreeSet::new();
        self.mapped_oids_recursive(name, &mut oids);
        oids
    }
    pub fn mapped_oids_recursive(&self, name: &Name, oids: &mut BTreeSet<OID>) {
        let Some(object) = self.objects.get(name) else {
            return;
        };
        for field in &object.fields {
            if let Some(ref oid) = field.oid {
                oids.insert(oid.clone());
            }
            match field.kind {
                Kind::Array(_, ref k) => {
                    if let Kind::DataObject(ref s) = k.as_ref() {
                        self.mapped_oids_recursive(s, oids);
                    }
                }
                Kind::DataObject(ref s) => {
                    self.mapped_oids_recursive(s, oids);
                }
                _ => {}
            }
        }
    }
    pub fn remove_bulk<B: Borrow<str> + Ord>(&mut self, names: &[B]) {
        for name in names {
            self.objects.remove(name.borrow());
        }
    }
    pub fn validate(&self) -> EResult<()> {
        let mut invalid_objects: BTreeSet<&Name> = <_>::default();
        for v in self.objects.values() {
            for field in &v.fields {
                self.validate_kind(&field.kind, &mut invalid_objects);
            }
        }
        if !invalid_objects.is_empty() {
            let mut err = String::new();
            for (s, i) in invalid_objects.into_iter().enumerate() {
                if s > 0 {
                    err.push_str(", ");
                }
                err.push_str(i);
            }
            return Err(Error::invalid_data(format!("objects not found: {}", err)));
        }
        Ok(())
    }
    pub fn parse_values(
        &self,
        object: &Name,
        buf: &[u8],
        endianess: Endianess,
    ) -> EResult<BTreeMap<OID, Value>> {
        let mut cursor = Cursor::new(buf);
        let mut values = BTreeMap::new();
        self.parse_values_recursive(&mut cursor, object, &self.objects, &mut values, endianess)?;
        Ok(values)
    }
    fn parse_values_recursive(
        &self,
        buf: &mut Cursor<&[u8]>,
        object: &Name,
        map: &BTreeMap<Name, DataObject>,
        values: &mut BTreeMap<OID, Value>,
        endianess: Endianess,
    ) -> EResult<()> {
        let Some(object) = map.get(object) else {
            return Err(Error::not_found(object));
        };
        for field in &object.fields {
            let kind = &field.kind;
            if let Some(ref oid) = field.oid {
                let pos = buf.position();
                let value = parse_value_by_kind(buf, kind, map, endianess)?;
                values.insert(oid.clone(), value);
                buf.set_position(pos);
            }
            match kind {
                Kind::Array(n, ref k) => {
                    if let Kind::DataObject(ref s) = k.as_ref() {
                        for _ in 0..*n {
                            self.parse_values_recursive(buf, s, map, values, endianess)?;
                        }
                    } else {
                        buf.set_position(buf.position() + u64::try_from(self.kind_size(kind)?)?);
                    }
                    continue;
                }
                Kind::DataObject(ref s) => {
                    self.parse_values_recursive(buf, s, map, values, endianess)?;
                    continue;
                }
                _ => {
                    buf.set_position(buf.position() + u64::try_from(self.kind_size(kind)?)?);
                    continue;
                }
            }
        }
        Ok(())
    }
    pub fn size_of(&self, name: &Name) -> EResult<usize> {
        let mut size = 0;
        for field in &self
            .objects
            .get(name)
            .ok_or_else(|| Error::invalid_data(format!("object not found: {}", name)))?
            .fields
        {
            size += self.kind_size(&field.kind)?;
        }
        Ok(size)
    }
    fn kind_size(&self, kind: &Kind) -> EResult<usize> {
        match kind {
            Kind::Bool | Kind::I8 | Kind::U8 => Ok(1),
            Kind::I16 | Kind::U16 => Ok(2),
            Kind::I32 | Kind::U32 | Kind::F32 => Ok(4),
            Kind::I64 | Kind::U64 | Kind::F64 => Ok(8),
            Kind::Array(n, k) => {
                let k_size = self.kind_size(k)?;
                Ok(n * k_size)
            }
            Kind::DataObject(ref s) => self.size_of(s),
        }
    }
    fn validate_kind<'a>(&self, kind: &'a Kind, invalid_objects: &mut BTreeSet<&'a Name>) {
        match kind {
            Kind::Array(_, ref k) => self.validate_kind(k, invalid_objects),
            Kind::DataObject(ref s) => {
                if !self.objects.contains_key(s) {
                    invalid_objects.insert(s);
                }
            }
            _ => {}
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct DataObject {
    #[serde(alias = "i")]
    pub name: Name,
    #[serde(default, alias = "f")]
    pub fields: Vec<Field>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Field {
    #[serde(alias = "i")]
    pub name: Name,
    #[serde(rename = "type", alias = "t")]
    pub kind: Kind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub oid: Option<OID>,
}

#[derive(Debug, Ord, PartialEq, Eq, PartialOrd, Clone)]
pub struct Name(String);

impl Deref for Name {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn is_valid_name(s: &str) -> bool {
    s.chars()
        .all(|c| c.is_alphanumeric() || c.is_numeric() || c == '_')
}

impl TryFrom<String> for Name {
    type Error = Error;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        if is_valid_name(&s) {
            Ok(Name(s))
        } else {
            Err(Error::invalid_data(format!("invalid name: {}", s)))
        }
    }
}

impl TryFrom<&str> for Name {
    type Error = Error;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        if is_valid_name(s) {
            Ok(Name(s.to_owned()))
        } else {
            Err(Error::invalid_data(format!("invalid name: {}", s)))
        }
    }
}

impl From<Name> for String {
    fn from(n: Name) -> String {
        n.0
    }
}

impl fmt::Display for Name {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'de> Deserialize<'de> for Name {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Name::try_from(s).map_err(serde::de::Error::custom)
    }
}

impl Serialize for Name {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Kind {
    Bool,
    I8,
    I16,
    I32,
    I64,
    U8,
    U16,
    U32,
    U64,
    F32,
    F64,
    Array(usize, Box<Kind>),
    DataObject(Name),
}

impl<'de> Deserialize<'de> for Kind {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        let mut sp = s.split(',');
        let kind = sp.next().unwrap();
        let maybe_array: Option<usize> = if let Some(n) = sp.next() {
            let n = n.parse().map_err(serde::de::Error::custom)?;
            Some(n)
        } else {
            None
        };
        if sp.next().is_some() {
            return Err(serde::de::Error::custom("too many commas in type"));
        }
        let data_kind = match kind {
            "bool" | "BOOL" | "BOOLEAN" => {
                maybe_array.map_or_else(|| Kind::Bool, |n| Kind::Array(n, Box::new(Kind::Bool)))
            }
            "i8" | "SINT" => {
                maybe_array.map_or_else(|| Kind::I8, |n| Kind::Array(n, Box::new(Kind::I8)))
            }
            "i16" | "INT" => {
                maybe_array.map_or_else(|| Kind::I16, |n| Kind::Array(n, Box::new(Kind::I16)))
            }
            "i32" | "DINT" => {
                maybe_array.map_or_else(|| Kind::I32, |n| Kind::Array(n, Box::new(Kind::I32)))
            }
            "i64" | "LINT" => {
                maybe_array.map_or_else(|| Kind::I64, |n| Kind::Array(n, Box::new(Kind::I64)))
            }
            "u8" | "USINT" => {
                maybe_array.map_or_else(|| Kind::U8, |n| Kind::Array(n, Box::new(Kind::U8)))
            }
            "u16" | "UINT" => {
                maybe_array.map_or_else(|| Kind::U16, |n| Kind::Array(n, Box::new(Kind::U16)))
            }
            "u32" | "UDINT" => {
                maybe_array.map_or_else(|| Kind::U32, |n| Kind::Array(n, Box::new(Kind::U32)))
            }
            "u64" | "ULINT" => {
                maybe_array.map_or_else(|| Kind::U64, |n| Kind::Array(n, Box::new(Kind::U64)))
            }
            "f32" | "REAL" => {
                maybe_array.map_or_else(|| Kind::F32, |n| Kind::Array(n, Box::new(Kind::F32)))
            }
            "f64" | "LREAL" => {
                maybe_array.map_or_else(|| Kind::F64, |n| Kind::Array(n, Box::new(Kind::F64)))
            }
            v => {
                let name: Name = Name::try_from(v).map_err(serde::de::Error::custom)?;
                if let Some(n) = maybe_array {
                    Kind::Array(n, Box::new(Kind::DataObject(name)))
                } else {
                    Kind::DataObject(name)
                }
            }
        };
        Ok(data_kind)
    }
}

impl fmt::Display for Kind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Kind::Bool => write!(f, "bool"),
            Kind::I8 => write!(f, "i8"),
            Kind::I16 => write!(f, "i16"),
            Kind::I32 => write!(f, "i32"),
            Kind::I64 => write!(f, "i64"),
            Kind::U8 => write!(f, "u8"),
            Kind::U16 => write!(f, "u16"),
            Kind::U32 => write!(f, "u32"),
            Kind::U64 => write!(f, "u64"),
            Kind::F32 => write!(f, "f32"),
            Kind::F64 => write!(f, "f64"),
            Kind::Array(n, k) => write!(f, "{},{}", k, n),
            Kind::DataObject(s) => write!(f, "{}", s),
        }
    }
}

impl Serialize for Kind {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}
