use crate::data::{
    DataSourceDescription, DataSourceInfo, DataSourceMut, EntryID, SlotMetaTile, SlotTile,
    SummaryTile, TileID,
};

pub trait DeferredDataSource {
    fn fetch_description(&self) -> DataSourceDescription;
    fn fetch_info(&mut self);
    fn get_infos(&mut self) -> Vec<DataSourceInfo>;
    fn fetch_summary_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool);
    fn get_summary_tiles(&mut self) -> Vec<SummaryTile>;
    fn fetch_slot_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool);
    fn get_slot_tiles(&mut self) -> Vec<SlotTile>;
    fn fetch_slot_meta_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool);
    fn get_slot_meta_tiles(&mut self) -> Vec<SlotMetaTile>;
}

pub struct DeferredDataSourceWrapper<T: DataSourceMut> {
    data_source: T,
    infos: Vec<DataSourceInfo>,
    summary_tiles: Vec<SummaryTile>,
    slot_tiles: Vec<SlotTile>,
    slot_meta_tiles: Vec<SlotMetaTile>,
}

impl<T: DataSourceMut> DeferredDataSourceWrapper<T> {
    pub fn new(data_source: T) -> Self {
        Self {
            data_source,
            infos: Vec::new(),
            summary_tiles: Vec::new(),
            slot_tiles: Vec::new(),
            slot_meta_tiles: Vec::new(),
        }
    }
}

impl<T: DataSourceMut> DeferredDataSource for DeferredDataSourceWrapper<T> {
    fn fetch_description(&self) -> DataSourceDescription {
        self.data_source.fetch_description()
    }

    fn fetch_info(&mut self) {
        self.infos.push(self.data_source.fetch_info());
    }

    fn get_infos(&mut self) -> Vec<DataSourceInfo> {
        std::mem::take(&mut self.infos)
    }

    fn fetch_summary_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.summary_tiles
            .push(self.data_source.fetch_summary_tile(entry_id, tile_id, full));
    }

    fn get_summary_tiles(&mut self) -> Vec<SummaryTile> {
        std::mem::take(&mut self.summary_tiles)
    }

    fn fetch_slot_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.slot_tiles
            .push(self.data_source.fetch_slot_tile(entry_id, tile_id, full));
    }

    fn get_slot_tiles(&mut self) -> Vec<SlotTile> {
        std::mem::take(&mut self.slot_tiles)
    }

    fn fetch_slot_meta_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.slot_meta_tiles.push(
            self.data_source
                .fetch_slot_meta_tile(entry_id, tile_id, full),
        );
    }

    fn get_slot_meta_tiles(&mut self) -> Vec<SlotMetaTile> {
        std::mem::take(&mut self.slot_meta_tiles)
    }
}

pub struct CountingDeferredDataSource<T: DeferredDataSource> {
    data_source: T,
    outstanding_requests: u64,
}

impl<T: DeferredDataSource> CountingDeferredDataSource<T> {
    pub fn new(data_source: T) -> Self {
        Self {
            data_source,
            outstanding_requests: 0,
        }
    }

    pub fn outstanding_requests(&self) -> u64 {
        self.outstanding_requests
    }

    fn start_request(&mut self) {
        self.outstanding_requests += 1;
    }

    fn finish_request<E>(&mut self, result: Vec<E>) -> Vec<E> {
        let count = result.len() as u64;
        assert!(self.outstanding_requests >= count);
        self.outstanding_requests -= count;
        result
    }
}

impl<T: DeferredDataSource> DeferredDataSource for CountingDeferredDataSource<T> {
    fn fetch_description(&self) -> DataSourceDescription {
        self.data_source.fetch_description()
    }

    fn fetch_info(&mut self) {
        self.start_request();
        self.data_source.fetch_info()
    }

    fn get_infos(&mut self) -> Vec<DataSourceInfo> {
        let result = self.data_source.get_infos();
        self.finish_request(result)
    }

    fn fetch_summary_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.start_request();
        self.data_source.fetch_summary_tile(entry_id, tile_id, full)
    }

    fn get_summary_tiles(&mut self) -> Vec<SummaryTile> {
        let result = self.data_source.get_summary_tiles();
        self.finish_request(result)
    }

    fn fetch_slot_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.start_request();
        self.data_source.fetch_slot_tile(entry_id, tile_id, full)
    }

    fn get_slot_tiles(&mut self) -> Vec<SlotTile> {
        let result = self.data_source.get_slot_tiles();
        self.finish_request(result)
    }

    fn fetch_slot_meta_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.start_request();
        self.data_source
            .fetch_slot_meta_tile(entry_id, tile_id, full)
    }

    fn get_slot_meta_tiles(&mut self) -> Vec<SlotMetaTile> {
        let result = self.data_source.get_slot_meta_tiles();
        self.finish_request(result)
    }
}

impl DeferredDataSource for Box<dyn DeferredDataSource> {
    fn fetch_description(&self) -> DataSourceDescription {
        self.as_ref().fetch_description()
    }

    fn fetch_info(&mut self) {
        self.as_mut().fetch_info()
    }

    fn get_infos(&mut self) -> Vec<DataSourceInfo> {
        self.as_mut().get_infos()
    }

    fn fetch_summary_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.as_mut().fetch_summary_tile(entry_id, tile_id, full)
    }

    fn get_summary_tiles(&mut self) -> Vec<SummaryTile> {
        self.as_mut().get_summary_tiles()
    }

    fn fetch_slot_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.as_mut().fetch_slot_tile(entry_id, tile_id, full)
    }

    fn get_slot_tiles(&mut self) -> Vec<SlotTile> {
        self.as_mut().get_slot_tiles()
    }

    fn fetch_slot_meta_tile(&mut self, entry_id: &EntryID, tile_id: TileID, full: bool) {
        self.as_mut().fetch_slot_meta_tile(entry_id, tile_id, full)
    }

    fn get_slot_meta_tiles(&mut self) -> Vec<SlotMetaTile> {
        self.as_mut().get_slot_meta_tiles()
    }
}
