#include "z64voice.h"
#include "stdbool.h"
#include "string.h"
#include "padmgr.h"

// internal voice functions
char* func_801A5A1C(s8* words);

typedef struct {
    /* 0x00 */ OSVoiceDictionary* dict;
    /* 0x04 */ s8 mode;
    /* 0x08 */ OSVoiceData* data;
    /* 0x0C */ u16 distance;
    /* 0x0E */ u16 answerNum;
    /* 0x10 */ u16 warning;
    /* 0x12 */ u16 voiceLevel;
    /* 0x14 */ u16 voiceRelLevel;
} OSVoiceContext; // size = 0x18

typedef enum VoiceMode {
    /* 0 */ VOICE_MODE_0,
    /* 1 */ VOICE_MODE_1,
    /* 2 */ VOICE_MODE_2
} VoiceMode;

// BSS
OSVoiceContext sVoiceContext;
OSVoiceHandle gVoiceHandle;
OSVoiceData D_801FD5C8; // Intermediate Voice Data during processsing?
OSVoiceData D_801FD5E8; // Best Match Voice Data?
u8 sVoiceMaskPattern[8];
char D_801FD610[256];

// Data
s8 D_801D8E50[96][3] = {
    "aa", "AA", "ii", "II", "uu", "UU", "ee", "EE", "oo", "OO", "KA", "GA", "KI", "GI", "KU", "GU",
    "KE", "GE", "KO", "GO", "SA", "ZA", "SI", "ZI", "SU", "ZU", "SE", "ZE", "SO", "ZO", "TA", "DA",
    "TI", "DI", "tu", "TU", "DU", "TE", "DE", "TO", "DO", "NA", "NI", "NU", "NE", "NO", "HA", "BA",
    "PA", "HI", "BI", "PI", "HU", "BU", "PU", "HE", "BE", "PE", "HO", "BO", "PO", "MA", "MI", "MU",
    "ME", "MO", "ya", "YA", "yu", "YU", "yo", "YO", "RA", "RI", "RU", "RE", "RO", "wa", "WA", "WI",
    "WE", "WO", "NN", "VU", "ka", "ke", "",   "",   "",   "",   "",   "",   "",   "",   "",   "",
};

s8 D_801D8F70[96][3] = {
    "aa", "AA", "ii", "II", "uu", "UU", "ee", "EE", "oo", "OO", "KA", "GA", "KI", "GI", "KU", "GU",
    "KE", "GE", "KO", "GO", "SA", "ZA", "SI", "ZI", "SU", "ZU", "SE", "ZE", "SO", "ZO", "TA", "DA",
    "TI", "DI", "tu", "TU", "DU", "TE", "DE", "TO", "DO", "NA", "NI", "NU", "NE", "NO", "HA", "BA",
    "PA", "HI", "BI", "PI", "HU", "BU", "PU", "HE", "BE", "PE", "HO", "BO", "PO", "MA", "MI", "  ",
    "MU", "ME", "MO", "ya", "YA", "yu", "YU", "yo", "YO", "RA", "RI", "RU", "RE", "RO", "wa", "WA",
    "WI", "WE", "WO", "NN", "VU", "ka", "ke", "",   "",   "",   "",   "",   "",   "",   "",   "",
};

const char D_801E0EC0[] = "Error %d\n";
const char D_801E0ECC[] = "NAI_VRS:osVoiceClearDictionary %d\n";
const char D_801E0EF0[] = "NAI_VRS:dict error! (%d-%d %s)\n";
const char D_801E0F10[] = "NAI_VRS:Ina_SetVruGain Error!\n";
const char D_801E0F30[] = "NAI_VRS:mask on  %d\n";
const char D_801E0F48[] = "NAI_VRS:mask off %d\n";
const char D_801E0F60[] = "NAI_VRS:answer No.:%d Dist:%d Warn:%04X Level:%5d SN:%5d (Num:%d)\n";
const char D_801E0FA4[] = "NAI_VRS:error !! (ANS_MAX:%d DIST:%d WARNING:%04X LEVEL:%5d SN:%5d)\n";

s32 func_801A51F0(s32 errorCode) {
    switch (errorCode) {
        case CONT_ERR_NO_CONTROLLER:
        case CONT_ERR_CONTRFAIL:
        case CONT_ERR_INVALID:
        case CONT_ERR_DEVICE:
        case CONT_ERR_VOICE_MEMORY:
        case CONT_ERR_VOICE_WORD:
        case CONT_ERR_VOICE_NO_RESPONSE:
            return -1;

        default:
            return 0;
    }
}

s32 func_801A5228(OSVoiceDictionary* dict) {
    OSMesgQueue* serialEventQueue;
    s32 errorCode;
    u8 numWords;
    u8 i;

    sVoiceContext.mode = VOICE_MODE_0;
    sVoiceContext.data = NULL;
    sVoiceContext.distance = 1000;
    sVoiceContext.answerNum = 5;
    sVoiceContext.warning = 0;
    sVoiceContext.dict = dict;

    numWords = dict->numWords;

    serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
    errorCode = osVoiceClearDictionary(&gVoiceHandle, numWords);
    PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

    if (errorCode != 0) {
        return errorCode;
    }

    for (i = 0; i < (((numWords - 1) / 8) + 1); i++) {
        sVoiceMaskPattern[i] = 0;
    }

    for (i = 0; i < numWords; i++) {
        serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
        errorCode = osVoiceSetWord(&gVoiceHandle, (u8*)&dict->words[i]);
        PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

        if (func_801A51F0(errorCode) != 0) {
            func_801A5A1C((s8*)&dict->words[i]);
        }
    }

    return errorCode;
}

OSVoiceData* func_801A5390(void) {
    OSVoiceData* voiceData;
    OSMesgQueue* serialEventQueue;

    voiceData = sVoiceContext.data;
    sVoiceContext.data = NULL;

    serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
    osVoiceStartReadData(&gVoiceHandle);
    PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

    return voiceData;
}

// Unused
OSVoiceDictionary* AudioVoice_GetVoiceDict(void) {
    return sVoiceContext.dict;
}

void func_801A53E8(u16 distance, u16 answerNum, u16 warning, u16 voiceLevel, u16 voiceRelLevel) {
    sVoiceContext.distance = distance;
    sVoiceContext.answerNum = answerNum;
    sVoiceContext.warning = warning;
    sVoiceContext.voiceLevel = voiceLevel;
    sVoiceContext.voiceRelLevel = voiceRelLevel;
}

// Unused
void func_801A541C(s32 analog, s32 digital) {
    s32 errorCode;
    OSMesgQueue* serialEventQueue;

    if (sVoiceContext.dict != NULL) {
        serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
        errorCode = osVoiceControlGain(&gVoiceHandle, analog, digital);
        PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

        if (errorCode != 0) {
            func_801A51F0(errorCode);
        }
    }
}

// Unused
s32 func_801A5488(u8* word) {
    s32 errorCode;
    OSMesgQueue* serialEventQueue;

    serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
    errorCode = osVoiceCheckWord(word);
    PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

    return errorCode;
}

u8* AudioVoice_GetVoiceMaskPattern(void) {
    return sVoiceMaskPattern;
}

s32 AudioVoice_InitWordImplAlt(u16 wordId) {
    s32 errorCode;
    u8 stopReadingData = true;
    u8 numWords;
    u8 i;
    OSMesgQueue* serialEventQueue;

    if (sVoiceContext.dict != NULL) {
        numWords = sVoiceContext.dict->numWords;
    } else {
        numWords = 20;
        stopReadingData = false;
    }

    if (wordId == VOICE_WORD_ID_NONE) {
        for (i = 0; i < numWords; i++) {
            sVoiceMaskPattern[i / 8] |= 1 << (i % 8);
        }
    } else {
        if (sVoiceMaskPattern[wordId / 8] & (1 << (wordId % 8))) {
            stopReadingData = false;
        } else {
            sVoiceMaskPattern[wordId / 8] |= (1 << (wordId % 8));
        }
    }

    if (stopReadingData) {
        serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
        errorCode = osVoiceStopReadData(&gVoiceHandle);
        PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

        if ((errorCode == 0) || (sVoiceContext.mode == VOICE_MODE_0)) {
            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            errorCode = osVoiceMaskDictionary(&gVoiceHandle, sVoiceMaskPattern, ((numWords - 1) / 8) + 1);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);
        }

        sVoiceContext.mode = VOICE_MODE_0;
    }

    return errorCode;
}

s32 AudioVoice_InitWordImpl(u16 wordId) {
    s32 errorCode;
    u8 stopReadingData = true;
    u8 numWords;
    u8 i;
    OSMesgQueue* serialEventQueue;

    if (sVoiceContext.dict != NULL) {
        numWords = sVoiceContext.dict->numWords;
    } else {
        numWords = 20;
        stopReadingData = false;
    }

    if (wordId == VOICE_WORD_ID_NONE) {
        for (i = 0; i < (((numWords - 1) / 8) + 1); i++) {
            sVoiceMaskPattern[i] = 0;
        }
    } else {
        if (!(sVoiceMaskPattern[wordId / 8] & (1 << (wordId % 8)))) {
            stopReadingData = false;
        } else {
            sVoiceMaskPattern[wordId / 8] &= (1 << (wordId % 8)) ^ 0xFF;
        }
    }

    if (stopReadingData) {
        serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
        errorCode = osVoiceStopReadData(&gVoiceHandle);
        PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

        if ((errorCode == 0) || (sVoiceContext.mode == VOICE_MODE_0)) {
            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            errorCode = osVoiceMaskDictionary(&gVoiceHandle, sVoiceMaskPattern, ((numWords - 1) / 8) + 1);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);
        }

        sVoiceContext.mode = VOICE_MODE_0;
    }

    return errorCode;
}

s32 func_801A5808(void) {
    s32 errorCode = 0;
    s32 pad;
    OSMesgQueue* serialEventQueue;

    switch (sVoiceContext.mode) {
        case VOICE_MODE_0:
            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            errorCode = osVoiceStartReadData(&gVoiceHandle);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

            sVoiceContext.mode = VOICE_MODE_1;
            break;

        case VOICE_MODE_1:
            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            errorCode = osVoiceGetReadData(&gVoiceHandle, &D_801FD5C8);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

            if (func_801A51F0(errorCode) == 0) {
                switch (gVoiceHandle.cmd_status) {
                    case VOICE_STATUS_READY:
                        sVoiceContext.mode = VOICE_MODE_2;
                        break;

                    case VOICE_STATUS_START:
                    case 2:
                    case VOICE_STATUS_CANCEL:
                    case 4:
                    case VOICE_STATUS_BUSY:
                    case 6:
                        break;

                    case VOICE_STATUS_END:
                        sVoiceContext.mode = VOICE_MODE_2;
                        break;

                    default:
                        break;
                }
            }
            break;

        case VOICE_MODE_2:
            if (((D_801FD5C8.warning & sVoiceContext.warning) == 0) &&
                (sVoiceContext.answerNum >= D_801FD5C8.answer_num) &&
                (sVoiceContext.distance >= D_801FD5C8.distance[0]) &&
                (D_801FD5C8.voice_level >= sVoiceContext.voiceLevel) &&
                (D_801FD5C8.voice_sn >= sVoiceContext.voiceRelLevel)) {
                D_801FD5E8 = D_801FD5C8;
                sVoiceContext.data = &D_801FD5E8;
            }

            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            osVoiceStopReadData(&gVoiceHandle);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

            serialEventQueue = PadMgr_VoiceAcquireSerialEventQueue();
            errorCode = osVoiceStartReadData(&gVoiceHandle);
            PadMgr_VoiceReleaseSerialEventQueue(serialEventQueue);

            sVoiceContext.mode = VOICE_MODE_1;
            break;

        default:
            break;
    }

    return func_801A51F0(errorCode);
}

// Unused
void AudioVoice_ResetData(void) {
    sVoiceContext.dict = NULL;
}

char* func_801A5A1C(s8* words) {
    u8 i;
    u8 j;
    u8 numSyllables = strlen((const char*)words); // technically twice the num of syllables
    u8 syllable[2];

    for (j = 0, i = 0; i < numSyllables; i += 2) {
        syllable[0] = words[i];
        syllable[1] = words[i + 1];

        if (syllable[0] == 0x83) {
            D_801FD610[j++] = D_801D8F70[syllable[1] - 0x40][0];
            D_801FD610[j++] = D_801D8F70[syllable[1] - 0x40][1];
        } else if (syllable[0] == 0x82) {
            D_801FD610[j++] = D_801D8E50[syllable[1] - 0x9F][0];
            D_801FD610[j++] = D_801D8E50[syllable[1] - 0x9F][1];
        } else if ((syllable[0] == 0x81) && (syllable[1] == 0x5B)) {
            D_801FD610[j++] = '-';
            D_801FD610[j++] = '-';
        } else {
            D_801FD610[j++] = ' ';
            D_801FD610[j++] = ' ';
        }
    }

    D_801FD610[i] = '\0';

    return D_801FD610;
}
