use std::rc::Rc;

use linked_hash_map::LinkedHashMap;

use crate::{hit_mod::IndexModelImporter, HitError, Hit, HitKernel, Patch, generic_import::finish_import, AddedEntry};

use super::generic_import::import_data_object_values;


pub fn import_from_patches<'a>(
    patches: Vec<Patch>,
    id: &str,
    kernel: Rc<HitKernel>
) -> Result<Hit, HitError> {
    let mut entries = LinkedHashMap::new();
    import_from_patches_and_entries(patches, id, kernel, &mut entries)
}

pub fn import_from_patches_and_entries<'a>(
    patches: Vec<Patch>,
    id: &str,
    kernel: Rc<HitKernel>,
    entries: &mut LinkedHashMap<String, AddedEntry>
) -> Result<Hit, HitError> {
    for patch in patches {
        import_patch(patch, entries)?;
    }

    let mut importer = IndexModelImporter::new(id, kernel.clone());
    for entry in entries.iter() {
        let id = entry.0;
        let added_entry = entry.1;
        let model = {
            kernel
                .get_model(&added_entry.model)?
        };
        import_data_object_values(
            model,
            id.clone(),
            added_entry.parent.clone(),
            &mut importer,
            added_entry.data.clone(),
        )?;
    }

    let hit = finish_import(importer, kernel)?;
    return Ok(hit);
}

pub fn apply_patches(hit: &Hit, patch: Vec<Patch>) -> Result<Hit, HitError> {
    let mut entries = LinkedHashMap::new();
    for entry in hit.index.iter() {
        let model = hit.get_model(&entry.0).unwrap();
        entries.insert(entry.0.to_string(), AddedEntry {
            id: entry.0.clone(),
            data: entry.1.borrow().data.clone(),
            parent: entry.1.borrow().get_parent(),
            model: model.get_name().to_string(),
        });
    }
    import_from_patches_and_entries(patch, hit.get_main_object_id(), hit.kernel.clone(), &mut entries)
}

pub fn duplicate_hit(hit: &Hit) -> Result<Hit, HitError> {
    apply_patches(hit, vec![])
}

fn import_patch<'a>(
    patch: Patch,
    entries: &mut LinkedHashMap<String, AddedEntry>,
) -> Result<(), HitError> {
    for deleted in patch.deleted.iter() {
        entries.remove(deleted);
    }
    for added in patch.added.iter() {
        entries.insert(added.id.clone(), added.clone());
    }
    for changed in patch.differences.iter() {
        let entry = entries.get_mut(&changed.id).unwrap();
        entry.data.remove(&changed.property);
        entry
            .data
            .insert(changed.property.clone(), changed.new_value.clone());
    }
    return Ok(());
}
