use crate::{
    error::PIRes,
    id::IndexId,
    index::{
        config::{IndexOrd, IndexTypeInternal, Indexes},
        keeper_tx::ExternalRefs,
        tree::{
            IndexApply,
            key_changes::{KeyChanges, ValueChange},
            nodes::compare,
        },
    },
};
use std::{
    any::Any,
    ops::{Bound, RangeBounds},
    vec::IntoIter,
};

pub struct TxIndexChangesDynamic {
    value: Box<dyn Any>,
    apply: Box<dyn Applier>,
}
impl TxIndexChangesDynamic {
    pub fn new<K: IndexTypeInternal + 'static, V: IndexTypeInternal + 'static>() -> Self {
        Self {
            value: Box::new(TypedTxIndexChanges::<K, V>::new()),
            apply: Box::new(ApplierImpl::<K, V>::new()),
        }
    }

    pub fn put<K: IndexOrd + Clone + 'static, V: IndexOrd + Clone + 'static>(&mut self, k: K, v: V) {
        if let Some(values) = self.value.downcast_mut::<TypedTxIndexChanges<K, V>>() {
            values.put(k, v);
        }
    }

    pub fn remove<K: IndexOrd + Clone + 'static, V: IndexOrd + Clone + 'static>(&mut self, k: K, v: Option<V>) {
        if let Some(values) = self.value.downcast_mut::<TypedTxIndexChanges<K, V>>() {
            values.remove(k, v);
        }
    }

    pub fn get<K: IndexOrd + Clone + 'static, V: IndexOrd + Clone + 'static>(
        &self,
        k: &K,
    ) -> Option<Vec<ValueChange<V>>> {
        if let Some(values) = self.value.downcast_ref::<TypedTxIndexChanges<K, V>>() {
            values.get(k)
        } else {
            None
        }
    }

    pub fn range<K, V, R>(&self, range: R) -> IntoIter<K>
    where
        K: IndexOrd + Clone + 'static,
        V: IndexOrd + Clone + 'static,
        R: RangeBounds<K>,
    {
        if let Some(values) = self.value.downcast_ref::<TypedTxIndexChanges<K, V>>() {
            values.range(range)
        } else {
            Vec::new().into_iter()
        }
    }
    pub fn apply(&self, store: ExternalRefs<'_>, index_id: &IndexId) -> PIRes<()> {
        self.apply.apply(store, index_id, &self.value)
    }
}

struct TypedTxIndexChanges<K, V> {
    values: Vec<KeyChanges<K, V>>,
}

impl<K, V> TypedTxIndexChanges<K, V> {
    fn new() -> Self {
        Self {
            values: Default::default(),
        }
    }
}
impl<K: IndexOrd + Clone, V: IndexOrd + Clone> TypedTxIndexChanges<K, V> {
    fn put(&mut self, k: K, v: V) {
        match self.values.binary_search_by(|n| compare(&n.k, &k)) {
            Ok(index) => {
                self.values[index].put(v);
            }
            Err(index) => {
                self.values.insert(index, KeyChanges::new(k, vec![ValueChange::Add(v)]));
            }
        }
    }

    fn remove(&mut self, k: K, v: Option<V>) {
        match self.values.binary_search_by(|n| compare(&n.k, &k)) {
            Ok(index) => {
                self.values[index].remove(v);
            }
            Err(index) => {
                self.values
                    .insert(index, KeyChanges::new(k, vec![ValueChange::Remove(v)]));
            }
        }
    }

    fn get(&self, k: &K) -> Option<Vec<ValueChange<V>>> {
        match self.values.binary_search_by(|n| compare(&n.k, k)) {
            Ok(index) => Some(self.values[index].changes()),
            Err(_) => None,
        }
    }

    fn get_all(&self) -> Vec<KeyChanges<K, V>> {
        self.values.clone()
    }
    pub fn range<R>(&self, range: R) -> IntoIter<K>
    where
        R: RangeBounds<K>,
    {
        let v = &self.values;
        let index = match range.start_bound() {
            Bound::Included(k) => match v.binary_search_by(|n| compare(&n.k, k)) {
                Ok(index) => Bound::Included(index),
                Err(index) => Bound::Included(index),
            },
            Bound::Excluded(k) => match v.binary_search_by(|n| compare(&n.k, k)) {
                Ok(index) => Bound::Excluded(index),
                Err(index) => Bound::Excluded(index),
            },
            Bound::Unbounded => Bound::Unbounded,
        };
        let end_index = match range.end_bound() {
            Bound::Included(k) => match v.binary_search_by(|n| compare(&n.k, k)) {
                Ok(index) => Bound::Included(index),
                Err(index) => Bound::Excluded(index),
            },
            Bound::Excluded(k) => match v.binary_search_by(|n| compare(&n.k, k)) {
                Ok(index) => Bound::Excluded(index),
                Err(index) => Bound::Excluded(index),
            },
            Bound::Unbounded => Bound::Unbounded,
        };
        let start = match index {
            Bound::Included(start) => start,
            Bound::Excluded(start) => start + 1,
            Bound::Unbounded => 0,
        };

        let end = match end_index {
            Bound::Included(end) => end + 1,
            Bound::Excluded(end) => end,
            Bound::Unbounded => v.len(),
        };

        v.get(start..end)
            .expect("the range is every time valid")
            .iter()
            .map(|val| val.k.clone())
            .collect::<Vec<_>>()
            .into_iter()
    }
}

trait Applier {
    fn apply(&self, persy: ExternalRefs<'_>, index: &IndexId, values: &Box<dyn Any>) -> PIRes<()>;
}

struct ApplierImpl<K, V> {
    ph: std::marker::PhantomData<(K, V)>,
}
impl<K, V> ApplierImpl<K, V> {
    fn new() -> Self {
        Self { ph: Default::default() }
    }
}
impl<K: IndexTypeInternal + 'static, V: IndexTypeInternal + 'static> Applier for ApplierImpl<K, V> {
    fn apply(&self, store: ExternalRefs<'_>, index_id: &IndexId, values: &Box<dyn Any>) -> PIRes<()> {
        if let Some(val) = values.downcast_ref::<TypedTxIndexChanges<K, V>>() {
            let changes = val.get_all();
            let mut index = Indexes::get_index_keeper_tx::<K, V>(store, index_id)?;
            index.apply(&changes)?;
            index.update_changed()?;
        }
        Ok(())
    }
}
