use crate::network::*;
use crate::packet::*;
use basin2_lib::result::*;
use bytes::BytesMut;

#[derive(PartialEq, Clone, Debug)]
pub struct UpdateAdvancementsPacket {
    pub reset: bool,
    pub added: Vec<(ResourceLocation, Advancement)>,
    pub removed: Vec<ResourceLocation>,
    pub progress: Vec<(ResourceLocation, Option<i64>)>,
}

impl CodablePacket for UpdateAdvancementsPacket {
    fn encode(self, buf: &mut BytesMut) {
        buf.set_mc_bool(self.reset);
        buf.set_mc_var_int(self.added.len() as i32);
        for (key, value) in self.added {
            buf.set_mc_string(key);
            match value.parentId {
                Some(parentId) => {
                    buf.set_mc_bool(true);
                    buf.set_mc_string(parentId);
                }
                None => {
                    buf.set_mc_bool(false);
                }
            }
            match value.display {
                Some(display) => {
                    buf.set_mc_bool(true);
                    buf.set_mc_chat_component(display.title);
                    buf.set_mc_chat_component(display.description);
                    buf.set_mc_item_stack(display.icon);
                    buf.set_mc_var_int(display.frame as i32);
                    let mut flags: i32 = 0;
                    if display.background.is_some() {
                        flags |= 1;
                    }
                    if display.showToast {
                        flags |= 2;
                    }
                    if display.hidden {
                        flags |= 4;
                    }
                    buf.set_mc_i32(flags);
                    if display.background.is_some() {
                        buf.set_mc_string(display.background.unwrap());
                    }
                    buf.set_mc_f32(display.x);
                    buf.set_mc_f32(display.y);
                }
                None => {
                    buf.set_mc_bool(false);
                }
            }
            buf.set_mc_var_int(value.criterion.len() as i32);
            for critereon in value.criterion {
                buf.set_mc_string(critereon);
            }
            buf.set_mc_var_int(value.requirements.len() as i32);
            for requirement_set in value.requirements {
                buf.set_mc_var_int(requirement_set.len() as i32);
                for requirement in requirement_set {
                    buf.set_mc_string(requirement);
                }
            }
        }
        buf.set_mc_var_int(self.removed.len() as i32);
        for key in self.removed {
            buf.set_mc_string(key);
        }
        buf.set_mc_var_int(self.progress.len() as i32);
        for (key, value) in self.progress {
            buf.set_mc_string(key);
            match value {
                Some(timestamp) => {
                    buf.set_mc_bool(true);
                    buf.set_mc_i64(timestamp);
                }
                None => {
                    buf.set_mc_bool(false);
                }
            }
        }
    }

    fn decode(buf: &mut BytesMut) -> Result<Self>
    where
        Self: Sized,
    {
        let reset = buf.get_mc_bool()?;
        let added_count = buf.get_mc_var_int()?;
        let mut added: Vec<(ResourceLocation, Advancement)> = vec![];
        for _ in 0..added_count {
            let key = buf.get_mc_string(32767)?;
            let parentId = if buf.get_mc_bool()? {
                Some(buf.get_mc_string(32767)?)
            } else {
                None
            };
            let display = if buf.get_mc_bool()? {
                let title = buf.get_mc_chat_component()?;
                let description = buf.get_mc_chat_component()?;
                let icon = buf.get_mc_item_stack()?;
                let frame: FrameType = buf.get_mc_enum()?;
                let flags = buf.get_mc_i32()?;
                let showToast = (flags & 2) > 0;
                let hidden = (flags & 4) > 0;
                let background = if (flags & 1) > 0 {
                    Some(buf.get_mc_string(32767)?)
                } else {
                    None
                };
                let x = buf.get_mc_f32()?;
                let y = buf.get_mc_f32()?;
                Some(AdvancementDisplayInfo {
                    title,
                    description,
                    icon,
                    frame,
                    showToast,
                    hidden,
                    background,
                    x,
                    y,
                })
            } else {
                None
            };
            let criterion_count = buf.get_mc_var_int()?;
            let mut criterion: Vec<String> = vec![];
            for _ in 0..criterion_count {
                criterion.push(buf.get_mc_string(32767)?)
            }
            let mut requirements: Vec<Vec<String>> = vec![];
            let requirements_count = buf.get_mc_var_int()?;
            for _ in 0..requirements_count {
                let requirement_set_count = buf.get_mc_var_int()?;
                let mut requirement_set: Vec<String> = vec![];
                for _ in 0..requirement_set_count {
                    requirement_set.push(buf.get_mc_string(32767)?);
                }
                requirements.push(requirement_set);
            }
            let advancement = Advancement {
                parentId,
                display,
                criterion,
                requirements,
            };
            added.push((key, advancement));
        }
        let removed_count = buf.get_mc_var_int()?;
        let mut removed: Vec<ResourceLocation> = vec![];
        for _ in 0..removed_count {
            removed.push(buf.get_mc_string(32767)?);
        }
        let progress_count = buf.get_mc_var_int()?;
        let mut progress: Vec<(ResourceLocation, Option<i64>)> = vec![];
        for _ in 0..progress_count {
            let key = buf.get_mc_string(32767)?;
            let timestamp = if buf.get_mc_bool()? {
                Some(buf.get_mc_i64()?)
            } else {
                None
            };
            progress.push((key, timestamp));
        }
        return Ok(UpdateAdvancementsPacket {
            reset,
            added,
            removed,
            progress,
        });
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::packet::test::*;

    #[test]
    fn test_cycle() -> Result<()> {
        cycle(UpdateAdvancementsPacket {
            reset: false,
            added: vec![(
                "a name".to_string(),
                Advancement {
                    parentId: Some("another advancement".to_string()),
                    display: Some(AdvancementDisplayInfo {
                        title: ChatComponent::from("display".to_string()),
                        description: ChatComponent::from("description".to_string()),
                        icon: ItemStack::empty(),
                        frame: FrameType::Task,
                        background: Some("background".to_string()),
                        showToast: true,
                        hidden: false,
                        x: 120.0,
                        y: 19.0,
                    }),
                    criterion: vec!["criterion1".to_string(), "criterion2".to_string()],
                    requirements: vec![
                        vec!["req11".to_string(), "req12".to_string()],
                        vec!["req21".to_string(), "req22".to_string()],
                    ],
                },
            )],
            removed: vec!["removed_advancement1".to_string()],
            progress: vec![("progress_advancement1".to_string(), Some(12))],
        })
    }
}
