#include "image.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>

namespace {

std::vector<uint8_t> readBytes(const std::string& path) {
    std::vector<uint8_t> b;
    FILE* f = std::fopen(path.c_str(), "rb");
    if (!f) return b;
    std::fseek(f, 0, SEEK_END);
    long n = std::ftell(f);
    std::fseek(f, 0, SEEK_SET);
    if (n > 0) { b.resize((size_t)n); if (std::fread(b.data(), 1, b.size(), f) != b.size()) b.clear(); }
    std::fclose(f);
    return b;
}

uint16_t u16(const uint8_t* p) { return (uint16_t)(p[0] | (p[1] << 8)); }
uint32_t u32(const uint8_t* p) { return (uint32_t)(p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24)); }
uint32_t argb(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
    return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}

// ---- TGA (uncompressed type 2 and RLE type 10; 24/32 bpp) ----
Image decodeTGA(const std::vector<uint8_t>& d) {
    Image im;
    if (d.size() < 18) return im;
    uint8_t idlen = d[0], type = d[2], bpp = d[16], desc = d[17];
    int w = u16(&d[12]), h = u16(&d[14]);
    if ((type != 2 && type != 10) || (bpp != 24 && bpp != 32) || w <= 0 || h <= 0) return im;
    int bytespp = bpp / 8;
    size_t off = 18 + idlen;
    std::vector<uint32_t> px((size_t)w * h);
    size_t pi = 0;
    auto put = [&](const uint8_t* p) {
        px[pi++] = argb(p[2], p[1], p[0], bytespp == 4 ? p[3] : 255); // TGA is BGR(A)
    };
    if (type == 2) {
        if (off + (size_t)w * h * bytespp > d.size()) return im;
        for (int i = 0; i < w * h; ++i) put(&d[off + (size_t)i * bytespp]);
    } else { // RLE
        while (pi < px.size() && off < d.size()) {
            uint8_t c = d[off++];
            int cnt = (c & 0x7f) + 1;
            if (c & 0x80) {
                if (off + bytespp > d.size()) break;
                for (int k = 0; k < cnt && pi < px.size(); ++k) put(&d[off]);
                off += bytespp;
            } else {
                for (int k = 0; k < cnt && pi < px.size(); ++k) { if (off + bytespp > d.size()) break; put(&d[off]); off += bytespp; }
            }
        }
    }
    // TGA is bottom-up unless descriptor bit 5 is set: flip to top-down if needed.
    im.w = w; im.h = h; im.argb.resize(px.size());
    bool topDown = (desc & 0x20) != 0;
    for (int y = 0; y < h; ++y) {
        int src = topDown ? y : (h - 1 - y);
        std::memcpy(&im.argb[(size_t)y * w], &px[(size_t)src * w], (size_t)w * 4);
    }
    im.ok = true;
    return im;
}

// ---- DIV TLV walk (shared by VTX): find first leaf with tag id, recursing containers ----
const uint8_t* findTag(const uint8_t* p, const uint8_t* end, uint16_t want, size_t& outLen) {
    while (p + 3 <= end) {
        uint16_t tw = u16(p); p += 2;
        uint16_t id = tw & 0x2fff;
        int lw = (tw & 0x8000) ? 4 : (tw & 0x4000) ? 2 : 1;
        if (p + lw > end) return nullptr;
        size_t len = (lw == 1) ? *p : (lw == 2) ? u16(p) : u32(p);
        p += lw;
        if (p + len > end) return nullptr;
        if (id == want) { outLen = len; return p; }
        if (id == 0x0003 || id == 0x0060) { // HEADER / image container
            size_t l2; const uint8_t* r = findTag(p, p + len, want, l2);
            if (r) { outLen = l2; return r; }
        }
        p += len;
    }
    return nullptr;
}

// ---- VTX (DIV-VTX2): size tag 0x2062 = (w,h) int32; pixels = trailing w*h*3 RGB ----
Image decodeVTX(const std::vector<uint8_t>& d) {
    Image im;
    if (d.size() < 16) return im;
    size_t len = 0;
    const uint8_t* sz = findTag(d.data() + 8, d.data() + d.size(), 0x2062, len);
    if (!sz || len < 8) return im;
    int w = (int)u32(sz), h = (int)u32(sz + 4);
    if (w <= 0 || h <= 0 || (size_t)w * h * 3 > d.size()) return im;
    const uint8_t* px = d.data() + d.size() - (size_t)w * h * 3; // trailing RGB block
    im.w = w; im.h = h; im.argb.resize((size_t)w * h);
    for (int i = 0; i < w * h; ++i) im.argb[i] = argb(px[i * 3], px[i * 3 + 1], px[i * 3 + 2]);
    im.ok = true;
    return im;
}

// ---- BSL (DIV-BSL2): a BIT-SLICED texture container ----
// Header (all int32 LE): w @0x08, h @0x0C, depth @0x10 (bits per slice, always
// 4), tableBytes @0x14 (directory length counted from the nEntries field),
// nEntries @0x18; then the entry directory {int32 recLen; int32 sliceType;
// char name[recLen-4]} x nEntries (names are authoring records only -- the
// runtime selects by the BMF BITSLICE tag, never by name); then w*h little-
// endian 32-bit texel WORDS.  Byte 0 of every word is pad; the other 3 bytes
// hold six 4-bit slices, nibble PAIR-SWAPPED (even slice = HIGH nibble):
//   slice 0 = bits 12-15   slice 1 = bits 8-11    slice 2 = bits 20-23
//   slice 3 = bits 16-19   slice 4 = bits 28-31   slice 5 = bits 24-27
// sliceType 7 = RGB444 (r=s5,g=s4,b=s3), 8 = RGBA4444 (+a=s2).
// Byte-exact with the shipping loader: nibble is returned <<4 (0x00..0xF0).
// Reference: VGCDivLoader::LoadBSLFile/getBSLData (DivLoader/VGCDivLoader.cpp
// :323-410), Division dsys PIMAGE.H dpiBSLTYPE_MONO0..5/RGB/RGBA; corpus-
// verified on all 66 archive BSLs (predicted populated slices match measured
// in every file).  BT_BSL=0 restores the old (WRONG) [pad,R,G,B] byte decode
// -- which overlaid 2-3 different gray slices as color channels = the rainbow
// "graffiti" mech skins.
uint8_t bslSlice(uint32_t word, int c) {
    int shift = (c + ((c + 1) % 2) * 2) * 4;                    // pair swap
    return (uint8_t)((word & (0xF0u << shift)) >> shift);       // 0x00..0xF0
}
Image decodeBSL(const std::vector<uint8_t>& d, int channel) {
    Image im;
    if (d.size() < 28) return im;
    int w = (int)u32(&d[8]), h = (int)u32(&d[12]);
    uint32_t tableBytes = u32(&d[20]);
    if (w <= 0 || h <= 0) return im;
    size_t need = (size_t)w * h * 4;
    size_t dataStart = 0x18 + (size_t)tableBytes;
    if (dataStart + need > d.size()) {
        if (need > d.size()) return im;
        dataStart = d.size() - need;    // damaged directory: fall back to trailing block
    }
    static const bool s_legacy = [] {
        const char* v = getenv("BT_BSL");
        return v != nullptr && v[0] == '0';
    }();
    if (channel < 0) channel = 0;       // BMF BITSLICE tag absent = slice 0
    const uint8_t* p = d.data() + dataStart;
    im.w = w; im.h = h; im.argb.resize((size_t)w * h);
    for (int i = 0; i < w * h; ++i) {
        const uint8_t* q = p + (size_t)i * 4;
        if (s_legacy) { im.argb[i] = argb(q[1], q[2], q[3]); continue; }
        uint32_t word = u32(q);
        if (channel <= 5) {             // 4-bit grayscale slice
            uint8_t g = bslSlice(word, channel);
            im.argb[i] = argb(g, g, g);
        } else {                        // 7 = RGB444, 8 = RGBA4444
            uint8_t a = (channel >= 8) ? bslSlice(word, 2) : 255;
            im.argb[i] = argb(bslSlice(word, 5), bslSlice(word, 4), bslSlice(word, 3), a);
        }
    }
    im.ok = true;
    return im;
}

std::string lowerExt(const std::string& path) {
    size_t dot = path.find_last_of('.');
    std::string e = (dot == std::string::npos) ? "" : path.substr(dot);
    for (char& c : e) c = (char)tolower((unsigned char)c);
    return e;
}

} // namespace

Image decodeImage(const std::string& path, int bslChannel) {
    std::vector<uint8_t> d = readBytes(path);
    if (d.size() < 16) return Image{};
    std::string e = lowerExt(path);
    if (e == ".tga") return decodeTGA(d);
    if (std::memcmp(d.data(), "DIV-VTX2", 8) == 0) return decodeVTX(d);
    if (std::memcmp(d.data(), "DIV-BSL2", 8) == 0) return decodeBSL(d, bslChannel);
    if (e == ".vtx") return decodeVTX(d);
    if (e == ".bsl") return decodeBSL(d, bslChannel);
    return Image{};
}
