/* Include Files */
#include "KTag.h"

QueueHandle_t xQueueAudio;
TaskHandle_t Audio_Task_Handle;

static const uint8_t START_BYTE = 0x7E;
static const uint8_t VERSION_BYTE = 0xFF;
static const uint8_t END_BYTE = 0xEF;

// Valid volumes are 0 - 30.
static const uint8_t COMMAND_SPECIFY_VOLUME = 0x06;
static const uint8_t COMMAND_PAUSE = 0x0E;
static const uint8_t COMMAND_PLAY_TRACK_IN_FOLDER = 0x0F;

__attribute__((always_inline)) inline uint16_t CalculateChecksum(uint8_t * buffer, uint8_t length)
{
  uint16_t checksum = 0;
    
  for (uint_fast8_t i = 0; i < length; i++)
  {
    checksum += buffer[i];
  }

  return (0 - checksum);
}

static void Send_Command(uint8_t command, bool requireFeedback, uint16_t parameter)
{
    uint8_t buffer[10];
    uint16_t checksum;
    
    buffer[0] = START_BYTE;
    buffer[1] = VERSION_BYTE;
    buffer[2] = 6; // count
    buffer[3] = command;
    buffer[4] = requireFeedback;
    buffer[5] = (uint8_t)(parameter >> 8);
    buffer[6] = (uint8_t)(parameter);
    
    checksum = CalculateChecksum(&buffer[1], 6);
    
    buffer[7] = (uint8_t)(checksum >> 8);
    buffer[8] = (uint8_t)(checksum);
    buffer[9] = END_BYTE;
    
    for (uint_fast8_t i = 0; i < 10; i++)
    {
        UART_Audio_Put(buffer[i]);
    }
}

SystemKResult_T Perform_Audio_Action(AudioAction_T * action)
{
    if (xQueueSend(xQueueAudio, action, 0) == pdTRUE)
    {
        return SYSTEMK_RESULT_SUCCESS;
    }
    else
    {
        return SYSTEMK_RESULT_QUEUE_IS_FULL;
    }
}

void Init_Audio(void)
{
    UART_Audio_Start();
    
    xQueueAudio = xQueueCreate(5, sizeof(AudioAction_T));
}

void Audio_Task(void * pvParameters)
{
    portBASE_TYPE xStatus;
    
	while (IsNVMInitialized() == false)
	{
        vTaskDelay(100 / portTICK_PERIOD_MS);
	}
    Send_Command(COMMAND_SPECIFY_VOLUME, false, NVM_VOLUME);
    
    while (true)
    {
        AudioAction_T action;
        
        xStatus = xQueueReceive(xQueueAudio, &action, 0);
        
        if (xStatus == pdPASS)
        {
            switch (action.ID)
            {
                case AUDIO_SET_VOLUME:
                    {
                        uint8_t volume = *((uint8_t *)action.Data);
                        if (volume <= 30)
                        {
                            Send_Command(COMMAND_SPECIFY_VOLUME, false, volume);
                        }
                    }
                    break;
                    
                case AUDIO_SILENCE:
                    Send_Command(COMMAND_PAUSE, false, 0x0000);
                    break;
                
                case AUDIO_PLAY_STARTUP_SOUND:
                    // Play track "001" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0101);
                    break;
                    
                case AUDIO_PLAY_SHOT_FIRED:
                    // Play track "002" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0102);
                    break;
                    
                case AUDIO_PLAY_TAG_RECEIVED:
                    // Play track "003" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0103);
                    break;
                
                case AUDIO_PLAY_TAGGED_OUT:
                    // Play track "004" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0104);
                    break;
                
                case AUDIO_PLAY_MISFIRE:
                    // Play track "005" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0105);
                    break;
                    
                case AUDIO_PRONOUNCE_NUMBER_0_TO_100:
                    {
                        uint8_t file_index = *((uint8_t *)action.Data);
                        if (file_index > 100)
                        {
                            file_index = 100;
                        }
                        else if (file_index == 0)
                        {
                            file_index = 101;
                        }
                        // The numbers are stored in folder "10".
                        // 001.mp3 is "one", 100.mp3 is "one hundred", and 101.mp3 is "zero".
                        uint16_t filenumber = 0x0A00 + file_index;
                        Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, filenumber);
                    }
                    break;
                    
                case AUDIO_PLAY_MENU_PROMPT:
                    // Play track "006" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0106);
                    break;
                    
                case AUDIO_PLAY_SELECTION_INDICATOR:
                    // Play track "007" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0107);
					break;
						                
                case AUDIO_PLAY_HEALTH_REMAINING:
                    // Play track "008" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0108);
                    break;
                
                case AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC:
                    // Play track "009" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0109);
                    break;
                    
                default:
                case AUDIO_PLAY_GENERIC_ERROR:
                    // Play track "010" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010A);
                    break;
                    
                case AUDIO_PLAY_VOLUME_PROMPT:
                    // Play track "011" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010B);
                    break;
                    
                case AUDIO_PLAY_RIGHT_HANDED:
                    // Play track "012" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010C);
                    break;
                    
                case AUDIO_PLAY_LEFT_HANDED:
                    // Play track "013" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010D);
                    break;
                    
                case AUDIO_PLAY_GAME_ON:
                     // Play track "014" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010E);
                    break;
                    
                case AUDIO_PLAY_HARDWARE_SETTINGS_PROMPT:
                    // Play track "015" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x010F);
                    break;
                    
                case AUDIO_PLAY_GAME_SETTINGS_PROMPT:
                    // Play track "016" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0110);
                    break;
                    
                case AUDIO_PLAY_BONK:
                    // Play track "017" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0111);
                    break;
                    
                case AUDIO_PLAY_NEAR_MISS:
                    // Play track "018" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0112);
                    break;
                    
                case AUDIO_PLAY_PLAYER_ID_PROMPT:
                    // Play track "019" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0113);
                    break;
                    
                case AUDIO_PLAY_TEAM_ID_PROMPT:
                    // Play track "020" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0114);
                    break;
                    
                case AUDIO_PLAY_FRIENDLY_FIRE:
                    // Play track "021" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0115);
                    break;
                    
                case AUDIO_PLAY_STARTING_THEME:
                    // Play track "022" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0116);
                    break;
                    
                case AUDIO_PLAY_BOOP:
                    // Play track "023" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0117);
                    break;
                    
                case AUDIO_PLAY_BEEP:
                    // Play track "024" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0118);
                    break;
                    
                case AUDIO_PLAY_REPROGRAMMING:
                    // Play track "025" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x0119);
                    break;
                    
                case AUDIO_PLAY_BOMB:
                    // Play track "026" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x011A);
                    break;
                    
                case AUDIO_PLAY_GAME_OVER:
                    // Play track "027" in folder "01".
                    Send_Command(COMMAND_PLAY_TRACK_IN_FOLDER, false, 0x011B);
                    break;
            }
                                 
            if (action.Play_To_Completion == true)
            {
                do
                {
                    vTaskDelay(100 / portTICK_PERIOD_MS);
                } while (Is_Audio_Playing() == true);

                KEvent_T command_received_event = {.ID = KEVENT_AUDIO_COMPLETED, .Data = (void *)action.ID};
                Post_KEvent(&command_received_event);
            }
        }
        
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

bool Is_Audio_Playing()
{
    bool result = false;
    
    // The signal is active low.
    if (Cy_GPIO_Read(Pin_Audio_Busy_PORT, Pin_Audio_Busy_NUM) == 0)
    {
        result = true;
    }
    
    return result;
}
