/*==============================================================================
API Recording Example
Copyright (c), Firelight Technologies Pty, Ltd 2012-2021.

This example shows recording and playback functionality, allowing the user to
trigger some sounds and then play back what they have recorded.  The provided
functionality is intended to assist in debugging.
==============================================================================*/
#include "fmod_studio.hpp"
#include "fmod.hpp"
#include "common.h"

const int SCREEN_WIDTH = NUM_COLUMNS;
const int SCREEN_HEIGHT = 10;

int currentScreenPosition = -1;
char screenBuffer[(SCREEN_WIDTH + 1) * SCREEN_HEIGHT + 1] = {0};

void initializeScreenBuffer();
void updateScreenPosition(const FMOD_VECTOR& worldPosition);

static const char* RECORD_FILENAME = "playback.cmd.txt";

enum State
{
    State_Selection,
    State_Record,
    State_Playback,
    State_Quit
};

State executeSelection(FMOD::Studio::System* system);
State executeRecord(FMOD::Studio::System* system);
State executePlayback(FMOD::Studio::System* system);
    
int FMOD_Main()
{
    void *extraDriverData = 0;
    Common_Init(&extraDriverData);

    FMOD::Studio::System* system = NULL;
    ERRCHECK( FMOD::Studio::System::create(&system) );

    // The example Studio project is authored for 5.1 sound, so set up the system output mode to match
    FMOD::System* coreSystem = NULL;
    ERRCHECK( system->getCoreSystem(&coreSystem) );
    ERRCHECK( coreSystem->setSoftwareFormat(0, FMOD_SPEAKERMODE_5POINT1, 0) );

    ERRCHECK( system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData) );

    State state = State_Selection;
    while (state != State_Quit)
    {
        switch (state)
        {
            case State_Selection:
                state = executeSelection(system);
                break;
            case State_Record:
                state = executeRecord(system);
                break;
            case State_Playback:
                state = executePlayback(system);
                break;
            case State_Quit:
                break;
        };
    };

    ERRCHECK( system->release() );

    Common_Close();

    return 0;
}

// Show the main selection menu
State executeSelection(FMOD::Studio::System* system)
{
    for (;;)
    {
        Common_Update();

        if (Common_BtnPress(BTN_ACTION1))
        {
            return State_Record;
        }
        if (Common_BtnPress(BTN_ACTION2))
        {
            return State_Playback;
        }
        if (Common_BtnPress(BTN_QUIT))
        {
            return State_Quit;
        }

        ERRCHECK( system->update() );
        
        Common_Draw("==================================================");
        Common_Draw("Recording and playback example.");
        Common_Draw("Copyright (c) Firelight Technologies 2012-2021.");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Waiting to start recording");
        Common_Draw("");
        Common_Draw("Press %s to start recording", Common_BtnStr(BTN_ACTION1));
        Common_Draw("Press %s to play back recording", Common_BtnStr(BTN_ACTION2));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
        Common_Draw("");
        Common_Sleep(50);
    }
}

// Start recording, load banks and then let the user trigger some sounds
State executeRecord(FMOD::Studio::System* system)
{
    FMOD::Studio::Bank* masterBank = NULL;
    ERRCHECK( system->loadBankFile(Common_MediaPath("Master.bank"), FMOD_STUDIO_LOAD_BANK_NONBLOCKING, &masterBank) );

    FMOD::Studio::Bank* stringsBank = NULL;
    ERRCHECK( system->loadBankFile(Common_MediaPath("Master.strings.bank"), FMOD_STUDIO_LOAD_BANK_NONBLOCKING, &stringsBank) );

    FMOD::Studio::Bank* vehiclesBank = NULL;
    ERRCHECK( system->loadBankFile(Common_MediaPath("Vehicles.bank"), FMOD_STUDIO_LOAD_BANK_NONBLOCKING, &vehiclesBank) );

    FMOD::Studio::Bank* sfxBank = NULL;
    ERRCHECK( system->loadBankFile(Common_MediaPath("SFX.bank"), FMOD_STUDIO_LOAD_BANK_NONBLOCKING, &sfxBank) );

    // Wait for banks to load
    ERRCHECK( system->flushCommands() );

    // Start recording commands - it will also record which banks we have already loaded by now
    ERRCHECK( system->startCommandCapture(Common_WritePath(RECORD_FILENAME), FMOD_STUDIO_COMMANDCAPTURE_NORMAL) );

    FMOD_GUID explosionID = {0};
    ERRCHECK( system->lookupID("event:/Weapons/Explosion", &explosionID) );

    FMOD::Studio::EventDescription* engineDescription = NULL;
    ERRCHECK( system->getEvent("event:/Vehicles/Ride-on Mower", &engineDescription) );

    FMOD::Studio::EventInstance* engineInstance = NULL;
    ERRCHECK( engineDescription->createInstance(&engineInstance) );

    ERRCHECK( engineInstance->setParameterByName("RPM", 650.0f) );
    ERRCHECK( engineInstance->start() );

    // Position the listener at the origin
    FMOD_3D_ATTRIBUTES attributes = { { 0 } };
    attributes.forward.z = 1.0f;
    attributes.up.y = 1.0f;
    ERRCHECK( system->setListenerAttributes(0, &attributes) );

    // Position the event 2 units in front of the listener
    attributes.position.z = 2.0f;
    ERRCHECK( engineInstance->set3DAttributes(&attributes) );

    initializeScreenBuffer();
    
    bool wantQuit = false;

    for (;;)
    {
        Common_Update();

        if (Common_BtnPress(BTN_MORE))
        {
            break;
        }

        if (Common_BtnPress(BTN_QUIT))
        {
            wantQuit = true;
            break;
        }

        if (Common_BtnPress(BTN_ACTION1))
        {
            // One-shot event
            FMOD::Studio::EventDescription* eventDescription = NULL;
            ERRCHECK( system->getEventByID(&explosionID, &eventDescription) );

            FMOD::Studio::EventInstance* eventInstance = NULL;
            ERRCHECK( eventDescription->createInstance(&eventInstance) );
            for (int i=0; i<10; ++i)
            {
                ERRCHECK( eventInstance->setVolume(i / 10.0f) );
            }

            ERRCHECK( eventInstance->start() );

            // Release will clean up the instance when it completes
            ERRCHECK( eventInstance->release() );
        }

        if (Common_BtnPress(BTN_LEFT))
        {
            attributes.position.x -= 1.0f;
            ERRCHECK( engineInstance->set3DAttributes(&attributes) );
        }
        
        if (Common_BtnPress(BTN_RIGHT))
        {
            attributes.position.x += 1.0f;
            ERRCHECK( engineInstance->set3DAttributes(&attributes) );
        }
        
        if (Common_BtnPress(BTN_UP))
        {
            attributes.position.z += 1.0f;
            ERRCHECK( engineInstance->set3DAttributes(&attributes) );
        }
        
        if (Common_BtnPress(BTN_DOWN))
        {
            attributes.position.z -= 1.0f;
            ERRCHECK( engineInstance->set3DAttributes(&attributes) );
        }

        if (Common_BtnPress(BTN_MORE))
        {
            break;
        }
        if (Common_BtnPress(BTN_QUIT))
        {
            wantQuit = true;
            break;
        }

        ERRCHECK(system->update());
        
        updateScreenPosition(attributes.position);
        Common_Draw("==================================================");
        Common_Draw("Recording and playback example.");
        Common_Draw("Copyright (c) Firelight Technologies 2012-2021.");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Recording!");
        Common_Draw("");
        Common_Draw(screenBuffer);
        Common_Draw("");
        Common_Draw("Press %s to finish recording", Common_BtnStr(BTN_MORE));
        Common_Draw("Press %s to play a one-shot", Common_BtnStr(BTN_ACTION1));
        Common_Draw("Use the arrow keys (%s, %s, %s, %s) to control the engine position", 
            Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT), Common_BtnStr(BTN_UP), Common_BtnStr(BTN_DOWN));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));

        Common_Sleep(50);
    }

    // Unload all the banks
    ERRCHECK( masterBank->unload() );
    ERRCHECK( stringsBank->unload() );
    ERRCHECK( vehiclesBank->unload() );
    ERRCHECK( sfxBank->unload() );

    // Finish recording
    ERRCHECK( system->flushCommands() );
    ERRCHECK( system->stopCommandCapture() );

    return (wantQuit ? State_Quit : State_Selection);
}

// Play back a previously recorded file
State executePlayback(FMOD::Studio::System* system)
{
    FMOD::Studio::CommandReplay* replay;
    ERRCHECK( system->loadCommandReplay(Common_WritePath(RECORD_FILENAME), FMOD_STUDIO_COMMANDREPLAY_NORMAL, &replay));
    int commandCount;
    ERRCHECK(replay->getCommandCount(&commandCount));
    float totalTime;
    ERRCHECK(replay->getLength(&totalTime));
    ERRCHECK(replay->start());
    ERRCHECK(system->update());

    for (;;)
    {
        Common_Update();

        if (Common_BtnPress(BTN_QUIT))
        {
            break;
        }

        if (Common_BtnPress(BTN_MORE))
        {
            bool paused;
            ERRCHECK(replay->getPaused(&paused));
            ERRCHECK(replay->setPaused(!paused));
        }

        FMOD_STUDIO_PLAYBACK_STATE state;
        ERRCHECK(replay->getPlaybackState(&state));
        if (state == FMOD_STUDIO_PLAYBACK_STOPPED)
        {
            break;
        }

        int currentIndex;
        float currentTime;
        ERRCHECK(replay->getCurrentCommand(&currentIndex, &currentTime));

        ERRCHECK(system->update());
        
        Common_Draw("==================================================");
        Common_Draw("Recording and playback example.");
        Common_Draw("Copyright (c) Firelight Technologies 2012-2021.");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Playing back commands:");
        Common_Draw("Command = %d / %d\n", currentIndex, commandCount);
        Common_Draw("Time = %.3f / %.3f\n", currentTime, totalTime);
        Common_Draw("");
        Common_Draw("Press %s to pause/unpause recording", Common_BtnStr(BTN_MORE));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));

        Common_Sleep(50);
    }


    ERRCHECK( replay->release() );
    ERRCHECK( system->unloadAll() );
    return State_Selection;
}

void initializeScreenBuffer()
{
    memset(screenBuffer, ' ', sizeof(screenBuffer));

    int idx = SCREEN_WIDTH;
    for (int i = 0; i < SCREEN_HEIGHT; ++i)
    {
        screenBuffer[idx] = '\n';
        idx += SCREEN_WIDTH + 1;
    }

    screenBuffer[(SCREEN_WIDTH + 1) * SCREEN_HEIGHT] = '\0';
}

int getCharacterIndex(const FMOD_VECTOR& position)
{
    int row = static_cast<int>(-position.z + (SCREEN_HEIGHT / 2));
    int col = static_cast<int>(position.x + (SCREEN_WIDTH / 2));
    
    if (0 < row && row < SCREEN_HEIGHT && 0 < col && col < SCREEN_WIDTH)
    {
        return (row * (SCREEN_WIDTH + 1)) + col;
    }
    
    return -1;
}

void updateScreenPosition(const FMOD_VECTOR& eventPosition)
{
    if (currentScreenPosition != -1)
    {
        screenBuffer[currentScreenPosition] = ' ';
        currentScreenPosition = -1;
    }

    FMOD_VECTOR origin = {0};
    int idx = getCharacterIndex(origin);
    screenBuffer[idx] = '^';
    
    idx = getCharacterIndex(eventPosition);    
    if (idx != -1)
    {
        screenBuffer[idx] = 'o';
        currentScreenPosition = idx;
    }
}
