//
//  Lol Engine
//
//  Copyright © 2009—2014 Benjamin “Touky” Huet <huet.benjamin@gmail.com>
//            © 2010—2018 Sam Hocevar <sam@hocevar.net>
//
//  Lol Engine is free software. It comes without any warranty, to
//  the extent permitted by applicable law. You can redistribute it
//  and/or modify it under the terms of the Do What the Fuck You Want
//  to Public License, Version 2, as published by the WTFPL Task Force.
//  See http://www.wtfpl.net/ for more details.
//

#include <lol/engine-internal.h>

#include <string>

#include "../../image/resource-private.h"

namespace lol
{

/*
 * Image implementation class
 */

class ZedImageCodec : public ResourceCodec
{
public:
    virtual std::string GetName() { return "<ZedImageCodec>"; }
    virtual ResourceCodecData* Load(std::string const &path);
    virtual bool Save(std::string const &path, ResourceCodecData* data);
};

DECLARE_IMAGE_CODEC(ZedImageCodec, 10)

/*
 * Public Image class
 */

ResourceCodecData* ZedImageCodec::Load(std::string const &path)
{
    if (!ends_with(path, ".RSC"))
        return nullptr;

    // Compacter definition
    struct CompactSecondary
    {
        CompactSecondary(int32_t size) { m_size = size; }
        int32_t m_size;
        array<int32_t> m_tiles;
    };
    struct CompactMain
    {
        CompactMain(int32_t size) { m_size = size; m_count = 0; }
        int32_t                 m_size;
        int32_t                 m_count;
        array<CompactSecondary> m_secondary;
    };
    struct Compacter2d
    {
        void PowSetup(int32_t start, int32_t count)
        {
            for (int i = 0; i < count; i++)
            {
                m_primary << CompactMain(start << i);
                for (int j = 0; j < count; j++)
                    m_primary.last().m_secondary << CompactSecondary(start << j);
            }
        }
        void StepSetup(int32_t start, int32_t interval, int32_t count)
        {
            for (int i = 0; i < count; i++)
            {
                m_primary << CompactMain(start + interval * i);
                for (int j = 0; j < count; j++)
                    m_primary.last().m_secondary << CompactSecondary(start + interval * j);
            }
        }
        void CustomSetup(array<int32_t> custom_list)
        {
            for (int i = 0; i < custom_list.count(); i++)
            {
                m_primary << CompactMain(custom_list[i]);
                for (int j = 0; j < custom_list.count(); j++)
                    m_primary.last().m_secondary << CompactSecondary(custom_list[j]);
            }
        }
        void Store(int32_t tile, ivec2 size)
        {
            for (int i = 0; i < m_primary.count(); i++)
            {
                if (size.y <= m_primary[i].m_size || i == m_primary.count() - 1)
                {
                    for (int j = 0; j < m_primary[i].m_secondary.count(); j++)
                    {
                        if (size.x <= m_primary[i].m_secondary[j].m_size || j == m_primary[i].m_secondary.count() - 1)
                        {
                            m_primary[i].m_secondary[j].m_tiles << tile;
                            m_primary[i].m_count++;
                            break;
                        }
                    }
                    break;
                }
            }
        }
        array<CompactMain>      m_primary;
    };

    File file;
    file.Open(path, FileAccess::Read, true);

    //Put file in memory
    long file_size = file.size();
    array<uint8_t> file_buffer;
    file_buffer.resize(file_size);
    file.Read((uint8_t*)&file_buffer[0], file_size);
    file.Close();

    //Get FileCount
    uint32_t file_pos = 0;
    uint16_t file_count = 0;
    file_count = *((uint16_t*)(&file_buffer[file_pos]));
    file_pos += sizeof(uint16_t);

    array<uint32_t> file_offset;
    file_offset.resize(file_count);
    //Get all the file offsets
    for (int i = 0; i < file_count; i++)
    {
        file_offset[i] = *((uint32_t*)(&file_buffer[file_pos]));
        file_pos += sizeof(uint32_t);
    }
    file_offset << file_size;

    //<Pos, Size>
    array<ivec2, ivec2> tiles;
    tiles.reserve(file_count);

    Compacter2d compacter;
    compacter.StepSetup(8, 8, 10);

    uint32_t total_size = 0;
    array<uint8_t> file_convert;
    file_convert.reserve(file_size);
    array<ivec2> available_sizes;
    //got through all the files and store them
    for (int i = 0; i < file_count; i++)
    {
        file_pos = file_offset[i];

        //Get image size
        uint8_t size_x = 0;
        uint8_t size_y = 0;
        size_y = file_buffer[file_pos++];
        size_x = file_buffer[file_pos++];

        //special tweak
        size_x *= 8;
        total_size += (size_x * size_y);

        //Prepare read
        uint32_t header_length = (size_y + 5) & 0xFC;
        uint32_t data_length = (file_offset[i+1] - file_offset[i]) - header_length;
        uint32_t data_pos = file_offset[i] + header_length;

        /* Seems useless in the end
        //Retrieve Header & footer
        array<uint8_t> header_data;
        header_data.resize(header_length);
        memcpy(&header_data[0], &file_buffer[file_offset[i]], header_length);
        array<uint8_t> footer_data;
        uint32_t footer_length = lol::min((uint32_t)file_buffer.count(), data_pos + data_length + header_length) - (data_pos + data_length);
        if (footer_length > 0)
        {
            footer_data.resize(footer_length);
            memcpy(&footer_data[0], &file_buffer[data_pos + data_length], footer_length);
        }
        */

        //Prepare buffer and tiles infos
        int32_t convert_pos = file_convert.count();
        ivec2 size = ivec2(size_x, size_y);
        //store tile in compacter
        compacter.Store(tiles.count(), ivec2(size_x, size_y));
        //push tile on the stack
        tiles.push(ivec2(file_convert.count(), data_length), ivec2(size_x, size_y));
        file_convert.resize(convert_pos + data_length);

        //Retrieve actual datas
        file_pos = data_pos;
        memcpy(&file_convert[convert_pos], &file_buffer[file_pos], data_length);
        file_pos += data_length;

        //Store size type
        {
            ivec2 size_16 = size;
            int32_t s_16 = 8;
            while (1)
            {
                if (size_16.x < s_16)
                {
                    size_16.x = s_16;
                    break;
                }
                s_16 <<= 1;
            }
            s_16 = 8;
            while (1)
            {
                if (size_16.y < s_16)
                {
                    size_16.y = s_16;
                    break;
                }
                s_16 <<= 1;
            }
            int j = 0;
            for (; j < available_sizes.count(); j++)
                if (available_sizes[j] == size_16)
                    break;
            if (j >= available_sizes.count())
                available_sizes << size_16;
        }
    }

    int32_t tex_sqrt = (int32_t)lol::sqrt((float)total_size);
    int32_t tex_size = 2;
    while (tex_size < tex_sqrt)
        tex_size <<= 1;

    //Prepare final image
    auto data = new ResourceTilesetData(new image(ivec2(tex_size)));
    auto image = data->m_image;
    uint8_t *pixels = image->lock<PixelFormat::Y_8>();

    //Data refactor stage
    ivec2 pos = ivec2(0);
    for (int j = compacter.m_primary.count() - 1; j >= 0; j--)
    {
        for (int k = compacter.m_primary[j].m_secondary.count() - 1; k >= 0; k--)
        {
            //Try something smaller
            if (pos.x + compacter.m_primary[j].m_secondary[k].m_size >= tex_size)
                continue;

            while (compacter.m_primary[j].m_secondary[k].m_tiles.count() > 0)
            {
                //Try something smaller
                if (pos.x + compacter.m_primary[j].m_secondary[k].m_size >= tex_size)
                    break;

                compacter.m_primary[j].m_count--;
                int i = compacter.m_primary[j].m_secondary[k].m_tiles.pop();
                int32_t file_off = tiles[i].m1[0];
                ivec2 t_size = tiles[i].m2;

                ASSERT(pos.y + t_size.y < tex_size);

                //Move image to texture
                int32_t img_off = pos.x + pos.y * tex_size;

                //At this stage image data consists of 4 vertical interlaced blocks
                for (int pass = 0; pass < 4; pass++)
                {
                    for (int y_cur = 0; y_cur < t_size.y; y_cur++)
                    {
                        for (int x_cur = 0; x_cur < t_size.x / 4; x_cur++)
                        {
                            int32_t img_pos = img_off + pass + 4 * x_cur + y_cur * (int32_t)tex_size;
                            pixels[img_pos] = file_convert[file_off++];
                        }
                    }
                }

                //Register new pos and move to next
                tiles[i].m1 = pos;
                pos.x += t_size.x;
            }
        }

        //Do another loop
        if (compacter.m_primary[j].m_count > 0)
        {
            pos.x = 0;
            pos.y += compacter.m_primary[j].m_size;
            j++;
        }
    }
    image->unlock(pixels);

    data->m_tiles = tiles;

    return data;
}

bool ZedImageCodec::Save(std::string const &path, ResourceCodecData* data)
{
    UNUSED(path, data);
    /* FIXME: do we need to implement this? */
    return true;
}

} /* namespace lol */