use roslibrust_codegen::{ArrayType, ConstantInfo, FieldInfo, MessageFile, ServiceFile};
use serde::{Deserialize, Serialize};

pub static ROS_TYPENAMES: &[&str] = &[
    "bool", "int8", "uint8", "byte", "char", "int16", "uint16", "int32", "uint32", "int64",
    "uint64", "float32", "float64", "string", "time", "duration",
];

#[derive(Deserialize, Serialize, Clone, Debug)]
pub enum ArrayInfo {
    NotAnArray,
    Vector,
    Array(usize),
}

#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Field {
    pub name: String,
    pub field_type: String,
    pub package: Option<String>,
    pub array_info: ArrayInfo,
}

impl From<&FieldInfo> for Field {
    fn from(value: &FieldInfo) -> Self {
        Self {
            name: value.field_name.clone(),
            field_type: value.field_type.field_type.clone(),
            package: value.field_type.package_name.clone(),
            // TODO this is a little silly, we should probably modify genmsg to use ArrayType instead...
            array_info: match value.field_type.array_info {
                ArrayType::FixedLength(n) => ArrayInfo::Array(n),
                ArrayType::Bounded(_) | ArrayType::Unbounded => ArrayInfo::Vector,
                ArrayType::NotArray => ArrayInfo::NotAnArray,
            },
        }
    }
}

impl Field {
    pub fn is_intrinsic_type(&self) -> bool {
        ROS_TYPENAMES.contains(&self.field_type.as_str())
    }

    pub fn is_vector_type(&self) -> bool {
        matches!(self.array_info, ArrayInfo::Vector)
    }

    pub fn is_fixed_size_array_type(&self) -> bool {
        matches!(self.array_info, ArrayInfo::Array(_))
    }

    pub fn fixed_size_array_size(&self) -> usize {
        if let ArrayInfo::Array(n) = self.array_info {
            n
        } else {
            0
        }
    }
}

#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Constant {
    pub name: String,
    pub constant_type: String,
    pub constant_value: String,
}

impl From<&ConstantInfo> for Constant {
    fn from(value: &ConstantInfo) -> Self {
        Self {
            name: value.constant_name.clone(),
            constant_type: value.constant_type.clone(),
            constant_value: value.constant_value.to_string(),
        }
    }
}

#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct MessageSpecification {
    pub short_name: String,
    pub package: String,
    pub fields: Vec<Field>,
    pub constants: Vec<Constant>,
    pub md5sum_first: String,
    pub md5sum_second: String,
    pub description: String,
    pub is_fixed_length: bool,
}

impl From<&MessageFile> for MessageSpecification {
    fn from(value: &MessageFile) -> Self {
        let md5sum = value.get_md5sum();
        let mut spec = Self {
            short_name: value.get_short_name(),
            package: value.get_package_name(),
            fields: value.get_fields().iter().map(Field::from).collect(),
            constants: value.get_constants().iter().map(Constant::from).collect(),
            md5sum_first: String::from(&md5sum[..md5sum.len() / 2]),
            md5sum_second: String::from(&md5sum[(md5sum.len() / 2)..]),
            description: value
                .get_definition()
                .replace('"', "\\\"")
                .replace('\n', "\"\n\""),
            is_fixed_length: value.is_fixed_length(),
        };
        spec.fields.iter_mut().for_each(|field| {
            if field.package.is_none() {
                field.package = Some(value.get_package_name());
            }
        });
        spec
    }
}

#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ServiceSpecification {
    pub short_name: String,
    pub package: String,
    pub request_name: String,
    pub response_name: String,
    pub md5sum: String,
}

impl From<&ServiceFile> for ServiceSpecification {
    fn from(value: &ServiceFile) -> Self {
        Self {
            short_name: value.get_short_name(),
            package: value.get_package_name(),
            request_name: value.request().get_short_name(),
            response_name: value.response().get_short_name(),
            md5sum: value.get_md5sum(),
        }
    }
}
