use crate::{
    dispatch::Dispatcher, world::ResourceId, Accessor, AccessorCow, DynamicSystemData, RunningTime,
    System, SystemData, World,
};
#[derive(Debug)]
pub struct BatchAccessor {
    reads: Vec<ResourceId>,
    writes: Vec<ResourceId>,
}
impl BatchAccessor {
    
    pub fn new(reads: Vec<ResourceId>, writes: Vec<ResourceId>) -> Self {
        BatchAccessor { reads, writes }
    }
}
impl Accessor for BatchAccessor {
    fn try_new() -> Option<Self> {
        None
    }
    fn reads(&self) -> Vec<ResourceId> {
        self.reads.clone()
    }
    fn writes(&self) -> Vec<ResourceId> {
        self.writes.clone()
    }
}
pub struct BatchUncheckedWorld<'a>(pub &'a World);
impl<'a> DynamicSystemData<'a> for BatchUncheckedWorld<'a> {
    type Accessor = BatchAccessor;
    fn setup(_accessor: &Self::Accessor, _world: &mut World) {}
    fn fetch(_access: &Self::Accessor, world: &'a World) -> Self {
        BatchUncheckedWorld(world)
    }
}
pub trait BatchController<'a, 'b> {
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    type BatchSystemData: SystemData<'a>;
    
    
    
    
    
    
    
    
    
    
    
    
    unsafe fn create(accessor: BatchAccessor, dispatcher: Dispatcher<'a, 'b>) -> Self;
}
pub struct DefaultBatchControllerSystem<'a, 'b> {
    accessor: BatchAccessor,
    dispatcher: Dispatcher<'a, 'b>,
}
impl<'a, 'b> BatchController<'a, 'b> for DefaultBatchControllerSystem<'a, 'b> {
    type BatchSystemData = ();
    unsafe fn create(accessor: BatchAccessor, dispatcher: Dispatcher<'a, 'b>) -> Self {
        DefaultBatchControllerSystem {
            accessor,
            dispatcher,
        }
    }
}
impl<'a> System<'a> for DefaultBatchControllerSystem<'_, '_> {
    type SystemData = BatchUncheckedWorld<'a>;
    fn run(&mut self, data: Self::SystemData) {
        self.dispatcher.dispatch(data.0);
    }
    fn running_time(&self) -> RunningTime {
        RunningTime::VeryLong
    }
    fn accessor<'c>(&'c self) -> AccessorCow<'a, 'c, Self> {
        AccessorCow::Ref(&self.accessor)
    }
    fn setup(&mut self, world: &mut World) {
        self.dispatcher.setup(world);
    }
}
unsafe impl<'a, 'b> Send for DefaultBatchControllerSystem<'a, 'b> {}
#[cfg(test)]
mod tests {
    use crate::{
        AccessorCow, BatchAccessor, BatchController, BatchUncheckedWorld, Dispatcher,
        DispatcherBuilder, RunningTime, System, World, Write,
    };
    
    
    #[test]
    fn test_setup() {
        let mut dispatcher = DispatcherBuilder::new()
            .with_batch::<CustomBatchControllerSystem>(
                DispatcherBuilder::new()
                    .with(BuyTomatoSystem, "buy_tomato_system", &[])
                    .with(BuyPotatoSystem, "buy_potato_system", &[]),
                "BatchSystemTest",
                &[],
            )
            .build();
        let mut world = World::empty();
        dispatcher.setup(&mut world);
        let potato_store = world.fetch::<PotatoStore>();
        let tomato_store = world.fetch::<TomatoStore>();
        assert!(!potato_store.is_store_open);
        assert!(!tomato_store.is_store_open);
        assert_eq!(potato_store.potato_count, 50);
        assert_eq!(tomato_store.tomato_count, 50);
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    #[test]
    fn test_parallel_batch_execution() {
        let mut dispatcher = DispatcherBuilder::new()
            .with(OpenStoresSystem, "open_stores_system", &[])
            .with_batch::<CustomBatchControllerSystem>(
                DispatcherBuilder::new()
                    .with(BuyTomatoSystem, "buy_tomato_system", &[])
                    .with(BuyPotatoSystem, "buy_potato_system", &[]),
                "BatchSystemTest",
                &[],
            )
            .with(CloseStoresSystem, "close_stores_system", &[])
            .build();
        let mut world = World::empty();
        dispatcher.setup(&mut world);
        {
            
            let potato_store = world.fetch::<PotatoStore>();
            let tomato_store = world.fetch::<TomatoStore>();
            assert!(!potato_store.is_store_open);
            assert!(!tomato_store.is_store_open);
            assert_eq!(potato_store.potato_count, 50);
            assert_eq!(tomato_store.tomato_count, 50);
        }
        
        for _i in 0..10 {
            dispatcher.dispatch(&world);
        }
        {
            
            
            let potato_store = world.fetch::<PotatoStore>();
            let tomato_store = world.fetch::<TomatoStore>();
            assert!(!potato_store.is_store_open);
            assert!(!tomato_store.is_store_open);
            assert_eq!(potato_store.potato_count, 50 - (1 * 3 * 10));
            assert_eq!(tomato_store.tomato_count, 50 - (1 * 3 * 10));
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    #[test]
    fn test_sequence_batch_execution() {
        let mut dispatcher = DispatcherBuilder::new()
            .with(OpenStoresSystem, "open_stores_system", &[])
            .with_batch::<CustomBatchControllerSystem>(
                DispatcherBuilder::new()
                    .with(BuyTomatoWalletSystem, "buy_tomato_system", &[])
                    .with(BuyPotatoWalletSystem, "buy_potato_system", &[]),
                "BatchSystemTest",
                &[],
            )
            .with(CloseStoresSystem, "close_stores_system", &[])
            .build();
        let mut world = World::empty();
        dispatcher.setup(&mut world);
        {
            
            let potato_store = world.fetch::<PotatoStore>();
            let tomato_store = world.fetch::<TomatoStore>();
            let customer_wallet = world.fetch::<CustomerWallet>();
            assert!(!potato_store.is_store_open);
            assert!(!tomato_store.is_store_open);
            assert_eq!(potato_store.potato_count, 50);
            assert_eq!(tomato_store.tomato_count, 50);
            assert_eq!(customer_wallet.cents_count, 2000);
        }
        
        for _i in 0..10 {
            dispatcher.dispatch(&world);
        }
        {
            
            
            let potato_store = world.fetch::<PotatoStore>();
            let tomato_store = world.fetch::<TomatoStore>();
            let customer_wallet = world.fetch::<CustomerWallet>();
            assert!(!potato_store.is_store_open);
            assert!(!tomato_store.is_store_open);
            assert_eq!(potato_store.potato_count, 50 - (1 * 3 * 10));
            assert_eq!(tomato_store.tomato_count, 50 - (1 * 3 * 10));
            assert_eq!(customer_wallet.cents_count, 2000 - ((50 + 150) * 3 * 10));
        }
    }
    
    #[derive(Debug, Clone, Copy)]
    pub struct PotatoStore {
        pub is_store_open: bool,
        pub potato_count: i32,
    }
    impl Default for PotatoStore {
        fn default() -> Self {
            PotatoStore {
                is_store_open: false,
                potato_count: 50,
            }
        }
    }
    #[derive(Debug, Clone, Copy)]
    pub struct TomatoStore {
        pub is_store_open: bool,
        pub tomato_count: i32,
    }
    impl Default for TomatoStore {
        fn default() -> Self {
            TomatoStore {
                is_store_open: false,
                tomato_count: 50,
            }
        }
    }
    #[derive(Debug, Clone, Copy)]
    pub struct CustomerWallet {
        pub cents_count: i32,
    }
    impl Default for CustomerWallet {
        fn default() -> Self {
            CustomerWallet { cents_count: 2000 }
        }
    }
    
    pub struct OpenStoresSystem;
    impl<'a> System<'a> for OpenStoresSystem {
        type SystemData = (Write<'a, PotatoStore>, Write<'a, TomatoStore>);
        fn run(&mut self, mut data: Self::SystemData) {
            data.0.is_store_open = true;
            data.1.is_store_open = true;
        }
    }
    pub struct CloseStoresSystem;
    impl<'a> System<'a> for CloseStoresSystem {
        type SystemData = (Write<'a, PotatoStore>, Write<'a, TomatoStore>);
        fn run(&mut self, mut data: Self::SystemData) {
            data.0.is_store_open = false;
            data.1.is_store_open = false;
        }
    }
    
    pub struct BuyPotatoSystem;
    impl<'a> System<'a> for BuyPotatoSystem {
        type SystemData = (Write<'a, PotatoStore>);
        fn run(&mut self, mut potato_store: Self::SystemData) {
            assert!(potato_store.is_store_open);
            potato_store.potato_count -= 1;
        }
    }
    pub struct BuyTomatoSystem;
    impl<'a> System<'a> for BuyTomatoSystem {
        type SystemData = (Write<'a, TomatoStore>);
        fn run(&mut self, mut tomato_store: Self::SystemData) {
            assert!(tomato_store.is_store_open);
            tomato_store.tomato_count -= 1;
        }
    }
    
    pub struct BuyPotatoWalletSystem;
    impl<'a> System<'a> for BuyPotatoWalletSystem {
        type SystemData = (Write<'a, PotatoStore>, Write<'a, CustomerWallet>);
        fn run(&mut self, (mut potato_store, mut customer_wallet): Self::SystemData) {
            assert!(potato_store.is_store_open);
            potato_store.potato_count -= 1;
            customer_wallet.cents_count -= 50;
        }
    }
    pub struct BuyTomatoWalletSystem;
    impl<'a> System<'a> for BuyTomatoWalletSystem {
        type SystemData = (Write<'a, TomatoStore>, Write<'a, CustomerWallet>);
        fn run(&mut self, (mut tomato_store, mut customer_wallet): Self::SystemData) {
            assert!(tomato_store.is_store_open);
            tomato_store.tomato_count -= 1;
            customer_wallet.cents_count -= 150;
        }
    }
    
    pub struct CustomBatchControllerSystem<'a, 'b> {
        accessor: BatchAccessor,
        dispatcher: Dispatcher<'a, 'b>,
    }
    impl<'a, 'b> BatchController<'a, 'b> for CustomBatchControllerSystem<'a, 'b> {
        type BatchSystemData = ();
        unsafe fn create(accessor: BatchAccessor, dispatcher: Dispatcher<'a, 'b>) -> Self {
            CustomBatchControllerSystem {
                accessor,
                dispatcher,
            }
        }
    }
    impl<'a> System<'a> for CustomBatchControllerSystem<'_, '_> {
        type SystemData = BatchUncheckedWorld<'a>;
        fn run(&mut self, data: Self::SystemData) {
            for _i in 0..3 {
                self.dispatcher.dispatch(data.0);
            }
        }
        fn running_time(&self) -> RunningTime {
            RunningTime::VeryLong
        }
        fn accessor<'c>(&'c self) -> AccessorCow<'a, 'c, Self> {
            AccessorCow::Ref(&self.accessor)
        }
        fn setup(&mut self, world: &mut World) {
            self.dispatcher.setup(world);
        }
    }
    unsafe impl<'a, 'b> Send for CustomBatchControllerSystem<'a, 'b> {}
}