using System.Collections.ObjectModel;
using System.IO;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Assertions;
namespace UnityEditor.U2D.Aseprite
{
    /// 
    /// Aseprite cell types.
    /// 
    public enum CellTypes
    {
        /// Raw pixel data
        RawImage = 0,
        /// Cell linked to another cell
        LinkedCell = 1,
        /// Compressed pixel data
        CompressedImage = 2,
        /// Compressed tilemap data
        CompressedTileMap = 3
    }
    /// 
    /// Parsed representation of an Aseprite Cell chunk.
    /// 
    public class CellChunk : BaseChunk
    {
        /// 
        public override ChunkTypes chunkType => ChunkTypes.Cell;
        internal CellChunk(uint chunkSize, ushort colorDepth, ReadOnlyCollection paletteEntries, byte alphaPaletteEntry) : base(chunkSize)
        {
            m_ColorDepth = colorDepth;
            m_PaletteEntries = paletteEntries;
            m_AlphaPaletteEntry = alphaPaletteEntry;
        }
        readonly ushort m_ColorDepth;
        readonly ReadOnlyCollection m_PaletteEntries;
        readonly byte m_AlphaPaletteEntry;
        /// 
        /// The layer index is a number to identify a layer in the sprite.
        /// Layers are numbered in the same order as Layer Chunks appear in the file.
        /// 
        public ushort layerIndex { get; private set; }
        /// 
        /// The Cell's X position on the canvas.
        /// 
        public short posX { get; private set; }
        /// 
        /// The Cell's Y position on the canvas.
        /// 
        public short posY { get; private set; }
        /// 
        /// Opacity level of the cell (0 = transparent, 255 = opaque).
        /// 
        public byte opacity { get; private set; }
        /// 
        /// The type of cell.
        /// 
        public CellTypes cellType { get; private set; }
        /// 
        /// A cell's draw order. Higher number means towards the front.
        /// 
        internal short zIndex { get; private set; }
        /// 
        /// The frame index of the cell (Only available for Linked Cells).
        /// 
        public int linkedToFrame { get; private set; } = -1;
        /// 
        /// The width of the cell in pixels.
        /// 
        public ushort width { get; private set; }
        /// 
        /// The height of the cell in pixels.
        /// 
        public ushort height { get; private set; }
        /// 
        /// The image data of the cell.
        /// 
        public NativeArray image { get; private set; }
        /// 
        /// User data associated with the cell.
        /// 
        public UserDataChunk dataChunk { get; set; }
        /// 
        /// Indices to the tiles in the cell. Note, only populated if the cell is of type CompressedTileMap.
        /// 
        internal NativeArray tileIndices { get; private set; }
        /// 
        /// Read and store the chunk data.
        /// 
        /// The active binary reader of the file.
        protected override void InternalRead(BinaryReader reader)
        {
            layerIndex = reader.ReadUInt16();
            posX = reader.ReadInt16();
            posY = reader.ReadInt16();
            opacity = reader.ReadByte();
            cellType = (CellTypes)reader.ReadUInt16();
            zIndex = reader.ReadInt16();
            // Not in use bytes
            for (var i = 0; i < 5; ++i)
            {
                var miscVal = reader.ReadByte();
                Assert.IsTrue(miscVal == 0);
            }
            if (cellType == CellTypes.RawImage)
            {
                width = reader.ReadUInt16();
                height = reader.ReadUInt16();
                byte[] imageData = null;
                if (m_ColorDepth == 32)
                    imageData = reader.ReadBytes(width * height * 4);
                else if (m_ColorDepth == 16)
                    imageData = reader.ReadBytes(width * height * 2);
                else if (m_ColorDepth == 8)
                    imageData = reader.ReadBytes(width * height);
                if (imageData != null)
                    image = AsepriteUtilities.GenerateImageData(m_ColorDepth, imageData, m_PaletteEntries, m_AlphaPaletteEntry);
            }
            else if (cellType == CellTypes.LinkedCell)
            {
                linkedToFrame = reader.ReadUInt16();
            }
            else if (cellType == CellTypes.CompressedImage)
            {
                width = reader.ReadUInt16();
                height = reader.ReadUInt16();
                var dataSize = (int)m_ChunkSize - ChunkHeader.stride - 20;
                var decompressedData = AsepriteUtilities.ReadAndDecompressedData(reader, dataSize);
                image = AsepriteUtilities.GenerateImageData(m_ColorDepth, decompressedData, m_PaletteEntries, m_AlphaPaletteEntry);
            }
            else if (cellType == CellTypes.CompressedTileMap)
            {
                // Width and height in number of tiles
                width = reader.ReadUInt16();
                height = reader.ReadUInt16();
                var bitsPerTile = reader.ReadUInt16();
                var tileIdMask = reader.ReadUInt32();
                var xFlipMask = reader.ReadUInt32();
                var yFlipMask = reader.ReadUInt32();
                var rotation90Mask = reader.ReadUInt32();
                // Not in use bytes
                for (var i = 0; i < 10; ++i)
                    reader.ReadByte();
                var dataSize = (int)m_ChunkSize - ChunkHeader.stride - 48;
                var decompressedData = AsepriteUtilities.ReadAndDecompressedData(reader, dataSize);
                var bytesPerTile = bitsPerTile / 8;
                var noOfTiles = decompressedData.Length / bytesPerTile;
                var indices = new NativeArray(noOfTiles, Allocator.Persistent);
                using var memoryStream = new MemoryStream(decompressedData);
                using var binaryReader = new BinaryReader(memoryStream);
                for (var i = 0; i < noOfTiles; ++i)
                {
                    uint tileData = 0;
                    if (bitsPerTile == 32)
                        tileData = binaryReader.ReadUInt32();
                    else if (bitsPerTile == 16)
                        tileData = binaryReader.ReadUInt16();
                    else if (bitsPerTile == 8)
                        tileData = binaryReader.ReadByte();
                    var tileId = tileData & tileIdMask;
                    indices[i] = tileId;
                }
                tileIndices = indices;
            }
        }
        /// 
        /// Dispose of the image and tile data.
        /// 
        public override void Dispose()
        {
            image.DisposeIfCreated();
            tileIndices.DisposeIfCreated();
        }
    }
}