﻿using System;
using System.IO;

namespace Oni.Imaging
{
    internal class TgaHeader
    {
        #region Private data
        private bool hasColorMap;
        private TgaImageType imageType;
        private int colorMapIndex;
        private int colorMapLength;
        private int colorMapEntrySize;
        private int width;
        private int height;
        private int pixelDepth;
        private int imageDescriptor;
        private bool xFlip;
        private bool yFlip;
        private bool hasAlpha;
        #endregion

        public TgaImageType ImageType => imageType;
        public int Width => width;
        public int Height => height;
        public int PixelSize => pixelDepth / 8;
        public bool XFlip => xFlip;
        public bool YFlip => yFlip;

        public static TgaHeader Read(BinaryReader reader)
        {
            int idLength = reader.ReadByte();

            var header = new TgaHeader();
            header.hasColorMap = (reader.ReadByte() != 0);
            header.imageType = (TgaImageType)reader.ReadByte();
            header.colorMapIndex = reader.ReadUInt16();
            header.colorMapLength = reader.ReadUInt16();
            header.colorMapEntrySize = reader.ReadByte();
            reader.ReadUInt16(); // x origin
            reader.ReadUInt16(); // y origin
            header.width = reader.ReadUInt16();
            header.height = reader.ReadUInt16();
            header.pixelDepth = reader.ReadByte();
            header.imageDescriptor = reader.ReadByte();

            if (!Enum.IsDefined(typeof(TgaImageType), header.ImageType) || header.ImageType == TgaImageType.None)
                throw new NotSupportedException(string.Format("Unsupported TGA image type {0}", header.ImageType));

            if (header.Width == 0 || header.Height == 0)
                throw new InvalidDataException("Invalid TGA file");

            if (header.ImageType == TgaImageType.TrueColor
                && (header.pixelDepth != 16
                    && header.pixelDepth != 24
                    && header.pixelDepth != 32))
            {
                throw new InvalidDataException(string.Format("Invalid true color pixel depth {0}", header.pixelDepth));
            }

            if (header.hasColorMap)
            {
                if (header.colorMapEntrySize != 16
                    && header.colorMapEntrySize != 24
                    && header.colorMapEntrySize != 32)
                {
                    throw new InvalidDataException(string.Format("Invalid color map entry size {0}", header.colorMapEntrySize));
                }

                if (header.ImageType != TgaImageType.ColorMapped
                    && header.ImageType != TgaImageType.RleColorMapped)
                {
                    //
                    // We have a color map but the image type does not use it so we'll just skip it.
                    //

                    reader.Position += header.colorMapLength * header.colorMapEntrySize / 8;
                }
            }

            //
            // Skip the identification field because we don't need it.
            //

            reader.Position += idLength;

            if (header.pixelDepth == 32)
                header.hasAlpha = ((header.imageDescriptor & 0x0f) == 8);
            else if (header.pixelDepth == 16)
                header.hasAlpha = ((header.imageDescriptor & 0x0f) == 1);
            else
                header.hasAlpha = false;

            header.xFlip = ((header.imageDescriptor & 16) == 16);
            header.yFlip = ((header.imageDescriptor & 32) == 32);

            return header;
        }

        public static TgaHeader Create(int width, int height, TgaImageType imageType)
        {
            return new TgaHeader {
                imageType = imageType,
                width = width,
                height = height,
                pixelDepth = 32,
                imageDescriptor = 8
            };
        }

        public void Write(BinaryWriter writer)
        {
            writer.Write((byte)0);
            writer.Write((byte)(hasColorMap ? 1 : 0));
            writer.Write((byte)imageType);
            writer.Write((ushort)colorMapIndex);
            writer.Write((ushort)colorMapLength);
            writer.Write((byte)colorMapEntrySize);
            writer.Write((ushort)0);
            writer.Write((ushort)0);
            writer.Write((ushort)width);
            writer.Write((ushort)height);
            writer.Write((byte)pixelDepth);
            writer.Write((byte)imageDescriptor);
        }

        public SurfaceFormat GetSurfaceFormat()
        {
            switch (pixelDepth)
            {
                case 16:
                    return hasAlpha ? SurfaceFormat.BGRA5551 : SurfaceFormat.BGRX5551;
                case 24:
                    return SurfaceFormat.BGRX;
                default:
                case 32:
                    return hasAlpha ? SurfaceFormat.BGRA : SurfaceFormat.BGRX;
            }
        }

        public Color GetPixel(byte[] src, int srcOffset)
        {
            switch (pixelDepth)
            {
                case 16:
                    if (hasAlpha)
                        return Color.ReadBgra5551(src, srcOffset);
                    else
                        return Color.ReadBgrx5551(src, srcOffset);

                case 24:
                    return Color.ReadBgrx(src, srcOffset);

                default:
                case 32:
                    if (hasAlpha)
                        return Color.ReadBgra(src, srcOffset);
                    else
                        return Color.ReadBgrx(src, srcOffset);
            }
        }
    }
}
