//=============================================================================
//
// Adventure Game Studio (AGS)
//
// Copyright (C) 1999-2011 Chris Jones and 2011-2025 various contributors
// The full list of copyright holders can be found in the Copyright.txt
// file, which is part of this source code distribution.
//
// The AGS source code is provided under the Artistic License 2.0.
// A copy of this license can be found in the file License.txt and at
// https://opensource.org/license/artistic-2-0/
//
//=============================================================================
//
// SpriteFile class handles sprite file parsing and streaming sprites.
// SpriteFileWriter manages writing sprites into the output stream one by one,
// accumulating index information, and may therefore be suitable for a variety
// of situations.
//
//=============================================================================
#ifndef __AGS_CN_AC__SPRFILE_H
#define __AGS_CN_AC__SPRFILE_H

#include <memory>
#include <vector>
#include "core/types.h"
#include "util/error.h"
#include "util/geometry.h"
#include "util/stream.h"
#include "util/string.h"


namespace AGS
{
namespace Common
{

class Bitmap;

// TODO: research old version differences
enum SpriteFileVersion
{
    kSprfVersion_Undefined = 0,
    kSprfVersion_Uncompressed = 4,
    kSprfVersion_Compressed = 5,
    kSprfVersion_Last32bit = 6,
    kSprfVersion_64bit = 10,
    kSprfVersion_HighSpriteLimit = 11,
    kSprfVersion_StorageFormats = 12,
    kSprfVersion_Current = kSprfVersion_StorageFormats
};

enum SpriteIndexFileVersion
{
    kSpridxfVersion_Initial = 1,
    kSpridxfVersion_Last32bit = 2,
    kSpridxfVersion_64bit = 10,
    kSpridxfVersion_HighSpriteLimit = 11,
    kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit
};

// Instructions to how the sprites are allowed to be stored
enum SpriteStorage
{
    // When possible convert the sprite into another format for less disk space
    // e.g. save 16/32-bit images as 8-bit colormaps with palette
    kSprStore_OptimizeForSize = 0x01
};

// Format in which the sprite's pixel data is stored
enum SpriteFormat
{
    kSprFmt_Undefined       = 0, // undefined, or keep as-is
    // Encoded as a 8-bit colormap with palette of 24-bit RGB values
    kSprFmt_PaletteRgb888   = 32,
    // Encoded as a 8-bit colormap with palette of 32-bit ARGB values
    kSprFmt_PaletteArgb8888 = 33,
    // Encoded as a 8-bit colormap with palette of 16-bit RGB565 values
    kSprFmt_PaletteRgb565   = 34
};

enum SpriteCompression
{
    kSprCompress_None = 0,
    kSprCompress_RLE,
    kSprCompress_LZW,
    kSprCompress_Deflate
};

typedef int32_t sprkey_t;

// SpriteFileIndex contains sprite file's table of contents
struct SpriteFileIndex
{
    int SpriteFileIDCheck = 0; // tag matching sprite file and index file
    std::vector<int16_t> Widths;
    std::vector<int16_t> Heights;
    std::vector<soff_t>  Offsets;

    inline size_t GetCount() const { return Offsets.size(); }
    inline sprkey_t GetLastSlot() const { return (sprkey_t)GetCount() - 1; }
};

// Invidual sprite data header (as read from the file)
struct SpriteDatHeader
{
    int BPP = 0; // color depth (bytes per pixel); or input format
    SpriteFormat SFormat = kSprFmt_Undefined; // storage format
    uint32_t PalCount = 0; // palette length, if applicable to storage format
    SpriteCompression Compress = kSprCompress_None; // compression type
    int Width = 0; // sprite's width
    int Height = 0; // sprite's height

    SpriteDatHeader() = default;
    SpriteDatHeader(int bpp, SpriteFormat sformat = kSprFmt_Undefined,
        uint32_t pal_count = 0, SpriteCompression compress = kSprCompress_None,
        int w = 0, int h = 0) : BPP(bpp), SFormat(sformat), PalCount(pal_count),
          Compress(compress), Width(w), Height(h) {}
};


// SpriteFile opens a sprite file for reading, reports general information,
// and lets read sprites in any order.
class SpriteFile
{
public:
    // Standart sprite file and sprite index names
    static const String DefaultSpriteFileName;
    static const String DefaultSpriteIndexName;

    SpriteFile();
    // Loads sprite reference information and inits sprite stream
    HError      OpenFile(std::unique_ptr<Stream> &&sprite_file,
                         std::unique_ptr<Stream> &&index_file,
                         std::vector<Size> &metrics);
    // Closes stream; no reading will be possible unless opened again
    void        Close();

    int         GetStoreFlags() const;
    // Tells if bitmaps in the file are compressed
    SpriteCompression GetSpriteCompression() const;
    // Tells the highest known sprite index
    sprkey_t    GetTopmostSprite() const;

    // Loads sprite index file
    bool        LoadSpriteIndexFile(std::unique_ptr<Stream> &&index_file,
                                    int expectedFileID, soff_t spr_initial_offs,
                                    sprkey_t topmost, std::vector<Size> &metrics);

    // Loads an image data and creates a ready bitmap
    HError      LoadSprite(sprkey_t index, Bitmap *&sprite);
    // Loads a raw sprite element data into the buffer, stores header info separately
    HError      LoadRawData(sprkey_t index, SpriteDatHeader &hdr, std::vector<uint8_t> &data);

private:
    // Rebuilds sprite index from the main sprite file
    HError      RebuildSpriteIndex(Stream *in, sprkey_t topmost, std::vector<Size> &metrics);
    // Seek stream to sprite
    void        SeekToSprite(sprkey_t index);

    // Internal sprite reference
    struct SpriteRef
    {
        soff_t Offset = 0; // data offset
        size_t RawSize = 0; // file size of element, in bytes
        // TODO: RawSize is currently unused, due to incompleteness of spriteindex format
    };

    // Array of sprite references
    std::vector<SpriteRef> _spriteData;
    std::unique_ptr<Stream> _stream; // the sprite stream
    SpriteFileVersion _version = kSprfVersion_Current;
    int _storeFlags = 0; // storage flags, specify how sprites may be stored
    SpriteCompression _compress = kSprCompress_None; // sprite compression type
    sprkey_t _curPos; // current stream position (sprite slot)
};


// SpriteFileWriter class writes a sprite file in a requested format.
// Start using it by calling Begin, write ready bitmaps or copy raw sprite data
// over slot by slot, then call Finalize to let it close the format correctly.
class SpriteFileWriter
{
public:
    SpriteFileWriter(std::unique_ptr<Stream> &&out)
        : _out(std::move(out)) {}
    ~SpriteFileWriter() = default;

    // Get the sprite index, accumulated after write
    const SpriteFileIndex &GetIndex() const { return _index; }

    // Initializes new sprite file format;
    // store_flags are SpriteStorage;
    // optionally hint how many sprites will be written.
    void Begin(int store_flags, SpriteCompression compress, sprkey_t last_slot = -1);
    // Writes a bitmap into file, compressing if necessary
    void WriteBitmap(const Bitmap *image);
    // Writes an empty slot marker
    void WriteEmptySlot();
    // Writes a raw sprite data without any additional processing
    void WriteRawData(const SpriteDatHeader &hdr, const uint8_t *data, size_t data_sz);
    // Finalizes current format; no further writing is possible after this
    void Finalize();

private:
    // Writes prepared image data in a proper file format, following explicit data_bpp rule
    void WriteSpriteData(const SpriteDatHeader &hdr,
        const uint8_t *im_data, size_t im_data_sz, int im_bpp,
        const uint32_t palette[256]);

    std::unique_ptr<Stream> _out;
    int _storeFlags = 0;
    SpriteCompression _compress = kSprCompress_None;
    soff_t _lastSlotPos = -1; // last slot save position in file
    // sprite index accumulated on write for reporting back to user
    SpriteFileIndex _index;
    // compression buffer
    std::vector<uint8_t> _membuf;
};


// Saves all sprites to file; fills in index data for external use.
// TODO: refactor to be able to save main file and index file separately (separate function for gather data?)
// Accepts available sprites as pairs of bool and Bitmap pointer, where boolean value
// tells if sprite exists and Bitmap pointer may be null;
// If a sprite's bitmap is missing, it will try reading one from the input file stream.
int SaveSpriteFile(const String &save_to_file,
    const std::vector<std::pair<bool, Bitmap*>> &sprites,
    SpriteFile *read_from_file, // optional file to read missing sprites from
    int store_flags, SpriteCompression compress, SpriteFileIndex &index);
// Saves sprite index table in a separate file
int SaveSpriteIndex(const String &filename, const SpriteFileIndex &index);

} // namespace Common
} // namespace AGS

#endif // __AGS_CN_AC__SPRFILE_H
