//! \file       ImageGEC.cs
//! \date       Mon Jun 20 15:45:46 2016
//! \brief      Yellow Pig image format.
//
// Copyright (C) 2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//

using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using GameRes.Utility;

namespace GameRes.Formats.YellowPig
{
    internal class GecMetaData : ImageMetaData
    {
        public byte Type;
        public int  DataOffset;
        public int  AlphaOffset;
    }

    [Export(typeof(ImageFormat))]
    public class GecFormat : ImageFormat
    {
        public override string         Tag { get { return "GEC"; } }
        public override string Description { get { return "Yellow Pig image format"; } }
        public override uint     Signature { get { return 0; } }

        public override ImageMetaData ReadMetaData (IBinaryStream stream)
        {
            var header = stream.ReadHeader (0x11);
            byte type = header[0];
            if (type != 0 && type != 1)
                return null;
            var info = new GecMetaData
            {
                Type    = type,
                OffsetX = header.ToInt16 (1),
                OffsetY = header.ToInt16 (3),
                Width   = header.ToUInt16 (5),
                Height  = header.ToUInt16 (7),
                BPP     = 0 == type ? 24 : 32,
                AlphaOffset = header.ToInt32 (9),
                DataOffset  = header.ToInt32 (0xD),
            };
            if (info.OffsetX < 0 || info.OffsetY < 0 || info.Width <= 0 || info.Height <= 0
                || info.DataOffset < 0 || info.DataOffset > stream.Length)
                return null;
            if (1 == type && info.AlphaOffset <= 0)
                return null;
            return info;
        }

        public override ImageData Read (IBinaryStream stream, ImageMetaData info)
        {
            var reader = new GecReader (stream.AsStream, (GecMetaData)info);
            reader.Unpack();
            return ImageData.CreateFlipped (info, reader.Format, null, reader.Data, reader.Stride);
        }

        public override void Write (Stream file, ImageData image)
        {
            throw new NotImplementedException ("GecFormat.Write not implemented");
        }
    }

    internal sealed class GecReader
    {
        byte[]          m_input;
        byte[]          m_output;
        GecMetaData     m_info;

        public PixelFormat  Format { get; private set; }
        public int          Stride { get; private set; }
        public byte[]         Data { get { return m_output; } }

        public GecReader (Stream input, GecMetaData info)
        {
            m_input = new byte[input.Length];
            input.Read (m_input, 0, m_input.Length);
            m_info = info;
        }

        int m_bits = 0;
        int m_bits_src = 0;
        int m_bits_count = 0;

        public void Unpack ()
        {
            if (0 == m_info.Type)
            {
                UnpackPixels (0x11);
                Format = PixelFormats.Bgr24;
                Stride = (int)m_info.Width * 3;
            }
            else
            {
                UnpackPixels (0x1D);
                int bits = 0x1D + m_info.AlphaOffset;
                m_alpha_width  = LittleEndian.ToUInt16 (m_input, 0x15);
                m_alpha_height = LittleEndian.ToUInt16 (m_input, 0x17);
                int data = bits + LittleEndian.ToInt32 (m_input, 0x19);
                var alpha = UnpackAlpha (bits, data);
                ApplyAlpha (alpha);
                Format = PixelFormats.Bgra32;
                Stride = (int)m_info.Width * 4;
            }
        }

        int m_alpha_width;
        int m_alpha_height;

        void ApplyAlpha (byte[] alpha)
        {
            var image = new byte[m_info.Width * m_info.Height * 4];
            int src = 0;
            int a_y = m_alpha_height - (int)m_info.Height - m_info.OffsetY;
            int a_src = a_y * m_alpha_width + m_info.OffsetX;
            int dst = 0;
            for (uint y = 0; y < m_info.Height; ++y)
            {
                for (uint x = 0; x < m_info.Width; ++x)
                {
                    image[dst++] = m_output[src++];
                    image[dst++] = m_output[src++];
                    image[dst++] = m_output[src++];
                    image[dst++] = alpha[a_src+x];
                }
                a_src += m_alpha_width;
            }
            m_output = image;
        }

        int m_dst;
        byte[] m_table = new byte[0x100];

        void UnpackPixels (int bits_src)
        {
            m_bits_src = bits_src;
            m_bits_count = 0;
            m_output = new byte[(int)m_info.Width * (int)m_info.Height * 3];
            int src = bits_src + m_info.DataOffset;
            for (int j = 0; j < 0x100; ++j)
                m_table[j] = (byte)j;
            byte[] frame1 = new byte[0x10002];
            byte[] frame2 = new byte[0x10002];
            m_dst = 0;
            while (m_dst < m_output.Length)
            {
                int count = Math.Min (m_output.Length - m_dst, 0xFFFF);
                if (GetNextBit() != 0)
                {
                    ReadFrame (frame1, count + 2);
                    UnpackFrame1 (frame1, frame2, count + 2);
                    UnpackFrame2 (frame2, 2, count, LittleEndian.ToUInt16 (frame2, 0));
                }
                else
                {
                    src = UnpackRLE (src, count);
                }
            }
        }

        byte[] UnpackAlpha (int bits_src, int data_src)
        {
            m_bits_src = bits_src;
            m_bits_count = 0;
            var alpha = new byte[m_alpha_height * m_alpha_width];
            int dst = 0;
            while (dst < alpha.Length)
            {
                if (GetNextBit() != 0)
                {
                    int count = GetInt();
                    byte v = m_input[data_src++];
                    while (count --> 0)
                        alpha[dst++] = v;
                }
                else
                {
                    alpha[dst++] = m_input[data_src++];
                }
            }
            return alpha;
        }

        void ReadFrame (byte[] frame, int count) // sub_423990
        {
            int j = 0;
            while (j < count)
            {
                if (0 != GetNextBit())
                {
                    frame[j++] = (byte)GetInt();
                }
                else
                {
                    int n = GetInt();
                    while (n --> 0)
                        frame[j++] = 0;
                }
            }
        }

        void UnpackFrame1 (byte[] frame, byte[] dst, int count) // sub_423670
        {
            byte prev = 1;
            int n = 0;
            while (count --> 0)
            {
                byte v8 = frame[n];
                byte v9 = m_table[v8];
                if (1 == v8)
                {
                    if (prev != 0)
                    {
                        m_table[1] = m_table[0];
                        m_table[0] = v9;
                    }
                }
                else if (v8 > 1)
                {
                    Buffer.BlockCopy (m_table, 1, m_table, 2, v8 - 1);
                    m_table[1] = v9;
                }
                dst[n++] = v9;
                prev = v8;
            }
        }

        ushort[] table1 = new ushort[0x100];
        ushort[] table2 = new ushort[0x100];
        ushort[] table3 = new ushort[0x10002];

        void UnpackFrame2 (byte[] frame, int src, int count, ushort first) // sub_4236F0
        {
            for (int i = 0; i < 0x100; ++i)
                table1[i] = 0;
            for (int i = 0; i < count; ++i)
                ++table1[frame[src+i]];
            ushort v = 0;
            for (int i = 0; i < 0x100; i += 1)
            {
                table2[i] = v;
                v += table1[i];
                table1[i] = 0;
            }
            for (int i = 0; i < count; ++i)
            {
                int d = frame[src+i];
                ushort a = table2[d];
                ushort b = table1[d]++;
                table3[a + b] = (ushort)i;
            }
            ushort next = table3[first];
            while (count --> 0)
            {
                m_output[m_dst++] = frame[src+next];
                next = table3[next];
            }
        }

        int UnpackRLE (int src, int count) // sub_423A10
        {
            while (count > 0)
            {
                if (0 == GetNextBit())
                {
                    m_output[m_dst++] = m_input[src++];
                    m_output[m_dst++] = m_input[src++];
                    m_output[m_dst++] = m_input[src++];
                    count -= 3;
                }
                else
                {
                    int n = GetInt() * 3;
                    m_output[m_dst]   = m_input[src++];
                    m_output[m_dst+1] = m_input[src++];
                    m_output[m_dst+2] = m_input[src++];
                    Binary.CopyOverlapped (m_output, m_dst, m_dst+3, n-3);
                    m_dst += n;
                    count -= n;
                }
            }
            return src;
        }

        int GetInt () // sub_423810
        {
            int count = 0;
            while (0 == GetNextBit())
            {
                ++count;
            }
            int v = 1;
            while (count > 0)
            {
                v = (v << 1) | GetNextBit();
                --count;
            }
            return v;
        }

        int GetNextBit ()
        {
            if (m_bits_count-- <= 0)
            {
                m_bits = LittleEndian.ToInt32 (m_input, m_bits_src);
                m_bits_src += 4;
                m_bits_count = 31;
            }
            else
            {
                m_bits >>= 1;
            }
            return m_bits & 1;
        }
    }
}
