use std::{str::FromStr, fmt::Display};
use proc_macro2::Span;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ErrorMsg {
    #[error("Failed to Parse Attribute: expected list of key/value pairs EX) ATTR_NAME(x = 1) and/or booleans EX) ATTR_NAME(is_x)")]
    FailedToParseAttr,
    #[error("Invalid Item: expected struct")]
    InvalidItem,
    #[error("Missing Attribute: attribute '{0}' is required")]
    MissingAttribute(&'static str),
    #[error("Missing Argument: '{0}' is required")]
    MissingArg(&'static str),
    #[error("Invalid Type: expected {expected}")]
    InvalidType{expected: &'static str},
    #[error("Duplicate Argument")]
    DuplicateArg,
    #[error("Invalid Argument")]
    InvalidArg,
}
use ErrorMsg::*;
#[derive(Debug)]
pub struct Error {
    pub msg: ErrorMsg,
    pub location: Span,
}
impl Error {
    pub fn new(location: Span, msg: ErrorMsg) -> Self {
        Self {
            location,
            msg
        }
    }
}
pub trait Concat: Sized {
    const NO_DUPLICATES: bool = true;
    fn concat(&mut self, other: Self) { *self = other }
}
impl<T: Concat> Concat for ArgResult<T> {
    fn concat(&mut self, other: Self) {
        let mut other = other;
        if other.is_found() {
            self.location = other.location;
            if self.is_found() && T::NO_DUPLICATES {
                self.add_error(DuplicateArg);
            }
        }
        if other.found_with_errors() {
            self.errors.append(&mut other.errors)
        }
        if other.found_with_value() {    
            match self.value {
                Some(ref mut value) => {
                    match other.value {
                        Some(other_value) => {
                            value.concat(other_value);
                        },
                        None => {}
                    }
    
                }
                None => self.value = other.value
            }
        }
    }
}
#[derive(Debug)]
pub struct ArgResult<T> {
    pub value: Option<T>,
    pub errors: Vec<Error>,
    pub location: Span,
}
impl<T> ArgResult<T> {
    pub fn new(location: Span) -> Self {
        Self {
            value: None,
            errors: vec![],
            location,
        }
    }
    pub fn add_error(&mut self, msg: ErrorMsg) {
        self.errors.push(Error::new(self.location, msg));
    }
    pub fn add_value(&mut self, value: T) {
        self.value = Some(value);
    }
    pub fn add_result(&mut self, value: Result<T, ErrorMsg>) {
        match value {
            Ok(value) => self.add_value(value),
            Err(error) => self.add_error(error)
        }
    }
    
    pub fn is_found(&self) -> bool { self.errors.len() > 0 || self.value.is_some() }
    pub fn found_with_errors(&self) -> bool { self.errors.len() > 0 }
    pub fn found_with_value(&self) -> bool { self.value.is_some() }
}
pub trait SynVersion: Sized {
    type Attribute: GetSpan;
    fn deserialize_attr_args(attr: &Self::Attribute) -> Option<Vec<Self::ArgMeta>>;
    fn deserialize_list_args(meta: &Self::ArgMeta) -> Option<Vec<Self::ArgMeta>>;
    type ArgMeta: GetSpan;
    fn deserialize_key(meta: &Self::ArgMeta) -> Option<String>;
    
    fn deserialize_attr_key(meta: &Self::Attribute) -> Option<String>;
    fn deserialize_integer<T>(meta: &Self::ArgMeta) -> Option<T> where T: FromStr, T::Err: Display;
    fn deserialize_float<T>(meta: &Self::ArgMeta) ->  Option<T> where T: FromStr, T::Err: Display;
    fn deserialize_string(meta: &Self::ArgMeta) -> Option<String>;
    fn deserialize_bool(meta: &Self::ArgMeta) -> Option<bool>;
    fn deserialize_array(meta: &Self::ArgMeta) -> Option<Vec<Self::ArgMeta>>;
    type Error;
    fn convert_error(error: Error) -> Self::Error;
}
pub trait GetSpan {
    fn get_span(&self) -> Span;
}
pub trait TryFromMeta<V: SynVersion>: Sized {
    type InitialType: Concat;
    type Metadata;
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType>;
    fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>>;
}
pub fn required_validation<A, B: From<A>>(state: ArgResult<A>, arg_name: &'static str) -> Result<B, Vec<Error>> {
    let mut state = state;
    match state.found_with_errors() {
        true => Err(state.errors),
        false if state.value.is_none() => {
            state.add_error(MissingArg(arg_name));
            Err(state.errors)
        }
        false => Ok(state.value.unwrap().into())
    }
}
impl Concat for String {}
impl<V: SynVersion> TryFromMeta<V> for String {
    type InitialType = Self;
    type Metadata = V::ArgMeta;
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self> {
        let mut result = ArgResult::new(meta.get_span());
        let maybe_string = V::deserialize_string(&meta);
        match maybe_string {
            Some(string) => result.add_value(string),
            None => result.add_error(InvalidType { expected: "string" })
        }
        result
    }
    fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
        required_validation(state, arg_name)
    }
}
impl Concat for bool {}
impl<V: SynVersion> TryFromMeta<V> for bool {
    type InitialType = Self;
    type Metadata = V::ArgMeta;
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
        let mut result = ArgResult::new(meta.get_span());
        let maybe_bool = V::deserialize_bool(&meta);
        match maybe_bool {
            Some(value) => result.add_value(value),
            None => result.add_error(InvalidType { expected: "boolean" })
        }
        result
    }
    fn validate(state: ArgResult<Self::InitialType>, _arg_name: &'static str) -> Result<Self, Vec<Error>> {
        match state.found_with_errors() {
            true => Err(state.errors),
            false if state.value.is_none() => Ok(false),
            false => Ok(state.value.unwrap())
        }
    }
}
impl<T: Concat> Concat for Vec<T> {
    const NO_DUPLICATES: bool = false;
    fn concat(&mut self, other: Self) {
        let mut other = other;
        self.append(&mut other);
    }
}
impl<V: SynVersion, T: TryFromMeta<V, Metadata = V::ArgMeta>> TryFromMeta<V> for Vec<T> {
    type InitialType = Vec<ArgResult<T::InitialType>>;
    type Metadata = V::ArgMeta;
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
        let mut result = ArgResult::new(meta.get_span());
        let array = 
            match V::deserialize_array(&meta) {
                Some(array) => array,
                None => {
                    result.add_error(InvalidType { expected: "array" });
                    return result;
                }
            };
        let mut values = vec![];
        for meta in array {
            let element = T::try_from_meta(meta);
            values.push(element);
        }
        result.add_value(values);
        result
    }
    fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
        let mut state = state;
        let values = 
            match state.found_with_errors() {
                true => return Err(state.errors),
                false if state.value.is_none() => {
                    state.add_error(MissingArg(arg_name));
                    return Err(state.errors);
                },
                false => state.value.unwrap()
            };
        let mut y = vec![];
        for element in values {
            let x = 
                match T::validate(element, arg_name) {
                    Ok(val) => val,
                    Err(ref mut errors) => {
                        state.errors.append(errors);
                        continue;
                    }
                };
            y.push(x);
        }
        match state.errors.len() {
            0 => Ok(y),
            _ => Err(state.errors)
        }
    }
}
impl<V: SynVersion, T: TryFromMeta<V>> TryFromMeta<V> for Option<T> {
    type InitialType = T::InitialType;
    type Metadata = T::Metadata;
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
        T::try_from_meta(meta)
    }
    fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
        match state.value {
            Some(_) => Ok(Some(T::validate(state, arg_name)?)),
            None if state.found_with_errors() => Err(state.errors),
            None => Ok(None)
        }
    }
}
pub trait AttributeName {
    const NAME: &'static str;
}
impl<T: AttributeName> AttributeName for Option<T> {
    const NAME: &'static str = T::NAME;
}
impl<V: SynVersion, T: Attribute<V>> Attribute<V> for Option<T>
where 
    Self: TryFromMeta<V, Metadata = V::Attribute>
{}
pub trait Attribute<V: SynVersion>: AttributeName + TryFromMeta<V, Metadata = V::Attribute> {
    fn from_attrs(location: Span, attrs: Vec<V::Attribute>) -> Result<Self, Vec<V::Error>> {
        let mut result = ArgResult::new(location);
        for attr in attrs {
            let maybe_key = V::deserialize_attr_key(&attr);
            let found_attribute = matches!(maybe_key, Some(key) if key == Self::NAME);
            if found_attribute == false { continue; }
            
            let attr = Self::try_from_meta(attr);
            result.concat(attr);
        }
        let maybe_attr = <Self as TryFromMeta<V>>::validate(result, Self::NAME);
        maybe_attr.map_err(|e| e.into_iter().map(|e| V::convert_error(e)).collect())
    }
}
pub trait CustomArgFromMeta<V: SynVersion>: Sized {
    fn try_from_meta(meta: V::ArgMeta) -> Result<Self, ErrorMsg>;
}
#[derive(Debug)]
pub struct CustomArg<T>(pub T);
impl<V: SynVersion, T: CustomArgFromMeta<V>> TryFromMeta<V> for CustomArg<T> {
    type InitialType = Self;
    type Metadata = V::ArgMeta;
    
    fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
        let mut result = ArgResult::new(meta.get_span());
        let x = <T as CustomArgFromMeta<V>>::try_from_meta(meta);
        result.add_result(x);
        
        let v = result.value.map(|v| Self(v));
        ArgResult { value: v, errors: result.errors, location: result.location }
    }
    fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
        required_validation(state, arg_name)
    }
}
impl<T> Concat for CustomArg<T> {}
impl<T: Default> Default for CustomArg<T> {
    fn default() -> Self { Self(T::default()) }
}
macro_rules! impl_integer {
    ($($type_name: ident), *) => {
        $(
            impl Concat for $type_name {}
            impl<V: SynVersion> TryFromMeta<V> for $type_name {
                type InitialType = Self;
                type Metadata = V::ArgMeta;
                fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
                    let mut result = ArgResult::new(meta.get_span());
                    let maybe_int = V::deserialize_integer(&meta);
                    match maybe_int {
                        Some(value) => result.add_value(value),
                        None => result.add_error(InvalidType { expected: stringify!($type_name) })
                    }
                    result
                }
                fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
                    required_validation(state, arg_name)
                }
            }
        )*
    };
}
macro_rules! impl_float {
    ($($type_name: ident), *) => {
        $(
            impl Concat for $type_name {}
            impl<V: SynVersion> TryFromMeta<V> for $type_name {
                type InitialType = Self;
                type Metadata = V::ArgMeta;
                fn try_from_meta(meta: Self::Metadata) -> ArgResult<Self::InitialType> {
                    let mut result = ArgResult::new(meta.get_span());
                    let maybe_int = V::deserialize_integer(&meta);
                    match maybe_int {
                        Some(value) => result.add_value(value),
                        None => result.add_error(InvalidType { expected: stringify!($type_name) })
                    }
                    result
                }
                fn validate(state: ArgResult<Self::InitialType>, arg_name: &'static str) -> Result<Self, Vec<Error>> {
                    required_validation(state, arg_name)
                }
            }
        )*
    };
}
impl_integer!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
impl_float!(f32, f64);