//! \file       ImageHG3.cs
//! \date       Sat Jul 19 17:31:09 2014
//! \brief      Frontwing HG3 image format implementation.
//
// Copyright (C) 2014 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.IO;
using System.ComponentModel.Composition;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using GameRes.Compression;
using GameRes.Utility;

namespace GameRes.Formats.CatSystem
{
    [Export(typeof(ImageFormat))]
    public class Hg3Format : ImageFormat
    {
        public override string Tag { get { return "HG3"; } }
        public override string Description { get { return "Frontwing proprietary image format"; } }
        public override uint Signature { get { return 0x332d4748; } }

        public override ImageMetaData ReadMetaData (Stream stream)
        {
            var header = new byte[0x4c];
            if (0x4c != stream.Read (header, 0, header.Length))
                return null;
            if (LittleEndian.ToUInt32 (header, 0) != Signature)
                return null;
            if (LittleEndian.ToUInt32 (header, 4) != 0x0c)
                return null;
            if (!Binary.AsciiEqual (header, 0x14, "stdinfo\0"))
                return null;
            if (0x38 != LittleEndian.ToUInt32 (header, 0x1c))
                return null;
            if (0x20 != LittleEndian.ToUInt32 (header, 0x2c))
                return null;
            uint width  = LittleEndian.ToUInt32 (header, 0x24); // @@L0
            uint height = LittleEndian.ToUInt32 (header, 0x28);
            int pos_x   = LittleEndian.ToInt32 (header, 0x30);
            int pos_y   = LittleEndian.ToInt32 (header, 0x34);
            pos_x      -= LittleEndian.ToInt32 (header, 0x44);
            pos_y      -= LittleEndian.ToInt32 (header, 0x48);

            return new ImageMetaData
            {
                Width = width,
                Height = height,
                OffsetX = pos_x,
                OffsetY = pos_y,
                BPP = 32,
            };
        }

        public override ImageData Read (Stream stream, ImageMetaData info)
        {
            stream.Position = 0x40;
            bool flipped = 0 == (stream.ReadByte() & 1) || info.OffsetY < 0;
            stream.Position = 0x4c;
            var header = new byte[0x28];
            if (0x28 != stream.Read (header, 0, header.Length))
                return null;
            if (!Binary.AsciiEqual (header, "img0000\0"))
                return null;
            uint data_size = LittleEndian.ToUInt32 (header, 0x0c);
            if (data_size < 0x18)
                return null;
            if (info.Height != LittleEndian.ToUInt32 (header, 0x14))
                return null;
            uint packed2_size   = LittleEndian.ToUInt32 (header, 0x18);
            uint unpacked2_size = LittleEndian.ToUInt32 (header, 0x1c);
            uint packed1_size   = LittleEndian.ToUInt32 (header, 0x20);
            uint unpacked1_size = LittleEndian.ToUInt32 (header, 0x24);
            if (packed2_size + packed1_size < packed2_size)
                return null;
            if (unpacked2_size + unpacked1_size < unpacked2_size)
                return null;

            long data_pos = stream.Position;
            using (var unpacked2 = ZLibCompressor.DeCompress (stream))
            {
                stream.Position = data_pos + packed2_size;
                using (var unpacked1 = ZLibCompressor.DeCompress (stream))
                {
                    var decoder = new Decoder (unpacked1.GetBuffer(), unpacked1_size,
                                               unpacked2.GetBuffer(), unpacked2_size,
                                               info.Width, info.Height);
                    decoder.Unpack();
                    var pixels = decoder.Data;
                    int stride = (int)info.Width * 4;
                    if (flipped)
                        return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, pixels, stride);
                    else
                        return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride);
                }
            }
        }

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

        class Decoder
        {
            byte[] m_in1;
            byte[] m_in2;
            byte[] m_image;
            uint m_in1_size;
            uint m_in2_size;
            uint m_dst_size;
            uint m_width;
            uint m_height;

            public byte[] Data { get { return m_image; } }

            public Decoder (byte[] in1, uint in1_size, byte[] in2, uint in2_size,
                            uint width, uint height)
            {
                m_in1 = in1;
                m_in1_size = in1_size;
                m_in2 = in2;
                m_in2_size = in2_size;
                m_width = width;
                m_height = height;
                m_dst_size = width*height*4;
                m_image = new byte[(int)m_dst_size];
            }

            uint esi;
            uint edi;
            uint eax;
            uint ebx;
            uint ecx;
            uint edx;

            uint L0, L1, L2, L3, L4;
            uint m_plane;

            public void Unpack ()
            {
                m_plane = 0;
                edi = 0;
                esi = 0;
                ebx = 0;
                eax = m_in1_size;
                L0 = m_in2_size;
                L1 = 0;
                bool skip_first = GetNextBit();
                Proc4();
                ecx = m_dst_size;
                if (eax > m_dst_size)
                    throw new InvalidFormatException ("Underflow at Hg3Format.Decoder.Unpack()");
                m_dst_size -= eax;
                ecx >>= 2;
                L2 = eax;
                L3 = ecx;
                L4 = ecx;
                for (;;)
                {
                    if (!skip_first)
                    {
    // @@1:
                        if (0 == L2)
                            break;
                        Proc4();
                        ecx = eax;
                        if (ecx > L2)
                            throw new InvalidFormatException ("Overflow at Hg3Format.Decoder.Unpack()");
                        L2 -= ecx;
                        eax = 0;
                        do
                            Proc2();
                        while (0 != --ecx);
                    }
    // @@1a:
                    if (0 == L2)
                        break;
                    Proc4();
                    ecx = eax;
                    if (ecx > L2 || ecx > L0)
                        throw new InvalidFormatException ("Overflow (2) at Hg3Format.Decoder.Unpack()");
                    L2 -= ecx;
                    L0 -= ecx;
                    do
                    {
                        eax = m_in2[L1++];
                        Proc2();
                    }
                    while (0 != --ecx);
                    skip_first = false;
                }
    // @@7:
                ecx = m_dst_size;
                esi = 0;
                if (0 != ecx)
                {
                    eax = 0;
                    do
                        Proc2();
                    while (0 != --ecx);
                }
                Proc6 (m_width, m_height);
    // @@9:
                eax = 0;
                edx = m_in1_size;
            }

            void Proc2 () // @@2
            {
                m_image[edi] = (byte)eax;
                edi += 4;
                if (0 == --L3)
                {
                    edi = ++m_plane;
                    L3 = L4;
                }
            }

            bool GetNextBit () // @@3
            {
                bool carry = 0 != (ebx & 1);
                ebx >>= 1;
                if (0 == ebx)
                {
                    if (0 == m_in1_size--)
                        throw new InvalidFormatException ("Hg3Format.Decoder.Underflow at GetNextBit()");
                    ebx = (uint)(m_in1[esi++] | 0x100);
                    carry = 0 != (ebx & 1);
                    ebx >>= 1;
                }
                return carry;
            }

            void Proc4 () // @@4
            {
                ecx = 0;
                eax = 0;
                do
                {
                    if (ecx >= 0x20)
                        throw new InvalidFormatException ("Hg3Format.Decoder.Overflow at Proc4");
                    ++ecx;
                }
                while (!GetNextBit());
                ++eax;
                while (0 != --ecx)
                {
                    eax += eax + (uint)(GetNextBit() ? 1 : 0);
                }
            }

            void Proc6 (uint width, uint height)
            {
                uint[] table = new uint[0x100];
                ecx = 0;
                for (uint i = 0; i < 0x100; ++i)
                {
                    eax = 0xffffffff;
                    edx = i;
                    do
                    {
                        eax >>= 2;
                        eax |= (edx & 3) << 30;
                        eax >>= 6;
                        edx >>= 2;
                    }
                    while (0 != (0x80 & eax));
                    table[i] = eax;
                }
                ecx = width * height * 4;
                for (uint i = 0; i < ecx; i += 4)
                {
                    eax = m_image[i];
                    edx = table[eax];
                    edx <<= 2;
                    eax = m_image[i+1];
                    edx += table[eax];
                    edx <<= 2;
                    eax = m_image[i+2];
                    edx += table[eax];
                    edx <<= 2;
                    eax = m_image[i+3];
                    edx += table[eax];
                    m_image[i]   = (byte)(edx);
                    m_image[i+1] = (byte)(edx >> 8);
                    m_image[i+2] = (byte)(edx >> 16);
                    m_image[i+3] = (byte)(edx >> 24);
                }
                edi = 0;
                for (int i = 0; i < 4; ++i)
                {
                    eax = m_image[edi];
                    edx = (uint)(0 == (eax & 1) ? 0 : 0xff);
                    eax >>= 1;
                    eax ^= edx;
                    m_image[edi++] = (byte)eax;
                }
                ecx = width;
                if (0 != --ecx)
                {
                    ecx <<= 2;
                    do
                    {
                        eax = m_image[edi];
                        edx = (uint)(0 == (eax & 1) ? 0 : 0xff);
                        eax >>= 1;
                        eax ^= edx;
                        eax += m_image[edi-4];
                        m_image[edi++] = (byte)eax;
                    }
                    while (0 != --ecx);
                }
                ecx = height;
                if (0 != --ecx)
                {
                    uint stride = width*4;
                    ecx *= stride;
                    do
                    {
                        eax = m_image[edi];
                        edx = (uint)(0 == (eax & 1) ? 0 : 0xff);
                        eax >>= 1;
                        eax ^= edx;
                        eax += m_image[edi-stride];
                        m_image[edi++] = (byte)eax;
                    }
                    while (0 != --ecx);
                }
            }
        }
    }
}
