// TODO: try less hard, just mem::transmute all this junk away
// TODO: PSOPacketData derive

#![recursion_limit="256"]

extern crate proc_macro;

use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemStruct, Meta, NestedMeta, Attribute};
use quote::quote;



#[derive(Debug, PartialEq)]
enum AttrMeta {
    None,
    Utf8,
    Utf16,
    NoDebug,
}



#[derive(Debug)]
enum AttrType {
    Value(syn::PathSegment, syn::Ident, AttrMeta),
    Array(syn::PathSegment, syn::Ident, usize, AttrMeta)
}

fn generate_struct_def(name: syn::Ident, attrs: &Vec<AttrType>) -> proc_macro2::TokenStream {
    let mut struct_def = Vec::new();
    for attr in attrs {
        let element = match attr {
            AttrType::Value(ty, name, _) => {
                quote!(#name: #ty)
            },
            AttrType::Array(ty, name, len, _) => {
                quote!(#name: [#ty; #len])
            }
        };
        struct_def.push(element);
    }

    quote! {
        struct #name {
            #(#struct_def),*
        }
    }
}


fn generate_psopacket_impl(pkt_cmd: u16, name: syn::Ident, attrs: &Vec<AttrType>, include_flag: bool) -> proc_macro2::TokenStream {
    let mut from_bytes = Vec::new();
    for attr in attrs {
        let element = match attr {
            AttrType::Value(ty, name, _) => {
                let type_str = ty.ident.to_string();
                if type_str == "Vec" {
                    let vec_type = match &ty.arguments {
                        syn::PathArguments::AngleBracketed(arg) => {
                            match &arg.args[0] {
                                syn::GenericArgument::Type(typ) => {
                                    match &typ {
                                        syn::Type::Path(path) => {
                                            Some(path.path.segments[0].ident.clone())
                                        }
                                        _ => None
                                    }
                                }
                                _ => None,
                            }
                        }
                        _ => None
                    }.unwrap();

                    quote! {
                        #name: {
                            let mut tmp = Vec::new();

                            for _ in 0..flag  {
                                //#name: <#ty as PSOPacketData>::from_bytes(&mut cur)?,
                                tmp.push(<#vec_type as PSOPacketData>::from_bytes(&mut cur)?);
                            }
                            tmp
                        }
                    }
                }
                else {
                    quote! {
                        #name: <#ty as PSOPacketData>::from_bytes(&mut cur)?,
                    }
                }
            },
            AttrType::Array(ty, name, len, _) => {
                quote! {
                    #name: {
                        let mut arr = [#ty::default(); #len];
                        for e in arr.iter_mut() {
                            *e = #ty::from_bytes(&mut cur)?
                        }
                        arr
                    },
                }
            }
        };
        from_bytes.push(element);
    }

    let mut as_bytes = Vec::new();
    for attr in attrs {
        let element = match attr {
            AttrType::Value(ty, name, _) => {
                let type_str = ty.ident.to_string();
                if type_str == "Vec" {
                    quote! {
                        flag = self.#name.len() as u32;
                        for i in self.#name.iter()  {
                            buf.extend_from_slice(&PSOPacketData::as_bytes(i));
                        }
                    }
                }
                else {
                    quote! {
                        buf.extend_from_slice(&PSOPacketData::as_bytes(&self.#name));
                    }
                }
            },
            AttrType::Array(_ty, name, len, _) => {
                quote! {
                    for i in 0..#len {
                        buf.extend_from_slice(&self.#name[i].as_bytes());
                    }
                }
            }
        };
        as_bytes.push(element);
    }

    quote! {
        impl PSOPacket for #name {
            fn from_bytes(data: &[u8]) -> Result<#name, PacketParseError> {
                let mut cur = std::io::Cursor::new(data);
                let mut b: [u8; 2] = [0; 2];
                cur.read(&mut b).unwrap();
                let len = u16::from_le_bytes(b);
                cur.read(&mut b).unwrap();
                let cmd = u16::from_le_bytes(b);
                let mut f: [u8; 4] = [0; 4];

                let flag = if #include_flag {
                    cur.read(&mut f).unwrap();
                    u32::from_le_bytes(f)
                }
                else { 0 };

                if cmd != #pkt_cmd {
                    return Err(PacketParseError::WrongPacketCommand);
                }

                if len as usize != data.len() {
                    return Err(PacketParseError::WrongPacketSize(len, data.len()));
                }

                let result = Ok(#name {
                    #(#from_bytes)*
                });

                if cur.position() as usize != data.len() {
                    return Err(PacketParseError::DataStructNotLargeEnough(cur.position(), data.len()));
                }

                result
            }

            fn as_bytes(&self) -> Vec<u8> {
                let mut buf = Vec::new();
                let mut flag = 0;
                #(#as_bytes)*

                while buf.len() % 4 != 0 {
                    buf.push(0);
                }

                let pkt_len = (buf.len() + if #include_flag { 8 } else { 4 }) as u16;
                let mut prebuf: Vec<u8> = Vec::new();

                prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len));
                prebuf.extend_from_slice(&u16::to_le_bytes(#pkt_cmd));
                if #include_flag {
                    prebuf.extend_from_slice(&u32::to_le_bytes(flag));
                }
                prebuf.append(&mut buf);
                prebuf
            }
        }
    }
}

fn generate_debug_impl(name: syn::Ident, attrs: &Vec<AttrType>) -> proc_macro2::TokenStream {
    let mut dbg_write = Vec::new();
    for attr in attrs {
        let element = match attr {
            AttrType::Value(ty, name, meta) => {
                let ident_str = name.to_string();
                let type_str = ty.ident.to_string();
                match meta {
                    AttrMeta::NoDebug => quote! {
                        write!(f, "    {} {}: [...]\n", #ident_str, #type_str)?;
                    },
                    _ => quote! {
                        write!(f, "    {} {}: {:?}\n", #ident_str, #type_str, self.#name)?;
                    }
                }
            },
            AttrType::Array(ty, name, len, meta) => {
                let ident_str = name.to_string();
                let type_str = ty.ident.to_string();
                match meta {
                    AttrMeta::Utf8 => quote! {
                        match std::str::from_utf8(&self.#name) {
                            Ok(v) => write!(f, "    {} [utf8; {}]: {:?}\n", #ident_str, #len, v)?,
                            Err(_) => write!(f, "    {} [{}; {}]: {:?}\n", #ident_str, #type_str, #len, self.#name.to_vec())?,
                        };
                    },
                    AttrMeta::Utf16 => quote! {
                        match String::from_utf16(&self.#name) {
                            Ok(v) => write!(f, "    {} [utf16; {}]: {:?}\n", #ident_str, #len, v)?,
                            Err(_) => write!(f, "    {} [{}; {}]: {:?}\n", #ident_str, #type_str, #len, self.#name.to_vec())?,
                        };
                    },
                    AttrMeta::NoDebug => quote! {
                        write!(f, "    {} [{}; {}]: [...]\n", #ident_str, #type_str, #len)?;
                    },
                    AttrMeta::None => quote! {
                        write!(f, "    {} [{}; {}]: {:?}\n", #ident_str, #type_str, #len, self.#name.to_vec())?;
                    }
                }
            }
        };
        dbg_write.push(element);
    }
    let name_str = name.to_string();
    quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "packet {} {{\n", #name_str)?;
                #(#dbg_write)*
                write!(f, "}}")
            }
        }
    }
}


fn generate_partialeq_impl(name: syn::Ident, attrs: &Vec<AttrType>) -> proc_macro2::TokenStream {
    let mut partialeq = Vec::new();
    for attr in attrs {
        let element = match attr {
            AttrType::Value(_, name, _) => {
                quote! {
                    if self.#name!= other.#name {
                        return false;
                    }
                }
            },
            AttrType::Array(_, name, _, _) => {
                quote! {
                    if self.#name[..] != other.#name[..] {
                        return false;
                    }
                }

            }
        };
        partialeq.push(element);
    }
    quote! {
        impl std::cmp::PartialEq for #name {
            fn eq(&self, other: &Self) -> bool {
                #(#partialeq)*
                true
            }
        }
    }

}

#[proc_macro_attribute]
pub fn pso_packet2(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as syn::AttributeArgs);
    let mut cmd = 0;
    let mut flag = true;
    for a in args {
        match &a {
            NestedMeta::Lit(lit) => {
                if let syn::Lit::Int(litint) = lit {
                    cmd = litint.base10_parse().unwrap();
                }
            },
            NestedMeta::Meta(k) => {
                if let syn::Meta::Path(syn::Path {segments, ..}) = k {
                    match segments[0].ident.to_string().as_str() {
                        "no_flag" => flag = false,
                        _ => {
                            return syn::Error::new(segments[0].ident.span(), "unknown macro param").to_compile_error().into();
                        }
                    }
                }
            },
        }
    }

    let mut attrs = Vec::new();
    let pkt_struct = parse_macro_input!(item as ItemStruct);

    let mut must_be_last = false;
    for field in pkt_struct.fields.iter() {
        if must_be_last {
            return syn::Error::new(field.ident.as_ref().unwrap().span(), "variables can not follow Vec or String").to_compile_error().into();
        }
        let mut attr_meta = AttrMeta::None;
        for attr in &field.attrs {
            attr_meta = match attr.path.segments[0].ident.to_string().as_str() {
                "utf8" => AttrMeta::Utf8,
                "utf16" => AttrMeta::Utf16,
                "nodebug" => AttrMeta::NoDebug,
                _ => AttrMeta::None
            }
        }
        match &field.ty {
            syn::Type::Array(ty) => {
                if let (syn::Type::Path(ref ty), syn::Expr::Lit(ref lit)) = (&*ty.elem, &ty.len) {
                    if let syn::Lit::Int(ref int) = lit.lit {
                        attrs.push(AttrType::Array(ty.path.segments[0].clone(),
                                                   field.ident.as_ref().unwrap().clone(),
                                                   int.base10_parse().unwrap(),
                                                   attr_meta
                        ))
                    }
                }
            },
            syn::Type::Path(ty) => {
                let type_str = ty.path.segments[0].ident.to_string();
                if type_str == "String" || type_str == "Vec"{
                    must_be_last = true;
                }
                attrs.push(AttrType::Value(ty.path.segments[0].clone(),
                                           field.ident.as_ref().unwrap().clone(),
                                           attr_meta))
            },
            _ => {}
        }
    }

    let struct_def = generate_struct_def(pkt_struct.ident.clone(), &attrs);
    let psopacket_impl = generate_psopacket_impl(cmd, pkt_struct.ident.clone(), &attrs, flag);
    let debug_impl =  generate_debug_impl(pkt_struct.ident.clone(), &attrs);
    let partialeq_impl =  generate_partialeq_impl(pkt_struct.ident.clone(), &attrs);

    let q = quote!{
        #[derive(Clone)]
        #struct_def
        #psopacket_impl
        #debug_impl
        #partialeq_impl
    };

    q.into()
}

#[proc_macro_attribute]
pub fn pso_packet(attr: TokenStream, item: TokenStream) -> TokenStream {
    let arg = parse_macro_input!(attr as syn::LitInt);
    let pkt_cmd = arg.base10_parse::<u16>().unwrap();

    let parsed = parse_macro_input!(item as ItemStruct);

    let mut from_bytes = Vec::new();
    let mut as_bytes = Vec::new();
    let mut dbg_write_vars = Vec::new();
    let mut partialeq = Vec::new();

    for f in parsed.fields.iter() {
        if let Some(ident) = &f.ident {
            let ident_str = ident.to_string();

            match &f.ty {
                syn::Type::Array(arr) => {
                    let array_length = if let syn::Expr::Lit(lit) = &arr.len {
                        if let syn::Lit::Int(int) = &lit.lit {
                            int.base10_parse::<usize>().unwrap()
                        }
                        else {
                            return syn::Error::new(arr.bracket_token.span, "unknown array size").to_compile_error().into();
                        }
                    } else {
                        return syn::Error::new(arr.bracket_token.span, "unknown array size").to_compile_error().into();
                    };
                    match *arr.elem {
                        syn::Type::Path(ref path) => {
                            let ty = path.path.segments[0].ident.to_string();
                            if ty.as_str() == "u8_str" {
                                dbg_write_vars.push(quote! {
                                    match std::str::from_utf8(&self.#ident) {
                                        Ok(v) => write!(f, "    {}: {:?}\n", #ident_str, v).unwrap(),
                                        Err(_) => write!(f, "    {}: {:?}\n", #ident_str, self.#ident.to_vec()).unwrap()
                                    }
                                });
                            }
                            else {
                                dbg_write_vars.push(quote! {
                                    write!(f, "    {}: {:?}\n", #ident_str, self.#ident.to_vec()).unwrap();
                                });
                            }
                            as_bytes.push(quote! {
                                for f in self.#ident.iter() {
                                    buf.extend_from_slice(&f.to_le_bytes())
                                }
                            });
                            match ty.as_str() {
                                "u8" | "u8_str" => {
                                    from_bytes.push(quote! {
                                        #ident: {
                                            let mut b: [u8; #array_length] = [0; #array_length];
                                            if let Ok(len) = cur.read(&mut b) {
                                                if len != #array_length {
                                                    return Err(PacketParseError::NotEnoughBytes);
                                                }
                                            }
                                            else {
                                                return Err(PacketParseError::NotEnoughBytes);
                                            };
                                            b
                                        },
                                    });
                                },
                                "u16" => {
                                    from_bytes.push(quote! {
                                        #ident: {
                                            let mut b: [u16; #array_length] = [0; #array_length];
                                            if let Ok(len) = cur.read(&mut b) {
                                                if len != #array_length {
                                                    return Err(PacketParseError::NotEnoughBytes);
                                                }
                                            }
                                            else {
                                                return Err(PacketParseError::NotEnoughBytes);
                                            };
                                            b
                                        },
                                    });
                                },
                                "u32" => {
                                    from_bytes.push(quote! {
                                        #ident: {
                                            let mut b: [u32; #array_length] = [0; #array_length];
                                            if let Ok(len) = cur.read(&mut b) {
                                                if len != #array_length {
                                                    return Err(PacketParseError::NotEnoughBytes);
                                                }
                                            }
                                            else {
                                                return Err(PacketParseError::NotEnoughBytes);
                                            };
                                            b
                                        },
                                    });
                                },


                                _ => {
                                    return syn::Error::new(path.path.segments[0].ident.span(), "type not supported")
                                        .to_compile_error().into();
                                }
                            }
                            partialeq.push(quote! {
                                if self.#ident[..] != other.#ident[..] {
                                    return false;
                                }
                            });
                        }
                        _ => {
                            panic!("why");
                        }
                    }
                },
                syn::Type::Path(path) => {
                    dbg_write_vars.push(quote! {
                        write!(f, "    {}: {:?}\n", #ident_str, self.#ident).unwrap();
                    });
                    let ty = path.path.segments[0].ident.to_string();

                    // as_bytes
                    match ty.as_str() {
                        "String" => {
                            as_bytes.push(quote! {
                                for c in self.#ident.as_str().encode_utf16() {
                                    buf.extend_from_slice(&c.to_le_bytes());
                                }
                            });
                        }
                        _ => {
                            as_bytes.push(quote! {
                                buf.extend_from_slice(&self.#ident.to_le_bytes());
                            });
                        }
                    }

                    // from_bytes
                    match ty.as_str() {
                        "u8" | "u8_str" => {
                            from_bytes.push(quote! {
                                #ident: {
                                    let mut b: [u8; 1] = [0; 1];
                                    if let Ok(len) = cur.read(&mut b) {
                                        if len != 1 {
                                            return Err(PacketParseError::NotEnoughBytes);
                                        }
                                    }
                                    else {
                                        return Err(PacketParseError::NotEnoughBytes);
                                    };
                                    b[0]
                                },
                            });
                        },
                        "u16" => {
                            from_bytes.push(quote! {
                                #ident: {
                                    let mut b: [u8; 2] = [0; 2];
                                    if let Ok(len) = cur.read(&mut b) {
                                        if len != 2 {
                                            return Err(PacketParseError::NotEnoughBytes);
                                        }
                                    }
                                    else {
                                        return Err(PacketParseError::NotEnoughBytes);
                                    };
                                    u16::from_le_bytes(b)
                                },
                            });
                        },
                        "u32" => {
                            from_bytes.push(quote! {
                                #ident: {
                                    let mut b: [u8; 4] = [0; 4];
                                    if let Ok(len) = cur.read(&mut b) {
                                        if len != 4 {
                                            return Err(PacketParseError::NotEnoughBytes);
                                        }
                                    }
                                    else {
                                        return Err(PacketParseError::NotEnoughBytes);
                                    };
                                    u32::from_le_bytes(b)
                                },
                            });
                        },
                        "String" => {
                            from_bytes.push(quote! {
                                #ident: {
                                    let mut s: Vec<u8> = Vec::new();
                                    if let Ok(len) = cur.read_to_end(&mut s) {
                                    }
                                    else {
                                        return Err(PacketParseError::NotEnoughBytes);
                                    };
                                    let mut utf16 = Vec::new();
                                    for c in s.chunks(2) {
                                        utf16.push(u16::from_le_bytes([c[0], c[1]]));
                                    }
                                    String::from_utf16_lossy(utf16.as_slice())
                                },
                            });
                        },
                        _ => {
                            from_bytes.push(quote! {
                                #ident: {
                                    let mut b: [u8; #path::SIZE] = [0; #path::SIZE];
                                    if let Ok(len) = cur.read(&mut b) {
                                        if len != #path::SIZE {
                                            return Err(PacketParseError::NotEnoughBytes);
                                        }
                                    }
                                    else {
                                        return Err(PacketParseError::NotEnoughBytes);
                                    };
                                    #path::from_le_bytes(b)?
                                },
                            });
                        }
                    }
                    partialeq.push(quote! {
                        if self.#ident != other.#ident {
                            return false;
                        }
                    });
                }
                _ => {
                }
            }
        }
    }

    let this_struct = parsed.ident.clone();
    let this_struct_str = this_struct.to_string();

    let psopacket = quote! {
        impl PSOPacket for #this_struct {
            fn from_bytes(data: &[u8]) -> Result<#this_struct, PacketParseError> {
                let mut cur = std::io::Cursor::new(data);
                let mut b: [u8; 2] = [0; 2];
                cur.read(&mut b).unwrap();
                let len = u16::from_le_bytes(b);
                cur.read(&mut b).unwrap();
                let cmd = u16::from_le_bytes(b);

                if cmd != #pkt_cmd {
                    return Err(PacketParseError::WrongPacketCommand);
                }

                if len as usize != data.len() {
                    return Err(PacketParseError::WrongPacketSize(len, data.len()));
                }

                let result = Ok(#this_struct {
                    #(#from_bytes)*
                });

                if cur.position() as usize != data.len() {
                    return Err(PacketParseError::DataStructNotLargeEnough(cur.position(), data.len()));
                }

                result
            }
            fn as_bytes(&self) -> Vec<u8> {
                let mut buf: Vec<u8> = Vec::new();
                #(#as_bytes)*

                while buf.len() % 4 != 0 {
                    buf.push(0);
                }

                let pkt_len = (buf.len() + 4) as u16;
                let mut prebuf: Vec<u8> = Vec::new();

                prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len));
                prebuf.extend_from_slice(&u16::to_le_bytes(#pkt_cmd));
                prebuf.append(&mut buf);

                prebuf
            }
        }
    };


    let psopacket_debug = quote! {
        impl std::fmt::Debug for #this_struct {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "packet {} {{\n", #this_struct_str).unwrap();
                #(#dbg_write_vars)*
                write!(f, "}}")
            }
        }
    };


    let psopacket_partialeq = quote! {
        impl std::cmp::PartialEq for #this_struct {
            fn eq(&self, other: &Self) -> bool {
                #(#partialeq)*
                true
            }
        }
    };

    let q = quote! {
        #[derive(Clone)]
        #parsed
        #psopacket
        #psopacket_debug
        #psopacket_partialeq
    };

    //println!("[[[{}]]]", q.to_string());

    q.into()
}


#[proc_macro_attribute]
pub fn game_command(attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}