#include "n64dd.h"

#include "libc64/aprintf.h"
#include "attributes.h"
#include "array_count.h"
#include "color.h"

// Draws text to framebuffer
typedef struct struct_801CA704 {
    /* 0x00 */ PrintCallback callback;
    /* 0x04 */ void* charTexBuf;
    /* 0x08 */ u16 unk_08;
    /* 0x0A */ u16 posX;
    /* 0x0C */ u16 posY;
    /* 0x10 */ Color_RGBA8_u32 color;
    /* 0x14 */ u16 baseX;
    /* 0x16 */ u16 baseY;
    /* 0x18 */ u16 endX;
    /* 0x1A */ u16 endY;
    /* 0x1C */ u8 sjisPrevByte;
    /* 0x20 */ void* frameBuf;
    /* 0x24 */ u16 screenWidth;
    /* 0x26 */ u16 screenHeight;
} struct_801CA704;

// clang-format off
u32 D_801D8BE0[0x5F] = {
    0x00009D14, 0x00232A14, 0x00296314, 0x002F8A14, 0x00457E18, 0x0063CA14, 0x0084AA14, 0x00A03314,
    0x00A45E14, 0x00BB4E14, 0x00CA6514, 0x00D3770E, 0x00E33302, 0x00E78108, 0x00EB2102, 0x00EC6C14,
    0x01008A14, 0x01163A14, 0x01217A14, 0x01377A14, 0x014D8A14, 0x01638A14, 0x01798A14, 0x018F7A14,
    0x01A58A14, 0x01BB8A14, 0x01D1170E, 0x01D5490E, 0x01DF770E, 0x01EF740A, 0x01F9770E, 0x02097A14,
    0x021FDC16, 0x024DBA14, 0x026E7A14, 0x0284BA14, 0x02A59A14, 0x02C16A14, 0x02D26A14, 0x02E3BA14,
    0x03048A14, 0x031A1A14, 0x03206A14, 0x03318A14, 0x03476A14, 0x0358CA14, 0x03799A14, 0x0395CA14,
    0x03B67A14, 0x03CCCA14, 0x03ED8A14, 0x04037A14, 0x04196A14, 0x042A8A14, 0x0440AA14, 0x045CDA14,
    0x04839A14, 0x049F9A14, 0x04BB7A14, 0x04D14D14, 0x04DF5C14, 0x04F33D14, 0x05015216, 0x050681FA,
    0x050A9D14, 0x052D9810, 0x05449A14, 0x05609810, 0x05779A14, 0x05939810, 0x05AA5A14, 0x05BB9C10,
    0x05DC7A14, 0x05F22A14, 0x05F83D14, 0x06067A14, 0x061C2A14, 0x0622D810, 0x06427810, 0x06549810,
    0x066B9B10, 0x06899B10, 0x06A74810, 0x06B06810, 0x06BE5A14, 0x06CF770E, 0x06DF870E, 0x06EFB70E,
    0x0707670E, 0x07138A0E, 0x0729670E, 0x07356D14, 0x074A1F16, 0x07526D14, 0x07675216,
};
// clang-format on

// Loads character texture to buffer
s32 func_801CA0B0(s32 charCode, void* charTexBuf, int* dx, int* dy, int* cy) {
    s32 offset;
    OSPiHandle* handle;
    OSMesgQueue queue;
    OSMesg msgBuf[1];
    OSIoMesg mesg;

    handle = osDriveRomInit();
    if (charCode >= 0x20 && charCode < 0x7F) { // ASCII
        offset = LeoGetAAdr2(D_801D8BE0[charCode - 0x20], dx, dy, cy);
    } else if (charCode >= 0x8140) { // Shift-JIS
        offset = LeoGetKAdr(charCode);
        *dx = 16;
        *dy = 16;
        *cy = 11;
    } else {
        return -1;
    }

    osCreateMesgQueue(&queue, msgBuf, ARRAY_COUNT(msgBuf));

    // clang-format off
    mesg.hdr.retQueue = &queue; \
    mesg.devAddr = offset + DDROM_FONT_START; \
    mesg.dramAddr = charTexBuf; \
    mesg.size = 0x80; \
    mesg.hdr.pri = 0;
    // clang-format on

    handle->transferInfo.cmdType = 2;
    osEPiStartDma(handle, &mesg, 0);
    osRecvMesg(&queue, NULL, OS_MESG_BLOCK);

    return 0;
}

const u16 D_801D9390[16] = {
    0x0001, 0x1085, 0x2109, 0x318D, 0x4211, 0x5295, 0x6319, 0x739D,
    0x8C63, 0x9CE7, 0xAD6B, 0xBDEF, 0xCE73, 0xDEF7, 0xEF7B, 0xFFFF,
};

// Maps 4-bit intensity to a 16-bit color
u16 func_801CA1D4(u32 arg0) {
    return D_801D9390[arg0 % ARRAY_COUNT(D_801D9390)];
}

void func_801CA1F0(void* charTexBuf, s32 posX, s32 posY, s32 dx, s32 dy, s32 cy, void* frameBuf, s32 screenWidth) {
    s32 intensity;
    s32 x;
    s32 y;
    u8* src = charTexBuf;
    u16* dst = frameBuf;

    for (y = 0; y < dy; y++) {
        for (x = 0; x < dx; x++) {
            if (!(x & 1)) {
                intensity = *src >> 4;
            } else {
                intensity = *src & 0xF;
                src++;
            }
            dst[posX + x + ((posY + (11 - cy) + y) * screenWidth)] = func_801CA1D4(intensity);
        }
        if (dx & 1) {
            src++;
        }
    }
}

void func_801CA2F8(struct_801CA704* arg0, u32 r, u32 g, u32 b, u32 a) {
    arg0->color.r = r;
    arg0->color.g = g;
    arg0->color.b = b;
    arg0->color.a = a;
}

void func_801CA314(struct_801CA704* arg0, s32 arg1, s32 arg2) {
    arg0->posX = arg0->baseX + arg1;
    arg0->posY = arg0->baseY + arg2;
}

void func_801CA334(struct_801CA704* arg0, s32 baseX, s32 baseY, s32 endX, s32 endY) {
    arg0->baseX = baseX;
    arg0->baseY = baseY;
    arg0->endX = endX;
    arg0->endY = endY;
}

void func_801CA350(struct_801CA704* arg0, void* frameBuf, s32 screenWidth, s32 screenHeight) {
    arg0->frameBuf = (u8*)frameBuf + 0x20000000;
    arg0->screenWidth = screenWidth;
    arg0->screenHeight = screenHeight;
    func_801CA334(arg0, 0, 0, screenWidth - 1, screenHeight - 1);
}

void func_801CA3B4(struct_801CA704* arg0, void* charTexBuf, s32 arg2) {
    arg0->charTexBuf = (u8*)charTexBuf + 0x20000000;
    arg0->unk_08 = arg2;
}

void func_801CA3CC(struct_801CA704* arg0, char c) {
    s32 charCode;
    int dx;
    int dy;
    int cy;

    if (arg0->sjisPrevByte != 0) {
        charCode = (arg0->sjisPrevByte << 8) | c;
    } else {
        if (c >= 0x80 && c < 0x99) {
            arg0->sjisPrevByte = c;
            return;
        }
        charCode = c;
    }

    arg0->sjisPrevByte = 0;
    if (func_801CA0B0(charCode, arg0->charTexBuf, &dx, &dy, &cy) == 0) {
        if (arg0->posX + dx > arg0->endX) {
            arg0->posX = arg0->baseX;
            if (arg0->posY + 16 > arg0->endY) {
                arg0->posY = arg0->baseY;
            } else {
                arg0->posY += 16;
            }
        }
        func_801CA1F0(arg0->charTexBuf, arg0->posX, arg0->posY, dx, dy, cy, arg0->frameBuf, arg0->screenWidth);
        arg0->posX += (dx == 16 ? dx : dx + 2);
    }
}

void func_801CA4F4(struct_801CA704* arg0, char c) {
    if (c >= ' ' && c <= 0xFF) {
        func_801CA3CC(arg0, c);
    } else {
        switch (c) {
            case '\n':
                arg0->posY += 32;
                FALLTHROUGH;
            case '\r':
                arg0->posX = arg0->baseX;
                break;
            case '\t':
                do {
                    func_801CA3CC(arg0, ' ');
                } while ((arg0->posX - arg0->baseX) % 256);
                break;
            case '\0':
                break;
        }
    }
}

void func_801CA5BC(struct_801CA704* arg0, const char* str, s32 arg2, size_t count) {
    const char* s = str;
    s32 n = arg2 * count;

    while (n != 0) {
        func_801CA4F4(arg0, *s++);
        n--;
    }
}

void func_801CA618(struct_801CA704* arg0, const char* str) {
    while (*str != 0) {
        func_801CA4F4(arg0, *str++);
    }
}

void* func_801CA670(void* arg, const char* str, size_t count) {
    func_801CA5BC(arg, str, 1, count);
    return arg;
}

void func_801CA6A0(struct_801CA704* arg0) {
    arg0->callback = &func_801CA670;
    arg0->posX = 0;
    arg0->posY = 0;
    arg0->baseX = 0;
    arg0->baseY = 0;
    arg0->endX = 0;
    arg0->endY = 0;
    arg0->color.rgba = 0;
    arg0->sjisPrevByte = 0;
    arg0->charTexBuf = NULL;
}

void func_801CA6D8(struct_801CA704* arg0) {
}

s32 func_801CA6E4(struct_801CA704* arg0, const char* fmt, va_list args) {
    return PrintUtils_VPrintf(&arg0->callback, fmt, args);
}

s32 func_801CA704(struct_801CA704* arg0, const char* fmt, ...) {
    s32 ret;
    va_list args;

    va_start(args, fmt);
    ret = func_801CA6E4(arg0, fmt, args);
    va_end(args);

    return ret;
}
