// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

mod config;
mod fair_pool;
pub mod logging_pool;
mod task_shared;
mod unified_pool;

use datafusion::execution::memory_pool::{
    FairSpillPool, GreedyMemoryPool, MemoryPool, TrackConsumersPool, UnboundedMemoryPool,
};
use fair_pool::CometFairMemoryPool;
use jni::objects::{Global, JObject};
use once_cell::sync::OnceCell;
use std::num::NonZeroUsize;
use std::sync::Arc;
use unified_pool::CometUnifiedMemoryPool;

pub(crate) use config::*;
pub(crate) use task_shared::*;

pub(crate) fn create_memory_pool(
    memory_pool_config: &MemoryPoolConfig,
    comet_task_memory_manager: Arc<Global<JObject<'static>>>,
    task_attempt_id: i64,
) -> Arc<dyn MemoryPool> {
    const NUM_TRACKED_CONSUMERS: usize = 10;
    match memory_pool_config.pool_type {
        MemoryPoolType::GreedyUnified => {
            let mut memory_pool_map = TASK_SHARED_MEMORY_POOLS.lock().unwrap();
            let per_task_memory_pool =
                memory_pool_map.entry(task_attempt_id).or_insert_with(|| {
                    let pool: Arc<dyn MemoryPool> = Arc::new(TrackConsumersPool::new(
                        CometUnifiedMemoryPool::new(
                            Arc::clone(&comet_task_memory_manager),
                            task_attempt_id,
                        ),
                        NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                    ));
                    PerTaskMemoryPool::new(pool)
                });
            per_task_memory_pool.num_plans += 1;
            Arc::clone(&per_task_memory_pool.memory_pool)
        }
        MemoryPoolType::FairUnified => {
            let mut memory_pool_map = TASK_SHARED_MEMORY_POOLS.lock().unwrap();
            let per_task_memory_pool =
                memory_pool_map.entry(task_attempt_id).or_insert_with(|| {
                    let pool: Arc<dyn MemoryPool> = Arc::new(TrackConsumersPool::new(
                        CometFairMemoryPool::new(
                            Arc::clone(&comet_task_memory_manager),
                            memory_pool_config.pool_size,
                        ),
                        NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                    ));
                    PerTaskMemoryPool::new(pool)
                });
            per_task_memory_pool.num_plans += 1;
            Arc::clone(&per_task_memory_pool.memory_pool)
        }
        MemoryPoolType::Greedy => Arc::new(TrackConsumersPool::new(
            GreedyMemoryPool::new(memory_pool_config.pool_size),
            NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
        )),
        MemoryPoolType::FairSpill => Arc::new(TrackConsumersPool::new(
            FairSpillPool::new(memory_pool_config.pool_size),
            NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
        )),
        MemoryPoolType::GreedyGlobal => {
            static GLOBAL_MEMORY_POOL_GREEDY: OnceCell<Arc<dyn MemoryPool>> = OnceCell::new();
            let memory_pool = GLOBAL_MEMORY_POOL_GREEDY.get_or_init(|| {
                Arc::new(TrackConsumersPool::new(
                    GreedyMemoryPool::new(memory_pool_config.pool_size),
                    NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                ))
            });
            Arc::clone(memory_pool)
        }
        MemoryPoolType::FairSpillGlobal => {
            static GLOBAL_MEMORY_POOL_FAIR: OnceCell<Arc<dyn MemoryPool>> = OnceCell::new();
            let memory_pool = GLOBAL_MEMORY_POOL_FAIR.get_or_init(|| {
                Arc::new(TrackConsumersPool::new(
                    FairSpillPool::new(memory_pool_config.pool_size),
                    NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                ))
            });
            Arc::clone(memory_pool)
        }
        MemoryPoolType::GreedyTaskShared | MemoryPoolType::FairSpillTaskShared => {
            let mut memory_pool_map = TASK_SHARED_MEMORY_POOLS.lock().unwrap();
            let per_task_memory_pool =
                memory_pool_map.entry(task_attempt_id).or_insert_with(|| {
                    let pool: Arc<dyn MemoryPool> =
                        if memory_pool_config.pool_type == MemoryPoolType::GreedyTaskShared {
                            Arc::new(TrackConsumersPool::new(
                                GreedyMemoryPool::new(memory_pool_config.pool_size),
                                NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                            ))
                        } else {
                            Arc::new(TrackConsumersPool::new(
                                FairSpillPool::new(memory_pool_config.pool_size),
                                NonZeroUsize::new(NUM_TRACKED_CONSUMERS).unwrap(),
                            ))
                        };
                    PerTaskMemoryPool::new(pool)
                });
            per_task_memory_pool.num_plans += 1;
            Arc::clone(&per_task_memory_pool.memory_pool)
        }
        MemoryPoolType::Unbounded => Arc::new(UnboundedMemoryPool::default()),
    }
}
