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

namespace AdvChkSys.Threading
{
    /// <summary>
    /// Provides additional extension methods for threading operations on chunks.
    /// </summary>
    public static class ChunkThreadingExtensions2
    {
        /// <summary>
        /// Processes chunks in a spiral pattern from the center outward.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="centerX">Center X coordinate</param>
        /// <param name="centerY">Center Y coordinate</param>
        /// <param name="radius">Radius in chunks</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksSpiralAsync<T>(
            this ChunkManager2D<T> manager,
            int centerX, int centerY, int radius,
            Func<Chunk2D<T>, Task> processor,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            // Generate spiral coordinates
            var coordinates = GenerateSpiralCoordinates(centerX, centerY, radius).ToArray();

            // Process in batches to maintain the spiral ordering while still using parallelism
            int batchSize = Math.Max(1, (int)Math.Sqrt(coordinates.Length));
            int batchCount = (coordinates.Length + batchSize - 1) / batchSize;

            // Use the maxDegreeOfParallelism parameter to limit concurrent tasks
            int parallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism;
            var semaphore = new SemaphoreSlim(parallelism, parallelism);

            for (int i = 0; i < batchCount; i++)
            {
                if (cancellationToken.IsCancellationRequested)
                    break;

                var batchCoords = coordinates
                    .Skip(i * batchSize)
                    .Take(batchSize)
                    .ToArray();

                var tasks = new List<Task>();
                foreach (var (x, y) in batchCoords)
                {
                    var chunk = manager.GetChunk(x, y);
                    if (chunk != null)
                    {
                        // Wait for a slot in the semaphore before starting a new task
                        await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

                        // Create a task that releases the semaphore when done
                        var task = Task.Run(async () =>
                        {
                            try
                            {
                                await processor(chunk).ConfigureAwait(false);
                            }
                            finally
                            {
                                semaphore.Release();
                            }
                        }, cancellationToken);

                        tasks.Add(task);
                    }
                }

                if (tasks.Count > 0)
                {
                    await Task.WhenAll(tasks).ConfigureAwait(false);
                }
            }

            // Clean up the semaphore
            semaphore.Dispose();
        }

        /// <summary>
        /// Generates coordinates in a spiral pattern from the center outward.
        /// </summary>
        private static IEnumerable<(int X, int Y)> GenerateSpiralCoordinates(int centerX, int centerY, int radius)
        {
            // Start with the center
            yield return (centerX, centerY);

            // Spiral outward
            for (int layer = 1; layer <= radius; layer++)
            {
                // Top edge (moving right)
                for (int x = centerX - layer + 1; x <= centerX + layer; x++)
                {
                    yield return (x, centerY - layer);
                }

                // Right edge (moving down)
                for (int y = centerY - layer + 1; y <= centerY + layer; y++)
                {
                    yield return (centerX + layer, y);
                }

                // Bottom edge (moving left)
                for (int x = centerX + layer - 1; x >= centerX - layer; x--)
                {
                    yield return (x, centerY + layer);
                }

                // Left edge (moving up)
                for (int y = centerY + layer - 1; y >= centerY - layer; y--)
                {
                    yield return (centerX - layer, y);
                }
            }
        }

        /// <summary>
        /// Processes chunks with dependency awareness.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="dependencyTracker">The dependency tracker</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksWithDependenciesAsync<T>(
            this ChunkManager2D<T> manager,
            ChunkDependencyTracker dependencyTracker,
            Func<Chunk2D<T>, Task> processor,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();

            await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
                chunks,
                chunk => processor((Chunk2D<T>)chunk),
                chunk => dependencyTracker.GetDependencies(chunk),
                maxDegreeOfParallelism,
                cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Processes chunks with dependency awareness.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="dependencyTracker">The dependency tracker</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksWithDependenciesAsync<T>(
            this ChunkManager3D<T> manager,
            ChunkDependencyTracker dependencyTracker,
            Func<Chunk3D<T>, Task> processor,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();

            await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
                chunks,
                chunk => processor((Chunk3D<T>)chunk),
                chunk => dependencyTracker.GetDependencies(chunk),
                maxDegreeOfParallelism,
                cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Processes chunks with thread safety.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksSafelyAsync<T>(
            this ChunkManager2D<T> manager,
            Func<Chunk2D<T>, Task> processor,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();
            var threadingManager = ChunkThreadingManager.Instance;

            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
                CancellationToken = cancellationToken
            };

            await Task.Run(() => Parallel.ForEach(chunks, options, async chunk =>
            {
                await threadingManager.WithLockAsync(chunk, async () =>
                {
                    await processor(chunk).ConfigureAwait(false);
                }, cancellationToken).ConfigureAwait(false);
            }), cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// Processes chunks with thread safety.
        /// </summary>
        /// <typeparam name="T">The type of chunk data</typeparam>
        /// <param name="manager">The chunk manager</param>
        /// <param name="processor">The function to process each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public static async Task ProcessChunksSafelyAsync<T>(
            this ChunkManager3D<T> manager,
            Func<Chunk3D<T>, Task> processor,
            int? maxDegreeOfParallelism = null,
            CancellationToken cancellationToken = default)
        {
            var chunks = manager.GetAllChunks().ToArray();
            var threadingManager = ChunkThreadingManager.Instance;

            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
                CancellationToken = cancellationToken
            };

            await Task.Run(() => Parallel.ForEach(chunks, options, async chunk =>
            {
                await threadingManager.WithLockAsync(chunk, async () =>
                {
                    await processor(chunk).ConfigureAwait(false);
                }, cancellationToken).ConfigureAwait(false);
            }), cancellationToken).ConfigureAwait(false);
        }
    }
}