// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// 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.

// bmp2raw.c -- Convert a 320x200x256 BMP to raw VGA splash format
//
// Output format:
//   768 bytes: palette (256 x 3 bytes RGB, 6-bit VGA values)
//   64000 bytes: pixels (320x200, top-to-bottom scanline order)
//
// Total output: 64768 bytes

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define VGA_W       320
#define VGA_H       200
#define PALETTE_ENTRIES 256
#define PIXEL_COUNT (VGA_W * VGA_H)

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "Usage: bmp2raw <input.bmp> <output.raw>\n");
        return 1;
    }

    FILE *fp = fopen(argv[1], "rb");

    if (!fp) {
        fprintf(stderr, "Cannot open %s\n", argv[1]);
        return 1;
    }

    // Read BMP header
    uint8_t hdr[54];

    if (fread(hdr, 1, 54, fp) != 54) {
        fprintf(stderr, "Bad BMP header\n");
        fclose(fp);
        return 1;
    }

    if (hdr[0] != 'B' || hdr[1] != 'M') {
        fprintf(stderr, "Not a BMP file\n");
        fclose(fp);
        return 1;
    }

    uint32_t pixelOffset = hdr[10] | (hdr[11] << 8) | (hdr[12] << 16) | (hdr[13] << 24);
    int32_t  width       = hdr[18] | (hdr[19] << 8) | (hdr[20] << 16) | (hdr[21] << 24);
    int32_t  height      = hdr[22] | (hdr[23] << 8) | (hdr[24] << 16) | (hdr[25] << 24);
    uint16_t bpp         = hdr[28] | (hdr[29] << 8);

    if (width != VGA_W || abs(height) != VGA_H || bpp != 8) {
        fprintf(stderr, "BMP must be %dx%d 8bpp (got %dx%d %dbpp)\n", VGA_W, VGA_H, width, abs(height), bpp);
        fclose(fp);
        return 1;
    }

    bool topDown = (height < 0);

    if (height < 0) {
        height = -height;
    }

    // Read palette (starts at offset 54 for BITMAPINFOHEADER, may vary)
    uint32_t dibSize = hdr[14] | (hdr[15] << 8) | (hdr[16] << 16) | (hdr[17] << 24);
    uint32_t palOffset = 14 + dibSize; // BMP file header (14) + DIB header

    // biClrUsed (DIB header offset 32 = file offset 46): an optimized 8bpp
    // BMP can have fewer than 256 entries, so a fixed 256-entry read would
    // pull pixel bytes into the palette tail.  0 means "all" (256).
    uint32_t clrUsed = hdr[46] | (hdr[47] << 8) | (hdr[48] << 16) | (hdr[49] << 24);

    if (clrUsed == 0 || clrUsed > PALETTE_ENTRIES) {
        clrUsed = PALETTE_ENTRIES;
    }

    fseek(fp, palOffset, SEEK_SET);

    uint8_t bmpPal[PALETTE_ENTRIES * 4];

    if (fread(bmpPal, 4, clrUsed, fp) != clrUsed) {
        fprintf(stderr, "Cannot read palette\n");
        fclose(fp);
        return 1;
    }

    // Convert palette: BMP is BGRA 8-bit, VGA is RGB 6-bit
    uint8_t vgaPal[PALETTE_ENTRIES * 3];

    for (uint32_t i = 0; i < clrUsed; i++) {
        vgaPal[i * 3 + 0] = bmpPal[i * 4 + 2] >> 2; // R
        vgaPal[i * 3 + 1] = bmpPal[i * 4 + 1] >> 2; // G
        vgaPal[i * 3 + 2] = bmpPal[i * 4 + 0] >> 2; // B
    }

    // Zero any palette entries the file didn't define.
    for (uint32_t i = clrUsed; i < PALETTE_ENTRIES; i++) {
        vgaPal[i * 3 + 0] = 0;
        vgaPal[i * 3 + 1] = 0;
        vgaPal[i * 3 + 2] = 0;
    }

    // Read pixel data
    fseek(fp, pixelOffset, SEEK_SET);

    // BMP rows are padded to 4-byte boundaries (320 is already aligned)
    uint8_t pixels[PIXEL_COUNT];

    if (topDown) {
        // Already top-to-bottom
        if (fread(pixels, 1, PIXEL_COUNT, fp) != PIXEL_COUNT) {
            fprintf(stderr, "Cannot read pixel data\n");
            fclose(fp);
            return 1;
        }
    } else {
        // Bottom-to-top — flip
        for (int y = VGA_H - 1; y >= 0; y--) {
            if (fread(pixels + y * VGA_W, 1, VGA_W, fp) != VGA_W) {
                fprintf(stderr, "Cannot read row %d\n", VGA_H - 1 - y);
                fclose(fp);
                return 1;
            }
        }
    }

    fclose(fp);

    // Write output
    FILE *out = fopen(argv[2], "wb");

    if (!out) {
        fprintf(stderr, "Cannot create %s\n", argv[2]);
        return 1;
    }

    fwrite(vgaPal, 1, sizeof(vgaPal), out);
    fwrite(pixels, 1, PIXEL_COUNT, out);
    fclose(out);

    printf("Converted %s -> %s (%d bytes)\n", argv[1], argv[2], (int)(sizeof(vgaPal) + PIXEL_COUNT));
    return 0;
}
