mod render;
mod retrieval;

#[cfg(test)]
mod tests;

extern crate pdf;

use crate::pdf::object::Resolve;
use crate::render::Renderer;
use image::DynamicImage;
use lazy_static::lazy_static;
use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache};
use pdf::object::{Object, ObjectWrite, Page, PageRc, PlainRef, Stream, Updater};
use pdf::primitive::{Dictionary, Primitive};
use pdf::xref::XRef;
use regex::Regex;
use retrieval::{get_prim_by_path_with_file, get_stream_data_by_path_with_file};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::fs;
use std::ops::Deref;
use std::path::Path;
use std::sync::{Arc, RwLock};
use base64::Engine;
use tauri::ipc::{InvokeResponseBody, Response};
use tauri::{Manager, State};
use uuid::Uuid;
use pdf::backend::Backend;

type CosFile = File<Vec<u8>, ObjectCache, StreamCache, NoLog>;
#[derive(Serialize, Debug, Clone)]
pub struct XRefTableModel {
    pub size: usize,
    pub entries: Vec<XRefEntryModel>,
}
#[derive(Serialize, Debug, Clone)]
pub struct XRefEntryModel {
    pub obj_num: u64,
    pub gen_num: u64,
    pub obj_type: String,
    pub offset: u64,
}
#[derive(Serialize, Debug, Clone)]
pub struct PdfFile {
    pub id: String,
    pub name: String,
    pub path: String,
    pub page_count: u32,
    pub xref_entries: usize,
    pub pages: Vec<PageModel>,
    pub bytes: String,
}

#[derive(Serialize, Debug, Clone)]
pub struct PrimitiveModel {
    pub key: String,
    pub ptype: String,
    pub sub_type: String,
    pub value: String,
    pub children: Vec<PrimitiveModel>,
    pub trace: Vec<PathTrace>,
    pub expanded: bool,
}

#[derive(Serialize, Debug, Clone)]
pub struct PrimitiveTreeView {
    pub depth: usize,
    pub key: String,
    pub ptype: String,
    pub sub_type: String,
    pub value: String,
    pub container: bool,
    pub expanded: bool,
    pub path: Vec<PathTrace>,
    pub active: bool,
}

#[derive(Serialize, Debug, Clone)]
pub struct PathTrace {
    pub key: String,
    #[serde(skip_serializing)]
    pub last_ref: Option<PlainRef>,
    pub last_jump: String,
}
impl PathTrace {
    fn new(key: String, last_ref: Option<PlainRef>) -> PathTrace {
        PathTrace {
            key,
            last_jump: last_ref
                .map(|r| r.id.to_string())
                .unwrap_or("Trailer".to_string()),
            last_ref: last_ref,
        }
    }

    fn trailer() -> PathTrace {
        PathTrace {
            key: "Trailer".to_string(),
            last_ref: None,
            last_jump: "Trailer".to_string(),
        }
    }
}

#[derive(Serialize, Debug, Clone)]
pub struct PageModel {
    key: String,
    obj_num: u64,
    page_num: u64,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TreeViewRequest {
    key: String,
    children: Vec<TreeViewRequest>,
    expand: bool,
}

impl TreeViewRequest {
    fn step(&self) -> Result<Step, String> {
        Step::parse_step(&self.key)
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentsModel {
    parts: Vec<Vec<String>>,
}

#[tauri::command]
fn get_all_files(session: State<Session>) -> Vec<PdfFile> {
    session
        .get_all_files()
        .iter()
        .map(|s| s.read().unwrap().pdf_file.clone())
        .collect()
}

#[tauri::command]
fn get_changes(id: &str, session: State<Session>) -> Result<Vec<PrimitiveModel>, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    Ok(file.cos_file.changes().iter()
        .map(|(obj, (prim, gen))| PrimitiveModel::from_primitive_until_refs(prim, PathTrace::new(obj.to_string(), Some(PlainRef{id: *obj, gen: *gen}))))
        .collect())
}

#[tauri::command]
fn clear_changes(id: &str, session: State<Session>) -> Result<(), String> {
    let lock = session.get_file(id)?;
    let mut file = lock.write().unwrap();
    file.cos_file.clear_changes();
    Ok(())
}
#[tauri::command]
fn get_all_file_ids(session: State<Session>) -> Vec<String> {
    session.get_all_file_ids()
}

#[tauri::command]
fn close_file(id: &str, session: State<Session>) {
    session.handle_close(id);
}

#[tauri::command]
fn get_file_by_id(id: &str, session: State<Session>) -> Result<PdfFile, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    Ok(file.pdf_file.clone())
}

#[tauri::command]
fn upload(path: &str, session: State<Session>) -> Result<String, String> {
    let file = t!(FileOptions::cached().open(path));

    let pdf_file = to_pdf_file(path, &file)?;
    let id = pdf_file.id.clone();

    session.handle_upload(pdf_file, file)?;

    Ok(id)
}

fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
    fn parse_title_from_path(path: &str) -> Option<String> {
        Path::new(path)
            .file_name()
            .and_then(|f| f.to_str().map(|s| s.to_string()))
    }

    let file_name = if let Some(ref info) = file.trailer.info_dict {
        info.title
            .as_ref()
            .map(|p| p.to_string_lossy())
            .unwrap_or(parse_title_from_path(path).unwrap_or_else(|| "Not found".to_string()))
    } else {
        "Not found".to_string()
    };

    let pages = file
        .pages()
        .enumerate()
        .map(|(i, page_ref)| PageModel {
            key: format!("Page {}", i + 1),
            obj_num: page_ref.unwrap().get_ref().get_inner().id,
            page_num: (i + 1) as u64,
        })
        .collect();
    let bytes = base64::engine::general_purpose::STANDARD.encode(t!(fs::read(path)));
    let pdf_file = PdfFile {
        id: Uuid::new_v4().to_string(),
        name: file_name.to_string().into(),
        path: path.to_string().into(),
        page_count: file.num_pages(),
        xref_entries: file.get_xref().len(),
        pages: pages,
        bytes
    };
    Ok(pdf_file)
}

fn read_contents(page: &Page, resolver: &impl Resolve) -> Result<ContentsModel, String> {
    if let Some(ref contents) = page.contents {
        let mut parts = vec![];
        for part in &contents.parts {
            let data = &t!(part.data(resolver));
            let string = String::from_utf8_lossy(data);
            let part = string
                .split("\n")
                .map(|s| s.to_string())
                .collect::<Vec<String>>();
            parts.push(part);
        }
        return Ok(ContentsModel { parts });
    }
    Err(String::from("Error occurred"))
}

#[tauri::command]
async fn get_contents(id: &str, path: &str, session: State<'_, Session>) -> Result<ContentsModel, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();

    let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?;
    let resolver = file.cos_file.resolver();

    let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver));
    read_contents(&page, &resolver)
}

#[tauri::command]
async fn update_contents(
    id: &str,
    path: &str,
    session: State<'_, Session>,
    contents: ContentsModel,
) -> Result<(), String> {
    let lock = session.get_file(id)?;
    let mut file = lock.write().unwrap();
    update_contents_with_file(path, contents, &mut file.cos_file)
}

fn update_contents_with_file(path: &str, contents: ContentsModel, file: &mut CosFile) -> Result<(), String> {
    let mut parts = vec![];
    for part in contents.parts {
        let stream_ref = Stream::new((), part.join("\n").as_bytes());
        parts.push(stream_ref);
    }
    let new_contents = pdf::content::Content{parts};

    if let Some(Step::Page(num)) = Step::parse(path).pop_front() {
        let page_rc: PageRc = t!(file.get_page(num - 1));
        let page_ref = page_rc.get_ref().get_inner().clone();
        let mut page = page_rc.deref().clone();
        page.contents = Some(new_contents);
        let _ = t!(file.update(page_ref, page));
        return Ok(());
    }
    Err(String::from("Path does not lead to a page!"))
}

#[tauri::command]
async fn get_stream_data_as_string(
    id: &str,
    path: &str,
    session: State<'_, Session>,
) -> Result<String, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    let data = get_stream_data_by_path_with_file(path, &file.cos_file)?;
    Ok(String::from_utf8_lossy(&data).into_owned())
}

#[tauri::command]
async fn update_stream_data_as_string(
    id: &str,
    path: &str,
    new_data: &str,
    session: State<'_, Session>,
) -> Result<(), String> {
    let lock = session.get_file(id)?;
    let mut file = lock.write().unwrap();
    let (_, _, trace) = get_prim_by_path_with_file(path, &file.cos_file)?;
    let i = trace.len() - 1;
    let plain_ref = trace[i].last_ref.expect("No trace to update");
    let stream = Stream::new((), new_data.as_bytes());
    let _ = t!(file.cos_file.update(plain_ref, stream));
    Ok(())
}

fn format_img_as_response(img: DynamicImage) -> Result<Response, String> {
    use std::mem;

    let w: u32 = img.width();
    let h: u32 = img.height();
    let mut data: Vec<u8> = img.into_rgba8().into_raw();

    let w_bytes = w.to_le_bytes(); // or to_be_bytes() depending on endianness
    let h_bytes = h.to_le_bytes();

    let old_capacity = data.capacity();

    let ptr = data.as_mut_ptr();
    let len = data.len();
    mem::forget(data);

    // Create a new Vec with the two u32s in front
    let merged = unsafe {
        let mut new_vec = Vec::from_raw_parts(ptr, len, old_capacity);

        // Insert u32 bytes at the front
        new_vec.splice(0..0, w_bytes.iter().copied());
        new_vec.splice(4..4, h_bytes.iter().copied());

        new_vec
    };
    Ok(Response::new(InvokeResponseBody::from(merged)))
}

#[tauri::command]
async fn get_stream_data_as_image(
    id: &str,
    path: &str,
    session: State<'_, Session>,
) -> Result<Response, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    let img = retrieval::get_image_by_path(path, &file.cos_file)?;
    format_img_as_response(img)
}

#[tauri::command]
async fn get_page_by_num(
    id: &str,
    num: u32,
    session: State<'_, Session>,
) -> Result<Response, String> {
    render_page_by_path(id, &format!("Page{}", num), session).await
}

#[tauri::command]
async fn render_page_by_path(
    id: &str,
    path: &str,
    session: State<'_, Session>,
) -> Result<Response, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    let page = get_page_by_path(path, &file.cos_file)?;
    let mut renderer = Renderer::new(&file.cos_file, 150);
    let img = renderer.render(&page)?;

    format_img_as_response(img)
}

fn get_page_by_path(path: &str, file: &CosFile) -> Result<pdf::object::Page, String> {
    let resolver = file.resolver();
    let (_, page_prim, _) = get_prim_by_path_with_file(path, file)?;
    let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver));
    Ok(page)
}

#[tauri::command]
fn get_prim_by_path(
    id: &str,
    path: &str,
    session: State<Session>,
) -> Result<PrimitiveModel, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    get_prim_model_by_path_with_file(path, &file.cos_file)
}

fn get_prim_model_by_path_with_file(path: &str, file: &CosFile) -> Result<PrimitiveModel, String> {
    let steps = Step::parse(path);
    if steps.len() == 0 {
        return Err(String::from(format!("{:?} is not a valid path!", steps)));
    }
    if steps.back().unwrap() == &Step::Data {
        return handle_data_step(steps, file);
    }

    let (_, prim, trace) = retrieval::get_prim_by_steps_with_file(steps, file)?;

    Ok(PrimitiveModel::from_primitive_with_children(&prim, trace))
}

fn handle_data_step(mut steps: VecDeque<Step>, file: &CosFile) -> Result<PrimitiveModel, String> {
    let _data = steps.pop_back();
    let (_, prim, trace) = retrieval::get_prim_by_steps_with_file(steps, file)?;
    if let Primitive::Stream(stream) = prim {
        let sub_type = match stream.info.get("Subtype") {
            Some(element) => match element {
                Primitive::Name(name) => name.as_str().to_string(),
                _ => String::from("-"),
            },
            _ => String::from("-"),
        };
        return Ok(PrimitiveModel::as_data(sub_type, trace));
    }
    Err(String::from("Parent of Data is not a stream"))
}

#[tauri::command]
fn get_prim_tree_by_path(
    id: &str,
    paths: Vec<TreeViewRequest>,
    session: State<Session>,
) -> Result<Vec<PrimitiveTreeView>, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();

    let results = paths
        .into_iter()
        .map(|path| get_prim_tree_by_path_with_file(path, &file.cos_file))
        .collect::<Result<Vec<_>, String>>()?
        .into_iter()
        .flatten()
        .collect();

    Ok(results)
}

fn get_prim_tree_by_path_with_file(
    node: TreeViewRequest,
    file: &CosFile,
) -> Result<Vec<PrimitiveTreeView>, String> {
    let step = node.step()?;
    let (parent, trace) = retrieval::resolve_parent(step.clone(), file)?;

    let trace = vec![trace];
    let mut parent_model: PrimitiveModel;
    if node.expand {
        parent_model = PrimitiveModel::from_primitive_with_children(&parent, trace);

        for child in node.children.iter() {
            expand(child, &mut parent_model, &parent, file)?;
        }
    } else {
        parent_model = PrimitiveModel::from_primitive(step.get_key(), &parent, trace);
    }

    Ok(PrimitiveTreeView::flatten(0, parent_model))
}

fn expand(
    node: &TreeViewRequest,
    parent_model: &mut PrimitiveModel,
    parent: &Primitive,
    file: &CosFile,
) -> Result<(), String> {
    if !node.expand {
        return Ok(());
    }
    let step = node.step()?;
    if let Ok(prim) = retrieval::resolve_step(parent, &step) {
        if let Primitive::Reference(x_ref) = prim {
            let jump = retrieval::resolve_xref(x_ref.id, file)?;
            let mut jump_trace = parent_model.trace.clone();
            jump_trace.push(PathTrace::new(step.get_key(), Some(x_ref.clone())));
            let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
            to_expand.add_children(&jump, &jump_trace);
            expand_children(node, file, &jump, &mut to_expand)?;
        } else {
            let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
            to_expand.add_children(prim, &to_expand.trace.clone());
            expand_children(node, file, prim, &mut to_expand)?;
        }
    }
    Ok(())
}

fn expand_children(
    node: &TreeViewRequest,
    file: &CosFile,
    prim: &Primitive,
    mut expanded: &mut PrimitiveModel,
) -> Result<(), String> {
    for child in node.children.iter() {
        expand(child, &mut expanded, prim, file)?;
    }

    Ok(())
}

#[derive(Debug, PartialEq, Clone)]
pub enum Step {
    String(String),
    Page(u32),
    Number(u64),
    Trailer,
    Data,
}

impl Step {
    fn parse_step(path: &str) -> Result<Step, String> {
        lazy_static! {
            static ref PAGE_RE: Regex = Regex::new(r"^Page(\d+)$").unwrap();
        }
        if path.len() == 0 {
            return Err(String::from("Path is empty"));
        }

        Ok(match &path.parse::<u64>().ok() {
            Some(i) => Step::Number(*i),
            None => match &path[..] {
                "Data" => Step::Data,
                "Trailer" => Step::Trailer,
                "/" => Step::Trailer,
                _ => {
                    if let Some(caps) = PAGE_RE.captures(path) {
                        Step::Page(
                            caps[1]
                                .parse::<u32>()
                                .map_err(|_| format!("Invalid page number in {}", path))?,
                        )
                    } else {
                        Step::String(path.to_string())
                    }
                }
            },
        })
    }

    fn parse(path: &str) -> VecDeque<Step> {
        let mut steps = VecDeque::new();
        if path.starts_with("/") {
            steps.push_back(Step::Trailer);
        }
        let split_path = path.split("/").collect::<VecDeque<&str>>();
        split_path
            .iter()
            .filter_map(|s| Step::parse_step(s).ok())
            .collect::<VecDeque<Step>>()
    }

    fn get_key(&self) -> String {
        match self {
            Step::String(s) => s.clone(),
            Step::Number(i) => i.to_string(),
            Step::Trailer => "Trailer".to_string(),
            Step::Page(n) => format!("Page{}", n),
            Step::Data => "Data".into(),
        }
    }
}

fn append_path(key: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
    let mut new_path = path.clone();
    let last_jump = new_path.last().unwrap().last_ref.clone();
    new_path.push(PathTrace::new(key, last_jump));
    new_path
}

impl PrimitiveModel {
    fn from_primitive(key: String, primitive: &Primitive, path: Vec<PathTrace>) -> PrimitiveModel {
        let value: String = match primitive {
            Primitive::Null => "Null".to_string(),
            Primitive::Integer(i) => i.to_string(),
            Primitive::Number(f) => f.to_string(),
            Primitive::Boolean(b) => b.to_string(),
            Primitive::String(s) => s.to_string_lossy(),
            Primitive::Stream(_) => "-".to_string(),
            Primitive::Dictionary(dict) => PrimitiveModel::format_dict_content(dict),
            Primitive::Array(arr) => PrimitiveModel::format_arr_content(arr),
            Primitive::Reference(pref) => {
                format!("Obj Nr: {} Gen Nr: {}", pref.id, pref.gen)
            }

            Primitive::Name(name) => name.clone().as_str().to_string(),
        };
        let sub_type: String = match primitive {
            Primitive::Dictionary(d) => d
                .get("Type")
                .and_then(|value| match value {
                    Primitive::Name(name) => Some(name.clone().as_str().to_string()),
                    _ => None,
                })
                .unwrap_or(String::from("-")),
            _ => String::from("-"),
        };
        PrimitiveModel {
            key: key,
            ptype: primitive.get_debug_name().into(),
            sub_type: sub_type,
            value: value,
            children: Vec::new(),
            trace: path,
            expanded: false,
        }
    }

    fn as_data(sub_type: String, trace: Vec<PathTrace>) -> PrimitiveModel {
        PrimitiveModel {
            key: "Data".to_string(),
            ptype: "Stream Data".to_string(),
            sub_type: sub_type,
            value: "".to_string(),
            children: Vec::new(),
            trace: append_path("Data".to_string(), &trace),
            expanded: false,
        }
    }

    fn format_dict_content(dict: &Dictionary) -> String {
        let mut result = String::from("{");
        let mut count = 0;
        for (key, value) in dict.iter() {
            result.push_str(&format!(
                "{}: {}",
                key,
                PrimitiveModel::format_prim_short(value)
            ));
            count += 1;
            if count < 4 {
                result.push_str(", ");
            } else {
                result.push_str(",...");
                break;
            }
        }
        result.push_str("}");
        result
    }
    fn format_arr_content(arr: &Vec<Primitive>) -> String {
        if arr.len() == 0 {
            return "[]".to_string();
        }
        let mut result = String::from("[");
        let contents = if arr.len() > 4 { &arr[0..4] } else { &arr[..] };
        for i in 0..contents.len() {
            let prim = contents.get(i).unwrap();
            result.push_str(&PrimitiveModel::format_prim_short(prim));
            if i != contents.len() - 1 {
                result.push_str(", ");
            }
        }

        if arr.len() > 4 {
            result.push_str(",...");
        }
        result.push_str("]");
        result
    }

    fn format_prim_short(prim: &Primitive) -> String {
        match prim {
            Primitive::Integer(i) => format!("{}", i),
            Primitive::Number(n) => format!("{}", n),
            Primitive::Boolean(b) => format!("{}", b),
            Primitive::String(s) => s.to_string().unwrap_or(String::from("-")),
            Primitive::Name(n) => n.as_str().to_string(),
            _ => prim.get_debug_name().to_string(),
        }
    }

    fn from_primitive_with_children(
        primitive: &Primitive,
        trace: Vec<PathTrace>,
    ) -> PrimitiveModel {
        let mut model = PrimitiveModel::from_primitive(
            trace.last().unwrap().key.clone(),
            primitive,
            trace.clone(),
        );
        model.add_children(primitive, &trace);
        model
    }

    fn from_primitive_until_refs(primitive: &Primitive, trace: PathTrace) -> PrimitiveModel {
        let mut parent_model = PrimitiveModel::from_primitive(
            trace.key.clone(),
            primitive,
            vec![trace.clone()],
        );
        let mut model = parent_model;
        model.add_children(primitive, &vec!(trace));
        model
    }

    fn add_children(&mut self, primitive: &Primitive, path: &Vec<PathTrace>) {
        self.expanded = true;
        match primitive {
            Primitive::Dictionary(dict) => dict.iter().for_each(|(name, value)| {
                self.add_child(
                    name.clone().as_str().to_string(),
                    value,
                    append_path(name.clone().as_str().to_string(), &path),
                );
            }),
            Primitive::Array(arr) => arr.iter().enumerate().for_each(|(i, obj)| {
                self.add_child(i.to_string(), obj, append_path(i.to_string(), &path));
            }),
            Primitive::Stream(stream) => {
                stream.info.iter().for_each(|(name, value)| {
                    self.add_child(
                        name.clone().as_str().to_string(),
                        value,
                        append_path(name.clone().as_str().to_string(), &path),
                    );
                });
                self.children.push(PrimitiveModel {
                    key: "Data".to_string(),
                    ptype: "Stream Data".to_string(),
                    sub_type: "-".to_string(),
                    value: "".to_string(),
                    children: vec![],
                    trace: append_path("Data".to_string(), &path),
                    expanded: false,
                });
            }
            _ => (),
        };
    }

    fn add_child(
        &mut self,
        key: String,
        child: &Primitive,
        path: Vec<PathTrace>,
    ) -> &PrimitiveModel {
        let child_model = Self::from_primitive(key, child, path);
        self.children.push(child_model);
        &self.children[self.children.len() - 1]
    }

    fn get_child(&mut self, key: String) -> Option<&mut PrimitiveModel> {
        self.children.iter_mut().find(|child| child.key == key)
    }

    fn is_container(&self) -> bool {
        self.ptype == "Dictionary"
            || self.ptype == "Array"
            || self.ptype == "Stream"
            || self.ptype == "Reference"
    }
    fn drain_children(&mut self) -> Vec<PrimitiveModel> {
        self.children.drain(..).collect()
    }
}

impl PrimitiveTreeView {
    fn from_primitive(depth: usize, primitive: PrimitiveModel) -> PrimitiveTreeView {
        let is_container = primitive.is_container();
        PrimitiveTreeView {
            depth: depth,
            key: primitive.key,
            ptype: primitive.ptype,
            sub_type: primitive.sub_type,
            value: primitive.value,
            container: is_container,
            expanded: primitive.expanded,
            path: primitive.trace,
            active: true,
        }
    }
    fn flatten(depth: usize, mut primitive: PrimitiveModel) -> Vec<PrimitiveTreeView> {
        let mut views: Vec<PrimitiveTreeView> = Vec::new();
        let children = primitive.drain_children();
        views.push(PrimitiveTreeView::from_primitive(depth, primitive));
        children.into_iter().for_each(|child| {
            views.extend(PrimitiveTreeView::flatten(depth + 1, child.clone()));
        });
        views
    }
}
#[tauri::command]
fn get_xref_table(id: &str, session: State<Session>) -> Result<XRefTableModel, String> {
    let lock = session.get_file(id)?;
    let file = lock.read().unwrap();
    get_xref_table_model_with_file(&file.cos_file)
}

fn get_xref_table_model_with_file(file: &CosFile) -> Result<XRefTableModel, String> {
    let resolver = file.resolver();
    let x_ref_table = file.get_xref();
    let mut models: Vec<XRefEntryModel> = Vec::new();

    for (i, x_ref) in x_ref_table.iter_real().enumerate() {
        models.push(match x_ref {
            XRef::Raw { pos, gen_nr } => {
                let prim: Primitive = resolver
                    .resolve(PlainRef {
                        id: i as u64,
                        gen: *gen_nr,
                    })
                    .unwrap();
                XRefEntryModel {
                    obj_num: i as u64,
                    gen_num: *gen_nr,
                    obj_type: prim.get_debug_name().to_string().into(),
                    offset: *pos as u64,
                }
            }
            XRef::Stream { stream_id, index } => XRefEntryModel {
                obj_num: i as u64,
                gen_num: 0,
                obj_type: "Stream".into(),
                offset: *index as u64,
            },
            XRef::Free {
                next_obj_nr,
                gen_nr,
            } => XRefEntryModel {
                obj_num: i as u64,
                gen_num: *gen_nr as u64,
                obj_type: "Free".into(),
                offset: *next_obj_nr as u64,
            },
            XRef::Promised => XRefEntryModel {
                obj_num: i as u64,
                gen_num: 0,
                obj_type: "Promised".into(),
                offset: 0,
            },
            XRef::Invalid => XRefEntryModel {
                obj_num: i as u64,
                gen_num: 0,
                obj_type: "Invalid".into(),
                offset: 0,
            },
        });
    }
    Ok(XRefTableModel {
        size: x_ref_table.len(),
        entries: models,
    })
}

struct Session {
    files: RwLock<HashMap<String, Arc<RwLock<SessionFile>>>>,
}

struct SessionFile {
    pdf_file: PdfFile,
    cos_file: CosFile,
}

impl Session {
    fn load() -> Session {
        Session {
            files: RwLock::new(HashMap::new()),
        }
    }

    fn get_file(&self, id: &str) -> Result<Arc<RwLock<SessionFile>>, String> {
        let lock = self.files.read().unwrap();
        lock.get(id)
            .cloned()
            .ok_or(format!(" File {} not found!", id))
    }

    fn get_all_files(&self) -> Vec<Arc<RwLock<SessionFile>>> {
        self.files.read().unwrap().values().cloned().collect()
    }

    fn get_all_file_ids(&self) -> Vec<String> {
        self.files
            .read()
            .unwrap()
            .keys()
            .map(|f| f.clone())
            .collect()
    }

    fn handle_upload(&self, pdf_file: PdfFile, cos_file: CosFile) -> Result<(), String> {
        if let Ok(mut files) = self.files.write() {
            return match files.insert(
                pdf_file.id.clone(),
                Arc::new(RwLock::new(SessionFile {
                    pdf_file: pdf_file,
                    cos_file: cos_file,
                })),
            ) {
                Some(_) => Err("File could not be uploaded!".to_string()),
                None => Ok(()),
            };
        };
        Err("Lock could not be acquired!".to_string())
    }

    fn handle_close(&self, id: &str) {
        self.files.write().unwrap().remove(id);
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_opener::init())
        .setup(|app| {
            app.manage(Session::load());
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            upload,
            get_all_files,
            get_all_file_ids,
            get_file_by_id,
            close_file,
            get_prim_by_path,
            get_prim_tree_by_path,
            get_xref_table,
            get_contents,
            update_contents,
            get_stream_data_as_string,
            update_stream_data_as_string,
            get_stream_data_as_image,
            get_page_by_num,
            get_changes,
            clear_changes
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
