using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdvChkSys.Interfaces;
using AdvChkSys.Manager;
using AdvChkSys.Loading;

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides extension methods for threading operations on chunks.
    /// </summary>
    public static class ChunkThreadingExtensions
    {
        /// <summary>
        /// Processes all loaded chunks in parallel.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="action">The action to perform on each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static Task ProcessAllChunksParallelAsync<T>(
            this ChunkManager2D<T> manager,
            Action<Chunk.Chunk2D<T>> action,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();
            return ChunkTaskScheduler.RunBatchParallelAsync(
                chunks.Select(c => new Action(() => action(c))).ToArray(),
                maxDegreeOfParallelism,
                cancellationToken);
        }

        /// <summary>
        /// Processes all loaded chunks in parallel.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="action">The action to perform on each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static Task ProcessAllChunksParallelAsync<T>(
            this ChunkManager3D<T> manager,
            Action<Chunk.Chunk3D<T>> action,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();
            return ChunkTaskScheduler.RunBatchParallelAsync(
                chunks.Select(c => new Action(() => action(c))).ToArray(),
                maxDegreeOfParallelism,
                cancellationToken);
        }

        /// <summary>
        /// Processes chunks in a region in parallel.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="action">The action to perform on each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksInRegionParallelAsync<T>(
            this ChunkManager2D<T> manager,
            int minX, int minY, int maxX, int maxY,
            Action<Chunk.Chunk2D<T>> action,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = new List<Chunk.Chunk2D<T>>();

            // Collect chunks in the region
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    var chunk = manager.GetChunk(x, y);
                    if (chunk != null)
                    {
                        chunks.Add(chunk);
                    }
                }
            }

            // Process in parallel
            await ChunkTaskScheduler.RunBatchParallelAsync(
                chunks.Select(c => new Action(() => action(c))).ToArray(),
                maxDegreeOfParallelism,
                cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Processes chunks in a region in parallel.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="minZ">Minimum Z coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="maxZ">Maximum Z coordinate</param>
        /// <param name="action">The action to perform on each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksInRegionParallelAsync<T>(
            this ChunkManager3D<T> manager,
            int minX, int minY, int minZ, int maxX, int maxY, int maxZ,
            Action<Chunk.Chunk3D<T>> action,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = new List<Chunk.Chunk3D<T>>();

            // Collect chunks in the region
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        var chunk = manager.GetChunk(x, y, z);
                        if (chunk != null)
                        {
                            chunks.Add(chunk);
                        }
                    }
                }
            }

            // Process in parallel
            await ChunkTaskScheduler.RunBatchParallelAsync(
                chunks.Select(c => new Action(() => action(c))).ToArray(),
                maxDegreeOfParallelism,
                cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Loads chunks in a region asynchronously with a specified priority.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="width">Chunk width</param>
        /// <param name="height">Chunk height</param>
        /// <param name="priority">Loading priority</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task LoadChunksInRegionAsync<T>(
            this ChunkManager2D<T> manager,
            int minX, int minY, int maxX, int maxY,
            int width, int height,
            ChunkLoadingPriority.Priority priority = ChunkLoadingPriority.Priority.Normal,
            CancellationToken cancellationToken = default)
        {
            // Create a loading priority system if not already integrated
            var loadingPriority = new ChunkLoadingPriority();

            // Enqueue all chunks in the region
            var tasks = new List<Task>();
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    if (cancellationToken.IsCancellationRequested)
                        break;

                    // Skip if already loaded
                    if (manager.IsChunkLoaded(x, y))
                        continue;

                    // Enqueue the request
                    var request = loadingPriority.EnqueueRequest(x, y, width, height, priority);

                    // Create a task that completes when the chunk is loaded
                    var tcs = new TaskCompletionSource<bool>();

                    void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
                    {
                        if (completedRequest.RequestId == request.RequestId)
                        {
                            loadingPriority.RequestCompleted -= Handler;
                            tcs.TrySetResult(true);
                        }
                    }

                    loadingPriority.RequestCompleted += Handler;

                    // Add timeout to prevent indefinite waiting
                    _ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
                    {
                        loadingPriority.RequestCompleted -= Handler;
                        if (!t.IsCanceled)
                            tcs.TrySetResult(false);
                    }, cancellationToken);

                    tasks.Add(tcs.Task);
                }
            }

            // Wait for all chunks to be loaded
            await Task.WhenAll(tasks).ConfigureAwait(false);

            // Clean up
            await loadingPriority.ShutdownAsync().ConfigureAwait(false);
        }

        /// <summary>
        /// Loads chunks in a region asynchronously with a specified priority.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="minZ">Minimum Z coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="maxZ">Maximum Z coordinate</param>
        /// <param name="width">Chunk width</param>
        /// <param name="height">Chunk height</param>
        /// <param name="depth">Chunk depth</param>
        /// <param name="priority">Loading priority</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task LoadChunksInRegionAsync<T>(
            this ChunkManager3D<T> manager,
            int minX, int minY, int minZ, int maxX, int maxY, int maxZ,
            int width, int height, int depth,
            ChunkLoadingPriority.Priority priority = ChunkLoadingPriority.Priority.Normal,
            CancellationToken cancellationToken = default)
        {
            // Create a loading priority system if not already integrated
            var loadingPriority = new ChunkLoadingPriority();

            // Enqueue all chunks in the region
            var tasks = new List<Task>();
            for (int x = minX; x <= maxX; x++)
            {
                for (int y = minY; y <= maxY; y++)
                {
                    for (int z = minZ; z <= maxZ; z++)
                    {
                        if (cancellationToken.IsCancellationRequested)
                            break;

                        // Skip if already loaded
                        if (manager.IsChunkLoaded(x, y, z))
                            continue;

                        // Enqueue the request
                        var request = loadingPriority.EnqueueRequest(x, y, z, width, height, depth, priority);

                        // Create a task that completes when the chunk is loaded
                        var tcs = new TaskCompletionSource<bool>();

                        void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
                        {
                            if (completedRequest.RequestId == request.RequestId)
                            {
                                loadingPriority.RequestCompleted -= Handler;
                                tcs.TrySetResult(true);
                            }
                        }

                        loadingPriority.RequestCompleted += Handler;

                        // Add timeout to prevent indefinite waiting
                        _ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
                        {
                            loadingPriority.RequestCompleted -= Handler;
                            if (!t.IsCanceled)
                                tcs.TrySetResult(false);
                        }, cancellationToken);

                        tasks.Add(tcs.Task);
                    }
                }
            }

            // Wait for all chunks to be loaded
            await Task.WhenAll(tasks).ConfigureAwait(false);

            // Clean up
            await loadingPriority.ShutdownAsync().ConfigureAwait(false);
        }

        /// <summary>
        /// Unloads chunks outside a specified region asynchronously.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate to keep</param>
        /// <param name="minY">Minimum Y coordinate to keep</param>
        /// <param name="maxX">Maximum X coordinate to keep</param>
        /// <param name="maxY">Maximum Y coordinate to keep</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task UnloadChunksOutsideRegionAsync<T>(
            this ChunkManager2D<T> manager,
            int minX, int minY, int maxX, int maxY,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            // Get all loaded chunks
            var chunks = manager.GetAllChunks().ToArray();

            // Filter chunks outside the region
            var chunksToUnload = chunks.Where(c =>
                c.X < minX || c.X > maxX || c.Y < minY || c.Y > maxY).ToArray();

            // Unload in parallel
            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
                CancellationToken = cancellationToken
            };

            await Task.Run(() =>
            {
                Parallel.ForEach(chunksToUnload, options, chunk =>
                {
                    manager.UnloadChunk(chunk.X, chunk.Y);
                });
            }, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Unloads chunks outside a specified region asynchronously.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="minX">Minimum X coordinate to keep</param>
        /// <param name="minY">Minimum Y coordinate to keep</param>
        /// <param name="minZ">Minimum Z coordinate to keep</param>
        /// <param name="maxX">Maximum X coordinate to keep</param>
        /// <param name="maxY">Maximum Y coordinate to keep</param>
        /// <param name="maxZ">Maximum Z coordinate to keep</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task UnloadChunksOutsideRegionAsync<T>(
            this ChunkManager3D<T> manager,
            int minX, int minY, int minZ, int maxX, int maxY, int maxZ,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            // Get all loaded chunks
            var chunks = manager.GetAllChunks().ToArray();

            // Filter chunks outside the region
            var chunksToUnload = chunks.Where(c =>
                c.X < minX || c.X > maxX ||
                c.Y < minY || c.Y > maxY ||
                c.Z < minZ || c.Z > maxZ).ToArray();

            // Unload in parallel
            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
                CancellationToken = cancellationToken
            };

            await Task.Run(() =>
            {
                Parallel.ForEach(chunksToUnload, options, chunk =>
                {
                    manager.UnloadChunk(chunk.X, chunk.Y, chunk.Z);
                });
            }, cancellationToken).ConfigureAwait(false);
        }
    }
}