use laddertypes::{parser::*, unparser::*, SugaredStructMember, SugaredTypeTerm, TypeDict, TypeTerm};

#[derive(Clone, Copy, Debug)]
pub enum ObjectSize {
    Static(u64),
    Dynamic(/* function to get size at runtime */),
    Unknown
}


pub fn get_type_size( dict: &mut impl TypeDict, ty: TypeTerm ) -> ObjectSize {
    let ty = ty.clone().get_lnf_vec().last().unwrap().clone();

    if ty == dict.parse("native.UInt8").expect("") {
        return ObjectSize::Static(1);
    }
    if ty == dict.parse("native.UInt16").expect("") {
        return ObjectSize::Static(2);
    }
    if ty == dict.parse("native.UInt32").expect("") ||
        ty == dict.parse("native.Float").expect("") {
        return ObjectSize::Static(4);
    }
    if ty == dict.parse("native.UInt64").expect("") ||
       ty == dict.parse("native.Double").expect("") {
        return ObjectSize::Static(8);
    }

    if let Some(layout) = StructLayout::parse( dict, ty ) {
        return layout.get_size();
    }

    ObjectSize::Unknown
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum LayoutType {
    Natural,
    Packed
}

#[derive(Clone, Debug)]
pub struct StructLayout {
    ty: laddertypes::TypeTerm,
    item_type: laddertypes::TypeTerm,
    pub members: Vec< (String, laddertypes::TypeTerm) >,
    offsets: std::collections::HashMap< String, u64 >,
    size: ObjectSize
}

impl StructLayout {
    pub fn get_type(&self) -> TypeTerm {
        self.ty.clone()
    }

    pub fn get_size(&self) -> ObjectSize {
        self.size
    }

    pub fn get_offset(&self, name: &String) -> u64 {
        *self.offsets.get(name).expect("")
    }

    pub fn calculate_offsets(&mut self, dict: &mut impl TypeDict) {
        let layout_type = LayoutType::Natural;
        let mut offset = 0;

        for (field_name, field_type) in self.members.iter() {
            //let field_c_type = crate::c_gen::get_c_repr_type(dict, field_type.clone(), true).expect("cant get c-repr for struct field");
            match get_type_size( dict, field_type.clone() ) {
                ObjectSize::Static(field_size) => {
                    if layout_type == LayoutType::Natural {
                        // add padding to natural alignment
                        if offset % field_size > 0 {
                            offset = field_size * ((offset / field_size)+1);
                        }
                    }

                    // take offset
                    self.offsets.insert(field_name.clone(), offset);

                    // advance by current size
                    offset += field_size;
                }
                ObjectSize::Dynamic() => {
                    eprintln!("dynamically sized Field in struct!");
                    self.offsets.insert(field_name.clone(), offset);
                    self.size = ObjectSize::Dynamic();
                    return;
                }
                ObjectSize::Unknown => {
                    eprintln!("unknown field size!");
                    self.offsets.insert(field_name.clone(), offset);
                    self.size = ObjectSize::Unknown;
                    return;
                }
            }
        }

        match self.size {
            ObjectSize::Dynamic() => {

            }
            _ => {
                self.size = ObjectSize::Static(offset);
            }
        }
    }

    pub fn generate_members(&self, dict: &mut impl TypeDict, prefix: &str, base_ptr: &str) {
        let mut offset = 0;

        for (field_name, field_type) in self.members.iter() {
            let field_c_type = crate::c_gen::get_c_repr_type(dict, field_type.clone(), true).expect("cant get c-repr for struct field");
            let offset = self.get_offset(field_name);
            println!(" {} * restrict {}_{} = {} + {};",
                field_c_type, prefix, field_name, base_ptr, offset
            );
        }
    }

    pub fn parse(dict: &mut impl TypeDict, struct_type: TypeTerm) -> Option <Self> {
        match struct_type.sugar(dict) {
            SugaredTypeTerm::Struct(sugared_members) => {
                let mut members = Vec::new();
                let mut item_type = dict.parse("Byte").expect("");

                for SugaredStructMember{ symbol, ty } in sugared_members {
                    members.push((symbol, ty.desugar(dict)));
                }

                let mut sl = StructLayout {
                    ty: TypeTerm::unit(),
                    item_type,
                    members,
                    offsets: std::collections::HashMap::new(),
                    size: ObjectSize::Unknown
                };

                sl.calculate_offsets(dict);
                sl.calculate_type(dict);

                Some(sl)
            }

            _ => {
                eprintln!("not a struct");
                None
            }
        }
    }

    fn calculate_type(&mut self, dict: &mut impl TypeDict) {
        self.ty =
            SugaredTypeTerm::Struct(
                self.members
                    .iter()
                    .cloned()
                    .map(|(symbol,ty)|
                        SugaredStructMember{ symbol, ty:ty.sugar(dict) })
                    .collect()
            )
                .desugar(dict);

    }
}