//! Annotation support inspired by qpdf's QPDFAnnotationObjectHelper

use std::collections::HashMap;

use super::error::Result;
use super::object_handle::Rectangle;
use crate::pdf::object::{Dict, Name, ObjRef, Object, PdfString};

pub mod annotation_flags {
    pub const INVISIBLE: u32 = 1 << 0;
    pub const HIDDEN: u32 = 1 << 1;
    pub const PRINT: u32 = 1 << 2;
    pub const NO_ZOOM: u32 = 1 << 3;
    pub const NO_ROTATE: u32 = 1 << 4;
    pub const NO_VIEW: u32 = 1 << 5;
    pub const READ_ONLY: u32 = 1 << 6;
    pub const LOCKED: u32 = 1 << 7;
    pub const TOGGLE_NO_VIEW: u32 = 1 << 8;
    pub const LOCKED_CONTENTS: u32 = 1 << 9;
}

pub struct AnnotationHelper<'a> {
    annot: &'a Dict,
    annot_ref: ObjRef,
}

impl<'a> AnnotationHelper<'a> {
    pub fn new(annot: &'a Dict, annot_ref: ObjRef) -> Self {
        Self { annot, annot_ref }
    }

    pub fn subtype(&self) -> Option<&str> {
        self.annot
            .get(&Name::new("Subtype"))
            .and_then(Object::as_name)
            .map(|n| n.as_str())
    }

    pub fn rect(&self) -> Option<Rectangle> {
        self.annot
            .get(&Name::new("Rect"))
            .and_then(|o| Rectangle::from_object(o))
    }

    pub fn flags(&self) -> u32 {
        self.annot
            .get(&Name::new("F"))
            .and_then(Object::as_int)
            .map(|i| i as u32)
            .unwrap_or(0)
    }

    pub fn is_invisible(&self) -> bool {
        (self.flags() & annotation_flags::INVISIBLE) != 0
    }

    pub fn is_hidden(&self) -> bool {
        (self.flags() & annotation_flags::HIDDEN) != 0
    }

    pub fn is_print(&self) -> bool {
        (self.flags() & annotation_flags::PRINT) != 0
    }

    pub fn is_read_only(&self) -> bool {
        (self.flags() & annotation_flags::READ_ONLY) != 0
    }

    pub fn appearance_dictionary(&self) -> Option<&Object> {
        self.annot.get(&Name::new("AP"))
    }

    pub fn appearance_state(&self) -> Option<&str> {
        self.annot
            .get(&Name::new("AS"))
            .and_then(Object::as_name)
            .map(|n| n.as_str())
    }

    pub fn get_appearance_stream(&self, which: &str, _state: Option<&str>) -> Option<&Object> {
        let ap = self.annot.get(&Name::new("AP"))?.as_dict()?;
        let stream = ap.get(&Name::new(which))?;
        match stream {
            Object::Dict(_) | Object::Stream { .. } => Some(stream),
            Object::Ref(_) => None,
            _ => None,
        }
    }

    pub fn contents(&self) -> Option<&str> {
        self.annot
            .get(&Name::new("Contents"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }

    pub fn modified_date(&self) -> Option<&str> {
        self.annot
            .get(&Name::new("M"))
            .and_then(Object::as_string)
            .and_then(|s| s.as_str())
    }
}

pub fn flatten_annotations(
    page: &mut Dict,
    annotations: &[&Dict],
    objects: &HashMap<ObjRef, Object>,
) -> Result<Vec<u8>> {
    let mut content = Vec::new();
    for annot in annotations {
        let helper = AnnotationHelper::new(annot, ObjRef::new(0, 0));
        if helper.is_hidden() || helper.is_invisible() {
            continue;
        }
        let rect = match helper.rect() {
            Some(r) => r,
            None => continue,
        };
        let ap_obj = helper.get_appearance_stream("N", helper.appearance_state());
        if ap_obj.is_none() {
            continue;
        }
        let stream = ap_obj.unwrap();
        let (dict, data) = match stream {
            Object::Stream { dict, data } => (dict, data),
            _ => continue,
        };
        let xobject_name = dict
            .get(&Name::new("Name"))
            .and_then(Object::as_name)
            .map(|n| n.as_str().to_string())
            .unwrap_or_else(|| format!("Fm{}", content.len()));

        let cm = format!(
            "q 1 0 0 1 {} {} cm /{} Do Q\n",
            rect.llx, rect.lly, xobject_name
        );
        content.extend_from_slice(cm.as_bytes());
        content.extend(data);
    }
    Ok(content)
}

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

    fn make_link_annotation() -> Dict {
        let mut dict = HashMap::new();
        dict.insert(Name::new("Subtype"), Object::Name(Name::new("Link")));
        dict.insert(
            Name::new("Rect"),
            Object::Array(vec![
                Object::Real(100.0),
                Object::Real(200.0),
                Object::Real(300.0),
                Object::Real(250.0),
            ]),
        );
        dict.insert(Name::new("F"), Object::Int(4)); // Print
        dict.insert(
            Name::new("Contents"),
            Object::String(PdfString::new(b"link".to_vec())),
        );
        dict
    }

    #[test]
    fn test_subtype_detection() {
        let dict = make_link_annotation();
        let helper = AnnotationHelper::new(&dict, ObjRef::new(1, 0));
        assert_eq!(helper.subtype(), Some("Link"));
    }

    #[test]
    fn test_rect_extraction() {
        let dict = make_link_annotation();
        let helper = AnnotationHelper::new(&dict, ObjRef::new(1, 0));
        let r = helper.rect().unwrap();
        assert_eq!(r.llx, 100.0);
        assert_eq!(r.lly, 200.0);
        assert_eq!(r.urx, 300.0);
        assert_eq!(r.ury, 250.0);
    }

    #[test]
    fn test_flag_checking() {
        let dict = make_link_annotation();
        let helper = AnnotationHelper::new(&dict, ObjRef::new(1, 0));
        assert!(helper.is_print());
        assert!(!helper.is_read_only());
        assert_eq!(helper.flags(), 4);
    }

    #[test]
    fn test_contents() {
        let dict = make_link_annotation();
        let helper = AnnotationHelper::new(&dict, ObjRef::new(1, 0));
        assert_eq!(helper.contents(), Some("link"));
    }
}
