/// .ase & .aseprite file format specs:
/// https://github.com/aseprite/aseprite/blob/main/docs/ase-file-specs.md    

using System;
using System.Collections.ObjectModel;
using System.IO;
using UnityEngine.Assertions;

namespace UnityEditor.U2D.Aseprite
{
    /// <summary>
    /// Parsed representation of an Aseprite file.
    /// Should be disposed after use.
    /// </summary>
    public class AsepriteFile : IDisposable
    {
        /// <summary>
        /// File size in bytes.
        /// </summary>
        public uint fileSize { get; private set; }
        /// <summary>
        /// Number of frames in the file.
        /// </summary>
        public ushort noOfFrames { get; private set; }
        /// <summary>
        /// Canvas width in pixels.
        /// </summary>
        public ushort width { get; private set; }
        /// <summary>
        /// Canvas height in pixels.
        /// </summary>
        public ushort height { get; private set; }
        /// <summary>
        /// Color depth (bits per pixel).
        /// </summary>
        public ushort colorDepth { get; private set; }
        
        internal uint flags { get; private set; }
        
        /// <summary>
        /// Time per frame in milliseconds.
        /// </summary>
        public ushort animSpeed { get; private set; }
        /// <summary>
        /// Palette entry (index) which represent transparent color
        /// in all non-background layers (only for Indexed sprites).
        /// </summary>
        public byte alphaPaletteEntry { get; private set; }
        /// <summary>
        /// Number of colors (0 means 256 for old sprites).
        /// </summary>
        public ushort noOfColors { get; private set; }
        /// <summary>
        /// Pixel width (pixel ratio is "pixel width/pixel height").
        /// If this or pixel height field is zero, pixel ratio is 1:1.
        /// </summary> 
        public byte pixelWidth { get; private set; }
        /// <summary>
        /// Pixel height (pixel ratio is "pixel width/pixel height").
        /// If this or pixel width field is zero, pixel ratio is 1:1.
        /// </summary>
        public byte pixelHeight { get; private set; }
        /// <summary>
        /// X position of the grid.
        /// </summary>
        public short gridPosX { get; private set; }
        /// <summary>
        /// Y position of the grid.
        /// </summary>
        public short gridPosY { get; private set; }
        /// <summary>
        /// Grid width (zero if there is no grid, grid size is 16x16 on Aseprite by default).
        /// </summary>
        public ushort gridWidth { get; private set; }
        /// <summary>
        /// Grid height (zero if there is no grid).
        /// </summary>
        public ushort gridHeight { get; private set; }
        /// <summary>
        /// Parsed data of each frame.
        /// </summary>
        public ReadOnlyCollection<FrameData> frameData => Array.AsReadOnly(m_FrameData);
        FrameData[] m_FrameData;

        internal void Read(BinaryReader reader)
        {
            fileSize = reader.ReadUInt32();
            var misc0 = reader.ReadUInt16();
            noOfFrames = reader.ReadUInt16();
            width = reader.ReadUInt16();
            height = reader.ReadUInt16();
            colorDepth = reader.ReadUInt16();
            flags = reader.ReadUInt32();
            animSpeed = reader.ReadUInt16();
            var misc1 = reader.ReadUInt32();
            var misc2 = reader.ReadUInt32();
            alphaPaletteEntry = reader.ReadByte();
            var miscByte0 = reader.ReadByte();
            var miscByte1 = reader.ReadByte();
            var miscByte2 = reader.ReadByte();
            noOfColors = reader.ReadUInt16();
            pixelWidth = reader.ReadByte();
            pixelHeight = reader.ReadByte();
            gridPosX = reader.ReadInt16();
            gridPosY = reader.ReadInt16();
            gridWidth = reader.ReadUInt16();
            gridHeight = reader.ReadUInt16();
            
            Assert.IsTrue(misc0 == 0xA5E0, "Unexpected file content. The file is most likely corrupt.");
            
            // Unused 84 bytes
            for (var i = 0; i < 84; ++i)
                reader.ReadByte();

            m_FrameData = new FrameData[noOfFrames];
        }

        internal void SetFrameData(int index, FrameData data)
        {
            if (index < 0 || index >= m_FrameData.Length)
                return;
            m_FrameData[index] = data;
        }

        public void Dispose()
        {
            for (var i = 0; i < m_FrameData.Length; ++i)
                m_FrameData[i].Dispose();
        }
    }
}