extern crate pdf;

#[cfg(test)]
mod tests {

    use crate::retrieval::get_image_by_path;
    use crate::{
        get_prim_by_path_with_file, get_prim_model_by_path_with_file,
        get_prim_tree_by_path_with_file, get_stream_data_by_path_with_file,
        get_xref_table_model_with_file, to_pdf_file, update_contents, update_contents_with_file,
        ContentsModel, PathTrace, PrimitiveModel, TreeViewRequest,
    };

    use image::codecs::jpeg::JpegEncoder;
    use image::{DynamicImage, ImageFormat, RgbaImage};
    use pdf::content::{display_ops, serialize_ops, Op};
    use pdf::file::FileOptions;
    use pdf::object::{Object, ObjectWrite, Page, PlainRef, Resolve};
    use pdf::primitive::Primitive;
    use std::io::Cursor;
    use std::time::Instant;

    macro_rules! timed {
        ($func_call:expr, $label:expr) => {{
            let start = std::time::Instant::now();
            let result = $func_call;
            let duration = std::time::Instant::now().duration_since(start);
            println!("{} took {:?}", $label, duration);
            result
        }};
    }
    // Import items to be tested from the parent module
    const PDF_SPEC_PATH: &str =
        "/home/kschuettler/Dokumente/Scientific Papers/PDF Specification/ISO_32000-2_2020(en).pdf";
    const STUDY_402: &str = "/home/kschuettler/Dokumente/TestFiles/402Study.pdf";

    #[test]
    fn test_read_x_ref() {
        let start = Instant::now();
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let resolver = file.resolver();
        let refs = get_xref_table_model_with_file(&file).unwrap().entries;
        let time = Instant::now().duration_since(start);
        println!("retrieving {} primitives took {:?}", refs.len(), time);
        let start = Instant::now();
        let ex = refs.get(19368).ok_or(Err::<i32, &str>("wtf")).unwrap();
        let prim: Primitive = resolver
            .resolve(PlainRef {
                id: ex.obj_num,
                gen: ex.gen_num,
            })
            .unwrap();
        let time = Instant::now().duration_since(start);
        println!("{:?}", prim);
        println!("retrieving one primitive took {:?}", time);
        let start = Instant::now();
        let refs = get_xref_table_model_with_file(&file).unwrap().entries;
        let time = Instant::now().duration_since(start);
        println!("retrieving {} primitives again took {:?}", refs.len(), time);
    }
    #[test]
    fn test_read_tree() {
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let mut path = Vec::new();
        path.push(TreeViewRequest {
            key: "Index".to_string(),
            children: vec![TreeViewRequest {
                key: "1".to_string(),
                children: vec![],
                expand: true,
            }],
            expand: true,
        });
        path.push(TreeViewRequest {
            key: "Info".to_string(),
            children: vec![],
            expand: true,
        });
        path.push(TreeViewRequest {
            key: "Root".to_string(),
            children: vec![TreeViewRequest {
                key: "Pages".to_string(),
                children: vec![],
                expand: true,
            }],
            expand: true,
        });
        let root = TreeViewRequest {
            key: "Trailer".to_string(),
            children: path,
            expand: true,
        };

        let message = format!("Retrieval of {:?}", root);
        let prim = timed!(get_prim_tree_by_path_with_file(root, &file), message).unwrap();
        for ele in prim {
            println!(
                "{}{} | {} | {} | {:?}",
                "    ".repeat(ele.depth),
                ele.key,
                ele.ptype,
                ele.value,
                ele.path
                    .iter()
                    .map(|p| p.key.to_string())
                    .collect::<Vec<String>>()
                    .join("/")
            );
        }
    }

    #[test]
    fn test_read_by_path() {
        let file = timed!(
            FileOptions::cached().open(STUDY_402).unwrap(),
            "Loading file"
        );
        let path = "/Page4/Resources/XObject/X13/Data";

        let message = format!("Retrieval of {:?}", path);
        let prim = timed!(get_prim_model_by_path_with_file(path, &file), message);
        match prim {
            Ok(prim) => print_node(prim, 0),
            Err(e) => println!("Error: {}", e),
        }
    }

    fn print_node(node: PrimitiveModel, depth: usize) {
        let spaces = "    ".repeat(depth);
        println!(
            "{}{} | {} | {} | {}",
            spaces, node.key, node.ptype, node.sub_type, node.value
        );
        for child in node.children {
            print_node(child, depth + 1);
        }
    }

    #[test]
    fn test_read_trailer() {
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let mut file2 = timed!(FileOptions::uncached().storage(), "Loading storage");

        let trail = timed!(
            file.trailer.to_primitive(&mut file2).unwrap(),
            "writing trailer"
        );
        let trail_model = PrimitiveModel::from_primitive_with_children(
            &trail,
            vec![PathTrace::new("Trailer".to_string(), None)],
        );
        print_node(trail_model, 5);
        println!("{:?}", file.trailer.info_dict);
    }

    #[test]
    fn test_read_pdf_file() {
        use crate::to_pdf_file;
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );

        let _pdf_file = timed!(to_pdf_file(PDF_SPEC_PATH, &file), "pages 1");
        let pdf_file = timed!(to_pdf_file(PDF_SPEC_PATH, &file), "pages 2");
        println!("{:?}", pdf_file);
    }
    #[test]
    fn test_read_contents() {
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );

        let (_, page2_prim, _) = get_prim_by_path_with_file("1", &file).unwrap();
        let resolver = file.resolver();
        let page2 = Page::from_primitive(page2_prim, &resolver).unwrap();
        let mut ops: Vec<Op> = timed!(
            page2.contents.unwrap().operations(&resolver).unwrap(),
            "parse ops"
        );
        let serialized = timed!(serialize_ops(&mut ops).unwrap(), "serializing");
        let display = timed!(display_ops(&mut ops).unwrap(), "displaying");
        println!("Serialized -----------------------------------------------------------------");
        println!("{}", String::from_utf8(serialized).unwrap());
        println!("Displayed -----------------------------------------------------------------");
        for (line, s) in display.iter().enumerate() {
            println!("{}: {}", line, s);
        }
    }
    #[test]
    fn test_read_stream() {
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let prim = timed!(
            get_prim_model_by_path_with_file("1/Contents/1", &file).unwrap(),
            "get prim"
        );
        print_node(prim, 3);
        let content1 = timed!(
            get_stream_data_by_path_with_file("1/Contents/1/Data", &file).unwrap(),
            "get content 1"
        );
        println!("{}", String::from_utf8(content1).unwrap());
    }

    #[test]
    fn test_read_img_by_path() {
        let file = timed!(
            FileOptions::cached()
                .open("/home/kschuettler/Dokumente/TestFiles/402Study.pdf")
                .unwrap(),
            "Loading file"
        );
        let path = "/Page4/Resources/XObject/X13/Data";

        let img = timed!(get_image_by_path(path, &file), "get image").expect("Failed to get image");
        let w = img.width();
        let h = img.height();
        let bytes = (h * w * 3) as usize;
        println!("Image Dimensions: {}-{}", w, h);
        println!("Image Bytes: {}", bytes);
        let mut writer = Cursor::new(Vec::new());
        timed!(
            img.write_to(&mut writer, ImageFormat::Jpeg),
            "save image jpeg"
        );
        let size = writer.get_ref().len();
        println!(
            "size: {} bytes {}% compression",
            size,
            100.0 - ((size as f32 / bytes as f32) * 100.0)
        );
        let mut writer = Cursor::new(Vec::new());
        let mut enc = JpegEncoder::new_with_quality(&mut writer, 10);
        timed!(enc.encode_image(&img), "encode image jpeg 10");
        let size = writer.get_ref().len();
        println!(
            "size: {} bytes {}% compression",
            size,
            100.0 - ((size as f32 / bytes as f32) * 100.0)
        );
    }

    use crate::render::{render, render_with_dpi, Renderer};

    #[test]
    fn test_render() {
        let file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let mut renderer = Renderer::new(&file, 150);
        let mut rendered_pages: Vec<DynamicImage> = vec![];
        timed!(
            for i in 0..1 {
                let img = timed!(
                    renderer.render(&file.get_page(i).unwrap()),
                    format!("render page {}", i)
                );
                rendered_pages.push(img.unwrap())
            },
            "rendering some pages"
        );
        timed!(
            for (num, image) in rendered_pages.iter().enumerate() {
                image.to_rgb8().save(format!("page_{}.jpg", num)).unwrap()
            },
            "saving images"
        );
    }
    #[test]
    fn test_update_contents() {
        let mut file = timed!(
            FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
            "Loading file"
        );
        let string = timed!(update_contents_with_file(
            "Page1",
            ContentsModel {
                parts: vec![vec!["q".to_string(), "Q".to_string()]],
            },
            &mut file,
        )
        .unwrap(), "Updating contents");

        println!("{}", string);
    }
}
