//! Manage the map of slot -> append vec

use {
    crate::accounts_db::{AccountStorageEntry, AppendVecId},
    dashmap::DashMap,
    solana_sdk::clock::Slot,
    std::sync::Arc,
};

pub mod meta;

#[derive(Clone, Debug)]
pub struct AccountStorageReference {
    /// the single storage for a given slot
    pub storage: Arc<AccountStorageEntry>,
    /// id can be read from 'storage', but it is an atomic read.
    /// id will never change while a storage is held, so we store it separately here for faster runtime lookup in 'get_account_storage_entry'
    pub id: AppendVecId,
}

pub type AccountStorageMap = DashMap<Slot, AccountStorageReference>;

#[derive(Default, Debug)]
pub struct AccountStorage {
    /// map from Slot -> the single append vec for the slot
    map: AccountStorageMap,
    /// while shrink is operating on a slot, there can be 2 append vecs active for that slot
    /// Once the index has been updated to only refer to the new append vec, the single entry for the slot in 'map' can be updated.
    /// Entries in 'shrink_in_progress_map' can be found by 'get_account_storage_entry'
    shrink_in_progress_map: DashMap<Slot, Arc<AccountStorageEntry>>,
}

impl AccountStorage {
    /// Return the append vec in 'slot' and with id='store_id'.
    /// can look in 'map' and 'shrink_in_progress_map' to find the specified append vec
    /// when shrinking begins, shrinking_in_progress is called.
    /// This fn looks in 'map' first, then in 'shrink_in_progress_map', then in 'map' again because
    /// 'shrinking_in_progress' first inserts the new append vec into 'shrink_in_progress_map'
    /// Then, when 'shrink_in_progress' is dropped,
    /// the old append vec is replaced in 'map' with the new append vec
    /// then the new append vec is dropped from 'shrink_in_progress_map'.
    /// So, it is possible for a race with this fn and dropping 'shrink_in_progress'.
    /// Callers to this function have 2 choices:
    /// 1. hold the account index read lock for the pubkey so that the account index entry cannot be changed prior to or during this call. (scans do this)
    /// 2. expect to be ready to start over and read the index again if this function returns None
    /// Operations like shrinking or write cache flushing may have updated the index between when the caller read the index and called this function to
    /// load from the append vec specified in the index.
    /// In practice, this fn will return the entry from the map in the very first lookup unless a shrink is in progress.
    /// The third lookup will only be called if a requesting thread exactly interposes itself between the 2 map manipulations in the drop of 'shrink_in_progress'.
    pub(crate) fn get_account_storage_entry(
        &self,
        slot: Slot,
        store_id: AppendVecId,
    ) -> Option<Arc<AccountStorageEntry>> {
        let lookup_in_map = || {
            self.map
                .get(&slot)
                .and_then(|r| (r.id == store_id).then_some(Arc::clone(&r.storage)))
        };

        lookup_in_map()
            .or_else(|| {
                self.shrink_in_progress_map.get(&slot).and_then(|entry| {
                    (entry.value().append_vec_id() == store_id).then(|| Arc::clone(entry.value()))
                })
            })
            .or_else(lookup_in_map)
    }

    /// returns true if shrink in progress is NOT active
    pub(crate) fn no_shrink_in_progress(&self) -> bool {
        self.shrink_in_progress_map.is_empty()
    }

    /// return the append vec for 'slot' if it exists
    /// This is only ever called when shrink is not possibly running and there is a max of 1 append vec per slot.
    pub fn get_slot_storage_entry(&self, slot: Slot) -> Option<Arc<AccountStorageEntry>> {
        assert!(
            self.no_shrink_in_progress(),
            "self.no_shrink_in_progress(): {slot}"
        );
        self.get_slot_storage_entry_shrinking_in_progress_ok(slot)
    }

    /// return the append vec for 'slot' if it exists
    pub(crate) fn get_slot_storage_entry_shrinking_in_progress_ok(
        &self,
        slot: Slot,
    ) -> Option<Arc<AccountStorageEntry>> {
        self.map.get(&slot).map(|entry| Arc::clone(&entry.storage))
    }

    pub(crate) fn all_slots(&self) -> Vec<Slot> {
        assert!(self.no_shrink_in_progress());
        self.map.iter().map(|iter_item| *iter_item.key()).collect()
    }

    /// returns true if there is no entry for 'slot'
    #[cfg(test)]
    pub(crate) fn is_empty_entry(&self, slot: Slot) -> bool {
        assert!(
            self.no_shrink_in_progress(),
            "self.no_shrink_in_progress(): {slot}"
        );
        self.map.get(&slot).is_none()
    }

    /// initialize the storage map to 'all_storages'
    pub fn initialize(&mut self, all_storages: AccountStorageMap) {
        assert!(self.map.is_empty());
        assert!(self.no_shrink_in_progress());
        self.map.extend(all_storages)
    }

    /// remove the append vec at 'slot'
    /// returns the current contents
    pub(crate) fn remove(
        &self,
        slot: &Slot,
        shrink_can_be_active: bool,
    ) -> Option<Arc<AccountStorageEntry>> {
        assert!(shrink_can_be_active || self.shrink_in_progress_map.is_empty());
        self.map.remove(slot).map(|(_, entry)| entry.storage)
    }

    /// iterate through all (slot, append-vec)
    pub(crate) fn iter(&self) -> AccountStorageIter<'_> {
        assert!(self.no_shrink_in_progress());
        AccountStorageIter::new(self)
    }

    pub(crate) fn insert(&self, slot: Slot, store: Arc<AccountStorageEntry>) {
        assert!(
            self.no_shrink_in_progress(),
            "self.no_shrink_in_progress(): {slot}"
        );
        assert!(self
            .map
            .insert(
                slot,
                AccountStorageReference {
                    id: store.append_vec_id(),
                    storage: store,
                }
            )
            .is_none());
    }

    /// called when shrinking begins on a slot and append vec.
    /// When 'ShrinkInProgress' is dropped by caller, the old store will be replaced with 'new_store' in the storage map.
    /// Fails if there are no existing stores at the slot.
    /// 'new_store' will be replacing the current store at 'slot' in 'map'
    /// So, insert 'new_store' into 'shrink_in_progress_map'.
    /// This allows tx processing loads to find the items in 'shrink_in_progress_map' after the index is updated and item is now located in 'new_store'.
    pub(crate) fn shrinking_in_progress(
        &self,
        slot: Slot,
        new_store: Arc<AccountStorageEntry>,
    ) -> ShrinkInProgress<'_> {
        let shrinking_store = Arc::clone(
            &self
                .map
                .get(&slot)
                .expect("no pre-existing storage for shrinking slot")
                .value()
                .storage,
        );

        // insert 'new_store' into 'shrink_in_progress_map'
        assert!(
            self.shrink_in_progress_map
                .insert(slot, Arc::clone(&new_store))
                .is_none(),
            "duplicate call"
        );

        ShrinkInProgress {
            storage: self,
            slot,
            new_store,
            old_store: shrinking_store,
        }
    }

    #[cfg(test)]
    pub(crate) fn len(&self) -> usize {
        self.map.len()
    }
}

/// iterate contents of AccountStorage without exposing internals
pub struct AccountStorageIter<'a> {
    iter: dashmap::iter::Iter<'a, Slot, AccountStorageReference>,
}

impl<'a> AccountStorageIter<'a> {
    pub fn new(storage: &'a AccountStorage) -> Self {
        Self {
            iter: storage.map.iter(),
        }
    }
}

impl<'a> Iterator for AccountStorageIter<'a> {
    type Item = (Slot, Arc<AccountStorageEntry>);

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(entry) = self.iter.next() {
            let slot = entry.key();
            let store = entry.value();
            return Some((*slot, Arc::clone(&store.storage)));
        }
        None
    }
}

/// exists while there is a shrink in progress
/// keeps track of the 'new_store' being created and the 'old_store' being replaced.
#[derive(Debug)]
pub struct ShrinkInProgress<'a> {
    storage: &'a AccountStorage,
    /// old store which will be shrunk and replaced
    old_store: Arc<AccountStorageEntry>,
    /// newly shrunk store with a subset of contents from 'old_store'
    new_store: Arc<AccountStorageEntry>,
    slot: Slot,
}

/// called when the shrink is no longer in progress. This means we can release the old append vec and update the map of slot -> append vec
impl<'a> Drop for ShrinkInProgress<'a> {
    fn drop(&mut self) {
        assert_eq!(
            self.storage
                .map
                .insert(
                    self.slot,
                    AccountStorageReference {
                        storage: Arc::clone(&self.new_store),
                        id: self.new_store.append_vec_id()
                    }
                )
                .map(|store| store.id),
            Some(self.old_store.append_vec_id())
        );

        // The new store can be removed from 'shrink_in_progress_map'
        assert!(self
            .storage
            .shrink_in_progress_map
            .remove(&self.slot)
            .is_some());
    }
}

impl<'a> ShrinkInProgress<'a> {
    pub fn new_storage(&self) -> &Arc<AccountStorageEntry> {
        &self.new_store
    }
    pub(crate) fn old_storage(&self) -> &Arc<AccountStorageEntry> {
        &self.old_store
    }
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, Serialize, AbiExample, AbiEnumVisitor)]
pub enum AccountStorageStatus {
    Available = 0,
    Full = 1,
    Candidate = 2,
}

impl Default for AccountStorageStatus {
    fn default() -> Self {
        Self::Available
    }
}

#[cfg(test)]
pub(crate) mod tests {
    use {super::*, std::path::Path};

    #[test]
    fn test_shrink_in_progress() {
        // test that we check in order map then shrink_in_progress_map
        let storage = AccountStorage::default();
        let slot = 0;
        let id = 0;
        // empty everything
        assert!(storage.get_account_storage_entry(slot, id).is_none());

        // add a map store
        let common_store_path = Path::new("");
        let store_file_size = 4000;
        let store_file_size2 = store_file_size * 2;
        // 2 append vecs with same id, but different sizes
        let entry = Arc::new(AccountStorageEntry::new(
            common_store_path,
            slot,
            id,
            store_file_size,
        ));
        let entry2 = Arc::new(AccountStorageEntry::new(
            common_store_path,
            slot,
            id,
            store_file_size2,
        ));
        storage
            .map
            .insert(slot, AccountStorageReference { id, storage: entry });

        // look in map
        assert_eq!(
            store_file_size,
            storage
                .get_account_storage_entry(slot, id)
                .map(|entry| entry.accounts.capacity())
                .unwrap_or_default()
        );

        // look in shrink_in_progress_map
        storage.shrink_in_progress_map.insert(slot, entry2);

        // look in map
        assert_eq!(
            store_file_size,
            storage
                .get_account_storage_entry(slot, id)
                .map(|entry| entry.accounts.capacity())
                .unwrap_or_default()
        );

        // remove from map
        storage.map.remove(&slot).unwrap();

        // look in shrink_in_progress_map
        assert_eq!(
            store_file_size2,
            storage
                .get_account_storage_entry(slot, id)
                .map(|entry| entry.accounts.capacity())
                .unwrap_or_default()
        );
    }

    impl AccountStorage {
        fn get_test_storage_with_id(&self, id: AppendVecId) -> Arc<AccountStorageEntry> {
            let slot = 0;
            // add a map store
            let common_store_path = Path::new("");
            let store_file_size = 4000;
            Arc::new(AccountStorageEntry::new(
                common_store_path,
                slot,
                id,
                store_file_size,
            ))
        }
        fn get_test_storage(&self) -> Arc<AccountStorageEntry> {
            self.get_test_storage_with_id(0)
        }
    }

    #[test]
    #[should_panic(expected = "self.no_shrink_in_progress()")]
    fn test_get_slot_storage_entry_fail() {
        let storage = AccountStorage::default();
        storage
            .shrink_in_progress_map
            .insert(0, storage.get_test_storage());
        storage.get_slot_storage_entry(0);
    }

    #[test]
    #[should_panic(expected = "self.no_shrink_in_progress()")]
    fn test_all_slots_fail() {
        let storage = AccountStorage::default();
        storage
            .shrink_in_progress_map
            .insert(0, storage.get_test_storage());
        storage.all_slots();
    }

    #[test]
    #[should_panic(expected = "self.no_shrink_in_progress()")]
    fn test_initialize_fail() {
        let mut storage = AccountStorage::default();
        storage
            .shrink_in_progress_map
            .insert(0, storage.get_test_storage());
        storage.initialize(AccountStorageMap::default());
    }

    #[test]
    #[should_panic(expected = "shrink_can_be_active || self.shrink_in_progress_map.is_empty()")]
    fn test_remove_fail() {
        let storage = AccountStorage::default();
        storage
            .shrink_in_progress_map
            .insert(0, storage.get_test_storage());
        storage.remove(&0, false);
    }

    #[test]
    #[should_panic(expected = "self.no_shrink_in_progress()")]
    fn test_iter_fail() {
        let storage = AccountStorage::default();
        storage
            .shrink_in_progress_map
            .insert(0, storage.get_test_storage());
        storage.iter();
    }

    #[test]
    #[should_panic(expected = "self.no_shrink_in_progress()")]
    fn test_insert_fail() {
        let storage = AccountStorage::default();
        let sample = storage.get_test_storage();
        storage.shrink_in_progress_map.insert(0, sample.clone());
        storage.insert(0, sample);
    }

    #[test]
    #[should_panic(expected = "duplicate call")]
    fn test_shrinking_in_progress_fail3() {
        // already entry in shrink_in_progress_map
        let storage = AccountStorage::default();
        let sample = storage.get_test_storage();
        storage.map.insert(
            0,
            AccountStorageReference {
                id: 0,
                storage: sample.clone(),
            },
        );
        storage.shrink_in_progress_map.insert(0, sample.clone());
        storage.shrinking_in_progress(0, sample);
    }

    #[test]
    #[should_panic(expected = "duplicate call")]
    fn test_shrinking_in_progress_fail4() {
        // already called 'shrink_in_progress' on this slot and it is still active
        let storage = AccountStorage::default();
        let sample_to_shrink = storage.get_test_storage();
        let sample = storage.get_test_storage();
        storage.map.insert(
            0,
            AccountStorageReference {
                id: 0,
                storage: sample_to_shrink,
            },
        );
        let _shrinking_in_progress = storage.shrinking_in_progress(0, sample.clone());
        storage.shrinking_in_progress(0, sample);
    }

    #[test]
    fn test_shrinking_in_progress_second_call() {
        // already called 'shrink_in_progress' on this slot, but it finished, so we succeed
        // verify data structures during and after shrink and then with subsequent shrink call
        let storage = AccountStorage::default();
        let slot = 0;
        let id_to_shrink = 1;
        let id_shrunk = 0;
        let sample_to_shrink = storage.get_test_storage_with_id(id_to_shrink);
        let sample = storage.get_test_storage();
        storage.map.insert(
            slot,
            AccountStorageReference {
                id: id_to_shrink,
                storage: sample_to_shrink,
            },
        );
        let shrinking_in_progress = storage.shrinking_in_progress(slot, sample.clone());
        assert!(storage.map.contains_key(&slot));
        assert_eq!(
            id_to_shrink,
            storage.map.get(&slot).unwrap().storage.append_vec_id()
        );
        assert_eq!(
            (slot, id_shrunk),
            storage
                .shrink_in_progress_map
                .iter()
                .next()
                .map(|r| (*r.key(), r.value().append_vec_id()))
                .unwrap()
        );
        drop(shrinking_in_progress);
        assert!(storage.map.contains_key(&slot));
        assert_eq!(
            id_shrunk,
            storage.map.get(&slot).unwrap().storage.append_vec_id()
        );
        assert!(storage.shrink_in_progress_map.is_empty());
        storage.shrinking_in_progress(slot, sample);
    }

    #[test]
    #[should_panic(expected = "no pre-existing storage for shrinking slot")]
    fn test_shrinking_in_progress_fail1() {
        // nothing in slot currently
        let storage = AccountStorage::default();
        let sample = storage.get_test_storage();
        storage.shrinking_in_progress(0, sample);
    }

    #[test]
    #[should_panic(expected = "no pre-existing storage for shrinking slot")]
    fn test_shrinking_in_progress_fail2() {
        // nothing in slot currently, but there is an empty map entry
        let storage = AccountStorage::default();
        let sample = storage.get_test_storage();
        storage.shrinking_in_progress(0, sample);
    }

    #[test]
    fn test_missing() {
        // already called 'shrink_in_progress' on this slot, but it finished, so we succeed
        // verify data structures during and after shrink and then with subsequent shrink call
        let storage = AccountStorage::default();
        let sample = storage.get_test_storage();
        let id = sample.append_vec_id();
        let missing_id = 9999;
        let slot = sample.slot();
        // id is missing since not in maps at all
        assert!(storage.get_account_storage_entry(slot, id).is_none());
        // missing should always be missing
        assert!(storage
            .get_account_storage_entry(slot, missing_id)
            .is_none());
        storage.map.insert(
            slot,
            AccountStorageReference {
                id,
                storage: sample.clone(),
            },
        );
        // id is found in map
        assert!(storage.get_account_storage_entry(slot, id).is_some());
        assert!(storage
            .get_account_storage_entry(slot, missing_id)
            .is_none());
        storage
            .shrink_in_progress_map
            .insert(slot, Arc::clone(&sample));
        // id is found in map
        assert!(storage
            .get_account_storage_entry(slot, missing_id)
            .is_none());
        assert!(storage.get_account_storage_entry(slot, id).is_some());
        storage.map.remove(&slot);
        // id is found in shrink_in_progress_map
        assert!(storage
            .get_account_storage_entry(slot, missing_id)
            .is_none());
        assert!(storage.get_account_storage_entry(slot, id).is_some());
    }
}
