using System;
using System.Collections.Generic;
using System.IO;
using AdvChkSys.Chunk;

namespace AdvChkSys.Serialization
{
    /// <summary>
    /// Provides serialization and deserialization for chunk instances.
    /// Supports Chunk2D T and Chunk3D T with primitive types (e.g., byte, int, float).
    /// </summary>.
    public static class ChunkSerializer
    {
        // -------- 2D --------

        /// <summary>
        /// Serializes a 2D chunk to a byte array.
        /// </summary>
        /// <typeparam name="T">The type of data stored in the chunk</typeparam>
        /// <param name="chunk">The chunk to serialize</param>
        /// <returns>Serialized byte array</returns>
        public static byte[] Serialize2D<T>(Chunk2D<T> chunk) where T : struct
        {
            using var ms = new MemoryStream();
            using var writer = new BinaryWriter(ms);

            writer.Write(chunk.X);
            writer.Write(chunk.Y);
            writer.Write(chunk.Width);
            writer.Write(chunk.Height);

            writer.Write(chunk.Metadata.Count);
            foreach (var kvp in chunk.Metadata)
            {
                writer.Write(kvp.Key);
                writer.Write(kvp.Value?.ToString() ?? "");
            }

            for (int x = 0; x < chunk.Width; x++)
                for (int y = 0; y < chunk.Height; y++)
                    WritePrimitive(writer, chunk[x, y]);

            writer.Flush();
            return ms.ToArray();
        }

        /// <summary>
        /// Deserializes a byte array into a 2D chunk.
        /// </summary>
        /// <typeparam name="T">The type of data stored in the chunk</typeparam>
        /// <param name="data">The serialized chunk data</param>
        /// <returns>Deserialized chunk</returns>
        public static Chunk2D<T> Deserialize2D<T>(byte[] data) where T : struct
        {
            using var ms = new MemoryStream(data);
            using var reader = new BinaryReader(ms);

            int x = reader.ReadInt32();
            int y = reader.ReadInt32();
            int width = reader.ReadInt32();
            int height = reader.ReadInt32();

            var chunk = new Chunk2D<T>(x, y, width, height);

            int metaCount = reader.ReadInt32();
            for (int i = 0; i < metaCount; i++)
            {
                string key = reader.ReadString();
                string value = reader.ReadString();
                chunk.Metadata[key] = value;
            }

            for (int ix = 0; ix < width; ix++)
                for (int iy = 0; iy < height; iy++)
                    chunk[ix, iy] = ReadPrimitive<T>(reader);

            return chunk;
        }

        // -------- 3D --------

        /// <summary>
        /// Serializes a 3D chunk to a byte array.
        /// </summary>
        /// <typeparam name="T">The type of data stored in the chunk</typeparam>
        /// <param name="chunk">The chunk to serialize</param>
        /// <returns>Serialized byte array</returns>
        public static byte[] Serialize3D<T>(Chunk3D<T> chunk) where T : struct
        {
            using var ms = new MemoryStream();
            using var writer = new BinaryWriter(ms);

            writer.Write(chunk.X);
            writer.Write(chunk.Y);
            writer.Write(chunk.Z);
            writer.Write(chunk.Width);
            writer.Write(chunk.Height);
            writer.Write(chunk.Depth);

            writer.Write(chunk.Metadata.Count);
            foreach (var kvp in chunk.Metadata)
            {
                writer.Write(kvp.Key);
                writer.Write(kvp.Value?.ToString() ?? "");
            }

            for (int x = 0; x < chunk.Width; x++)
                for (int y = 0; y < chunk.Height; y++)
                    for (int z = 0; z < chunk.Depth; z++)
                        WritePrimitive(writer, chunk[x, y, z]);

            writer.Flush();
            return ms.ToArray();
        }

        /// <summary>
        /// Deserializes a byte array into a 3D chunk.
        /// </summary>
        /// <typeparam name="T">The type of data stored in the chunk</typeparam>
        /// <param name="data">The serialized chunk data</param>
        /// <returns>Deserialized chunk</returns>
        public static Chunk3D<T> Deserialize3D<T>(byte[] data) where T : struct
        {
            using var ms = new MemoryStream(data);
            using var reader = new BinaryReader(ms);

            int x = reader.ReadInt32();
            int y = reader.ReadInt32();
            int z = reader.ReadInt32();
            int width = reader.ReadInt32();
            int height = reader.ReadInt32();
            int depth = reader.ReadInt32();

            var chunk = new Chunk3D<T>(x, y, z, width, height, depth);

            int metaCount = reader.ReadInt32();
            for (int i = 0; i < metaCount; i++)
            {
                string key = reader.ReadString();
                string value = reader.ReadString();
                chunk.Metadata[key] = value;
            }

            for (int ix = 0; ix < width; ix++)
                for (int iy = 0; iy < height; iy++)
                    for (int iz = 0; iz < depth; iz++)
                        chunk[ix, iy, iz] = ReadPrimitive<T>(reader);

            return chunk;
        }

        // -------- Helpers --------

        private static void WritePrimitive<T>(BinaryWriter writer, T value) where T : struct
        {
            if (typeof(T) == typeof(byte))
                writer.Write((byte)(object)value);
            else if (typeof(T) == typeof(int))
                writer.Write((int)(object)value);
            else if (typeof(T) == typeof(float))
                writer.Write((float)(object)value);
            else if (typeof(T) == typeof(double))
                writer.Write((double)(object)value);
            else if (typeof(T) == typeof(short))
                writer.Write((short)(object)value);
            else if (typeof(T) == typeof(long))
                writer.Write((long)(object)value);
            else if (typeof(T) == typeof(bool))
                writer.Write((bool)(object)value);
            else
                throw new NotSupportedException($"Type {typeof(T)} is not supported for serialization.");
        }

        private static T ReadPrimitive<T>(BinaryReader reader) where T : struct
        {
            if (typeof(T) == typeof(byte))
                return (T)(object)reader.ReadByte();
            else if (typeof(T) == typeof(int))
                return (T)(object)reader.ReadInt32();
            else if (typeof(T) == typeof(float))
                return (T)(object)reader.ReadSingle();
            else if (typeof(T) == typeof(double))
                return (T)(object)reader.ReadDouble();
            else if (typeof(T) == typeof(short))
                return (T)(object)reader.ReadInt16();
            else if (typeof(T) == typeof(long))
                return (T)(object)reader.ReadInt64();
            else if (typeof(T) == typeof(bool))
                return (T)(object)reader.ReadBoolean();
            else
                throw new NotSupportedException($"Type {typeof(T)} is not supported for deserialization.");
        }
    }
}