﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Oni.Imaging
{
    internal static class TgaWriter
    {
        public static void Write(Surface surface, string filePath)
        {
            surface = surface.Convert(SurfaceFormat.BGRA);

            var imageType = TgaImageType.TrueColor;
            byte[] rleData = null;

            if (surface.Width > 2 && surface.Height > 2)
            {
                rleData = Rle32Compress(surface.Width, surface.Height, surface.Data);

                if (rleData.Length > surface.Data.Length)
                    rleData = null;
                else
                    imageType = TgaImageType.RleTrueColor;
            }

            var header = TgaHeader.Create(surface.Width, surface.Height, imageType);

            Directory.CreateDirectory(Path.GetDirectoryName(filePath));

            using (var stream = File.Create(filePath))
            using (var writer = new BinaryWriter(stream))
            {
                header.Write(writer);

                if (rleData != null)
                    writer.Write(rleData, 0, rleData.Length);
                else
                    writer.Write(surface.Data, 0, surface.Data.Length);
            }
        }

        private static byte[] Rle32Compress(int width, int height, byte[] sourceData)
        {
            var result = new List<byte>();

            for (int y = height - 1; y >= 0; y--)
            {
                int lineOffset = y * width * 4;
                int lastPixel = BitConverter.ToInt32(sourceData, y * width * 4);
                int runStart = 0;
                byte packetType = 64;

                for (int x = 1; x < width; x++)
                {
                    int pixel = BitConverter.ToInt32(sourceData, x * 4 + lineOffset);

                    if (pixel == lastPixel)
                    {
                        if (packetType == 0)
                        {
                            Rle32WritePackets(result, packetType, sourceData, runStart, x - 1, lineOffset);
                            runStart = x - 1;
                        }

                        packetType = 128;
                    }
                    else
                    {
                        if (packetType == 128)
                        {
                            Rle32WritePackets(result, packetType, sourceData, runStart, x, lineOffset);
                            runStart = x;
                        }

                        packetType = 0;
                    }

                    lastPixel = pixel;

                    if (x == width - 1)
                    {
                        Rle32WritePackets(result, packetType, sourceData, runStart, width, lineOffset);
                    }
                }
            }

            return result.ToArray();
        }

        private static void Rle32WritePackets(List<byte> result, byte packetType, byte[] sourceData, int xStart, int xStop, int lineOffset)
        {
            int count = xStop - xStart;

            if (count == 0)
                return;

            int startOffset = (xStart * 4) + lineOffset;

            if (packetType == 128)
            {
                for (; count > 128; count -= 128)
                {
                    result.Add((byte)(packetType | 127));

                    for (int i = 0; i < 4; i++)
                        result.Add(sourceData[startOffset + i]);
                }

                result.Add((byte)(packetType | (count - 1)));

                for (int i = 0; i < 4; i++)
                    result.Add(sourceData[startOffset + i]);
            }
            else
            {
                for (; count > 128; count -= 128)
                {
                    result.Add((byte)(packetType | 127));

                    for (int i = 0; i < 4 * 128; i++)
                        result.Add(sourceData[startOffset + i]);

                    startOffset += 4 * 128;
                }

                result.Add((byte)(packetType | (count - 1)));

                for (int i = 0; i < 4 * count; i++)
                    result.Add(sourceData[startOffset + i]);
            }
        }
    }
}
