//! Rocks DB implementation of the storage layer
use crate::{ReferenceType, RocksDb};
use futures::Stream;
use nostr_rs_storage_base::{
    cursor::{check_future_call, FutureResult, FutureValue},
    Error, EventFilter, Storage,
};
use nostr_rs_types::types::Event;
use rocksdb::{DBIteratorWithThreadMode, DB};
use std::{
    collections::VecDeque,
    pin::Pin,
    task::{Context, Poll},
};

pub struct Cursor<'a> {
    /// Reference to the rocks db database. This is useful to load the event
    /// data, because in the secondary indexes we only store the event ID. It
    /// could be possible to avoid a reference and speed up things even more to
    /// duplicate the event, but that would mean it would use way more disk
    /// space.
    db: &'a RocksDb,
    /// List of filters to apply to the events, before returning. If no filter
    /// is given each events from the secondary index will be returned,
    /// otherwise the events will be filtered by the given filter, and only
    /// those events that comply will be returned
    filter: Option<EventFilter>,
    /// Reference to the namespace to use to query the secondary index. If none
    /// is given the secondary_index_iterator must be constructed outside this
    /// wrapper.
    index: Option<ReferenceType>,
    /// The current secondary index iterator. If none is given the iterator will
    /// try to create one using the namespace property and the first prefix from
    /// prefixes (it will also be copied to current_prefix)
    index_iterator: Option<DBIteratorWithThreadMode<'a, DB>>,
    /// Index key or prefix to iterate over in the index
    index_keys: VecDeque<Vec<u8>>,
    /// The current prefix to load
    current_index_key: Vec<u8>,
    /// Limit of events to return, None means nothing
    limit: Option<usize>,
    /// Amount of events returned
    returned: usize,
    /// Future event to return
    future_event: Option<FutureResult<'a>>,
}

impl<'a> Cursor<'a> {
    pub fn new(
        db: &'a RocksDb,
        index: Option<ReferenceType>,
        prefixes: Vec<Vec<u8>>,
        filter: Option<EventFilter>,
        secondary_index_iterator: Option<DBIteratorWithThreadMode<'a, DB>>,
        limit: Option<usize>,
    ) -> Self {
        Self {
            db,
            index,
            index_iterator: secondary_index_iterator,
            current_index_key: Vec::new(),
            index_keys: prefixes.into(),
            filter,
            limit,
            returned: 0,
            future_event: None,
        }
    }
    /// Selects the next prefix available and starts an iterator using the
    /// secondary index. If no prefix is available from prefixes the functions
    /// return None, signalling upstream the are no more results
    fn select_next_prefix_using_secondary_index(&mut self) -> Option<()> {
        self.index_iterator = None;
        let prefix = self.index_keys.pop_front()?;
        let index = self
            .index
            .map(|index| self.db.reference_to_cf_handle(index).ok())?;

        self.index_iterator = Some(
            self.db
                .db
                .prefix_iterator_cf(index.as_ref()?, prefix.clone()),
        );
        self.current_index_key = prefix;
        Some(())
    }
}

impl<'a> Stream for Cursor<'a> {
    type Item = Result<Event, Error>;

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, None)
    }

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        if Some(self.returned) == self.limit {
            return Poll::Ready(None);
        }

        let this = Pin::into_inner(self);
        let db = this.db;

        match check_future_call(&mut this.future_event, &this.filter, cx) {
            FutureValue::Found(event) => {
                this.returned += 1;
                return Poll::Ready(Some(event));
            }
            FutureValue::Pending => return Poll::Pending,
            _ => {}
        }

        loop {
            let secondary_index = if let Some(iterator) = this.index_iterator.as_mut() {
                iterator
            } else if this.index.is_some() {
                let _ = this.select_next_prefix_using_secondary_index();
                if let Some(iterator) = this.index_iterator.as_mut() {
                    iterator
                } else {
                    return Poll::Ready(None);
                }
            } else {
                // No secondary index is used to query, this means the query is
                // using the ID filter, so it is more efficient to use the
                // primary index to prefetch events that may satisfy the query
                return if let Some(prefix) = this.index_keys.pop_front() {
                    this.future_event = Some(db.get_event(prefix));
                    match check_future_call(&mut this.future_event, &this.filter, cx) {
                        FutureValue::Found(event) => {
                            this.returned += 1;
                            Poll::Ready(Some(event))
                        }
                        FutureValue::Pending => Poll::Pending,
                        _ => continue,
                    }
                } else {
                    Poll::Ready(None)
                };
            };

            return match secondary_index.next() {
                Some(Ok((key, value))) => {
                    if !key.starts_with(&this.current_index_key) {
                        let _ = this.select_next_prefix_using_secondary_index();
                        continue;
                    }

                    this.future_event = Some(db.get_event(value));
                    match check_future_call(&mut this.future_event, &this.filter, cx) {
                        FutureValue::Found(event) => {
                            this.returned += 1;
                            Poll::Ready(Some(event))
                        }
                        FutureValue::Pending => Poll::Pending,
                        _ => continue,
                    }
                }
                Some(Err(err)) => {
                    this.returned += 1;
                    Poll::Ready(Some(Err(Error::Internal(err.to_string()))))
                }
                None => {
                    let _ = this.select_next_prefix_using_secondary_index();
                    continue;
                }
            };
        }
    }
}
