/*
Copyright (C) 2023 Sneed, ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "msu1emu.h"
#include "../gblhdr.h"
#include "../ui.h"
#include "cfg.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Data
u1 MSU_StatusRead;
u4 MSU_Data_SeekPort;
u4 MSU_Data_Addr;
u1* MSU_DATA = NULL;

// DSP
short* TRACK_DATA = NULL;
u2 MSU_Track;
u2 MSU_Resume_Track = -1;
u1 MSU_AudioVolume;
u1 MSU_StateControl;
int MSU_Rate_Add = 0;
int MSU_Track_Position = 0;
int MSU_Resume_Track_Position = 0;
int MSU_Loop_Point = 0;
int MSU_Track_Length = 0;
char MSU_BasePath[4096];

// Prepare read registers
void initMSU1regsRead(void)
{
    REGPTR(0x2000) = msustatusread;
    REGPTR(0x2001) = msudataread;
    REGPTR(0x2002) = msuid1;
    REGPTR(0x2003) = msuid2;
    REGPTR(0x2004) = msuid3;
    REGPTR(0x2005) = msuid4;
    REGPTR(0x2006) = msuid5;
    REGPTR(0x2007) = msuid6;
}

// Prepare write registers
void initMSU1regsWrite(void)
{
    REGPTW(0x2000) = msudataseek0;
    REGPTW(0x2001) = msudataseek1;
    REGPTW(0x2002) = msudataseek2;
    REGPTW(0x2003) = msudataseek3;
    REGPTW(0x2004) = msu1track0;
    REGPTW(0x2005) = msu1track1;
    REGPTW(0x2006) = msu1volume;
    REGPTW(0x2007) = msu1statecontrol;
}

// Read the MSU binary file (.msu file)
int readMSU()
{
    // Cleanup
    if (MSU_DATA) {
        free(MSU_DATA);
        MSU_DATA = NULL;
    }
    MSU_Data_SeekPort = 0;
    MSU_Data_Addr = 0;
    MSU_StatusRead = MSU_REVISION;
    MSU_StatusRead &= ~MSU_STATUS_DATA_BUSY; // Clear data busy bit
    MSU_AudioVolume = 0xFF;

    // Get Filename
    size_t len = strlen(MSU_BasePath);
    if (len >= 4) {
        MSU_BasePath[len - 4] = 0;
    }

    char MSUBinaryFile[4120];
    snprintf(MSUBinaryFile, sizeof(MSUBinaryFile), "%s.msu", MSU_BasePath);

    // Open binary
    FILE* MSUBinary;
    long filelen;
    MSUBinary = fopen(MSUBinaryFile, "rb");
    if (MSUBinary) {
#ifdef DEBUG
        printf("Found MSU-1 binary!\n");
#endif
        fseek(MSUBinary, 0, SEEK_END);
        filelen = ftell(MSUBinary);
        rewind(MSUBinary);

        MSU_DATA = (u1*)malloc(filelen);
        if (MSU_DATA) {
            IGNORE_RESULT(fread(MSU_DATA, filelen, 1, MSUBinary));
            fclose(MSUBinary);
            return 1;
        } else {
            fclose(MSUBinary);
            printf("Not enough space in memory for MSU-1.\n");
        }
    }
    return 0;
}

void MSU1HandleTrackChange()
{
    // Writes have no effect if audio busy bit set
    if (MSU_StatusRead & MSU_STATUS_AUDIO_BUSY) {
        return;
    }

    // Begin track change, set audio busy bit
    MSU_StatusRead |= MSU_STATUS_AUDIO_BUSY;

    // Set error bit in case track read fails
    MSU_StatusRead |= MSU_STATUS_ERROR;

    // If requested track matches resume track, restore track position
    if (MSU_Track == MSU_Resume_Track) {
        MSU_Track_Position = MSU_Resume_Track_Position;
        // Reset resume variables
        MSU_Resume_Track = -1;
        MSU_Resume_Track_Position = 0;
    } else {
        MSU_Track_Position = 0;
    }

    MSU_Rate_Add = 0;

    // Requested a track.
    MSU_Track_Length = 0;
    MSU_StatusRead &= ~(MSU_STATUS_PLAY + MSU_STATUS_LOOP); // Turn off playing bits

    // Request new track
#ifdef DEBUG
    printf("Requesting track %hu to be played..\n", MSU_Track);
#endif
    if (TRACK_DATA) {
        free(TRACK_DATA);
        TRACK_DATA = NULL;
    }

    // Print track file
    char MSUTrackFile[4120];
    snprintf(MSUTrackFile, sizeof(MSUTrackFile), "%s-%hu.pcm", MSU_BasePath, MSU_Track);

    // Open track
    FILE* TrackFileReader;
    long filelen;
    TrackFileReader = fopen(MSUTrackFile, "rb");
    if (TrackFileReader) {
        fseek(TrackFileReader, 0, SEEK_END);
        filelen = ftell(TrackFileReader);

        // remove header
        filelen -= 8;

        // initialize track data
        TRACK_DATA = (short*)malloc(filelen);
        if (TRACK_DATA) {
            fseek(TrackFileReader, 4, SEEK_SET);
            IGNORE_RESULT(fread(&MSU_Loop_Point, sizeof(int), 1, TrackFileReader));
            IGNORE_RESULT(fread(TRACK_DATA, filelen, 1, TrackFileReader));
            fclose(TrackFileReader);
#ifdef DEBUG
            printf("Succesfully loaded Track %lu with length %lu\n", MSU_Track, filelen);
#endif
            MSU_Track_Length = filelen / 2;
        } else {
            fclose(TrackFileReader);
            // Clear audio busy bit
            MSU_StatusRead &= ~MSU_STATUS_AUDIO_BUSY;
            printf("Not enough space in memory for MSU-1.\n");
            return;
        }
    } else {
        // Clear audio busy bit
        MSU_StatusRead &= ~MSU_STATUS_AUDIO_BUSY;
        return;
    }
    // Audio read success, clear error bit
    MSU_StatusRead &= ~MSU_STATUS_ERROR;
    // Track change complete, clear audio busy bit
    MSU_StatusRead &= ~MSU_STATUS_AUDIO_BUSY;
}

// Handle control register bits
void MSU1HandleControlBits()
{
    // Writes have no effect if audio busy bit or error bit set
    if (MSU_StatusRead & (MSU_STATUS_AUDIO_BUSY | MSU_STATUS_ERROR)) {
        return;
    }

    // Check for and set up resume
    if (MSU_StateControl == MSU_CONTROL_RESUME) {
        MSU_Resume_Track = MSU_Track;
        MSU_Resume_Track_Position = MSU_Track_Position;
    }

    // Set status bits based on control register bits
    MSU_StatusRead = (MSU_StatusRead & ~(MSU_STATUS_PLAY + MSU_STATUS_LOOP)) | ((MSU_StateControl & (MSU_CONTROL_PLAY + MSU_CONTROL_LOOP)) << 4);
#ifdef DEBUG
    printf("Status bits new: %hhu\n", MSU_StatusRead);
#endif
}

// Mix MSU1 audio signal with DSP
void mixMSU1Audio(int* start, int* end, int rate)
{
    // Play
    if ((MSU_StatusRead & (MSU_STATUS_PLAY & ~MSU_STATUS_AUDIO_BUSY)) && MSU_Track_Length > 0) {
        MSU_StatusRead |= MSU_STATUS_AUDIO_BUSY; // Set audio busy flag
        for (; start < end; start++) {
            // Check if the pointer of the track is valid.
            if (MSU_Track_Position < MSU_Track_Length) {
                *start += (TRACK_DATA[MSU_Track_Position] * MSU_AudioVolume * MusicVol) / 0x4000;

                // Stereo Mixer
                if (StereoSound) {
                    start++; // advance to the right-channel sample
                    *start += (TRACK_DATA[MSU_Track_Position + 1] * MSU_AudioVolume * MusicVol) / 0x4000;
                }

                // Take care of incrementing the pointer
                MSU_Rate_Add += 44100;
                while (MSU_Rate_Add >= rate) {
                    MSU_Track_Position += 2;
                    MSU_Rate_Add -= rate;
                }
            }

            // Check if we should repeat
            if (MSU_Track_Position >= MSU_Track_Length) {
                if (MSU_StatusRead & MSU_STATUS_LOOP) {
                    MSU_Track_Position = MSU_Loop_Point * 2;
                } else {
                    // Track finished and not looping, clear play bit
                    MSU_StatusRead &= ~MSU_STATUS_PLAY;
                }
            }
        }
        MSU_StatusRead &= ~MSU_STATUS_AUDIO_BUSY; // Clear audio busy flag
    }
}