/*----------------------------------------------------------------------------
 *
 * File:
 * eas_main.c
 *
 * Contents and purpose:
 * The entry point and high-level functions for the EAS Synthesizer test
 * harness.
 *
 * Copyright Sonic Network Inc. 2004

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 775 $
 *   $Date: 2007-07-20 10:11:11 -0700 (Fri, 20 Jul 2007) $
 *----------------------------------------------------------------------------
*/

#ifdef _lint
#include "lint_stdlib.h"
#else
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#endif

#include "eas.h"
#include "eas_wave.h"
#include "eas_report.h"

/* determines how many EAS buffers to fill a host buffer */
#define NUM_BUFFERS         8

/* default file to play if no filename is specified on the command line */
static const char defaultTestFile[] = "test.mid";

EAS_I32 polyphony;

/* prototypes for helper functions */
static void StrCopy(char *dest, const char *src, EAS_I32 size);
static EAS_BOOL ChangeFileExt(char *str, const char *ext, EAS_I32 size);
static EAS_RESULT PlayFile (EAS_DATA_HANDLE easData, const char* filename, const char* outputFile, const S_EAS_LIB_CONFIG *pLibConfig, void *buffer, EAS_I32 bufferSize);
static EAS_BOOL EASLibraryCheck (const S_EAS_LIB_CONFIG *pLibConfig);

/* main is defined after playfile to avoid the need for two passes through lint */

/*----------------------------------------------------------------------------
 * PlayFile()
 *----------------------------------------------------------------------------
 * Purpose:
 * This function plays the file requested by filename
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/

static EAS_RESULT PlayFile (EAS_DATA_HANDLE easData, const char* filename, const char* outputFile, const S_EAS_LIB_CONFIG *pLibConfig, void *buffer, EAS_I32 bufferSize)
{
    EAS_HANDLE handle;
    EAS_RESULT result, reportResult;
    EAS_I32 count;
    EAS_STATE state;
    EAS_I32 playTime;
    char waveFilename[256];
    WAVE_FILE *wFile;
    EAS_INT i;
    EAS_PCM *p;

    /* determine the name of the output file */
    wFile = NULL;
    if (outputFile == NULL)
    {
        StrCopy(waveFilename, filename, sizeof(waveFilename));
        if (!ChangeFileExt(waveFilename, "wav", sizeof(waveFilename)))
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Error in output filename %s\n", waveFilename); */ }
            return EAS_FAILURE;
        }
        outputFile = waveFilename;
    }

    /* call EAS library to open file */
    if ((reportResult = EAS_OpenFile(easData, filename, &handle)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_OpenFile returned %ld\n", reportResult); */ }
        return reportResult;
    }

    /* prepare to play the file */
    if ((result = EAS_Prepare(easData, handle)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_Prepare returned %ld\n", result); */ }
        reportResult = result;
    }

    /* get play length */
    if ((result = EAS_ParseMetaData(easData, handle, &playTime)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_ParseMetaData returned %ld\n", result); */ }
        return result;
    }
    EAS_ReportEx(_EAS_SEVERITY_NOFILTER, 0xe624f4d9, 0x00000005 , playTime / 1000, playTime % 1000);

    if (reportResult == EAS_SUCCESS)
    {
        /* create the output file */
        wFile = WaveFileCreate(outputFile, pLibConfig->numChannels, pLibConfig->sampleRate, sizeof(EAS_PCM) * 8);
        if (!wFile)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Unable to create output file %s\n", waveFilename); */ }
            reportResult = EAS_FAILURE;
        }
    }

    /* rendering loop */
    while (reportResult == EAS_SUCCESS)
    {

        /* we may render several buffers here to fill one host buffer */
        for (i = 0, p = buffer; i < NUM_BUFFERS; i++, p+= pLibConfig->mixBufferSize * pLibConfig->numChannels)
        {

            /* get the current time */
            if ((result = EAS_GetLocation(easData, handle, &playTime)) != EAS_SUCCESS)
            {
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_GetLocation returned %d\n",result); */ }
                if (reportResult == EAS_SUCCESS)
                    reportResult = result;
                break;
            }
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Parser time: %d.%03d\n", playTime / 1000, playTime % 1000); */ }

            /* render a buffer of audio */
            if ((result = EAS_Render(easData, p, pLibConfig->mixBufferSize, &count)) != EAS_SUCCESS)
            {
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_Render returned %d\n",result); */ }
                if (reportResult == EAS_SUCCESS)
                    reportResult = result;
            }
        }

        if (result == EAS_SUCCESS)
        {
            /* write it to the wave file */
            if (WaveFileWrite(wFile, buffer, bufferSize) != bufferSize)
            {
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "WaveFileWrite failed\n"); */ }
                reportResult = EAS_FAILURE;
            }
        }

        if (reportResult == EAS_SUCCESS)
        {
            /* check stream state */
            if ((result = EAS_State(easData, handle, &state)) != EAS_SUCCESS)
            {
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_State returned %d\n", result); */ }
                reportResult = result;
            }

            /* is playback complete */
            if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR))
                break;
        }
    }

    /* close the output file */
    if (wFile)
    {
        if (!WaveFileClose(wFile))
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Error closing wave file %s\n", waveFilename); */ }
            if (reportResult == EAS_SUCCESS)
                result = EAS_FAILURE;
        }
    }

    /* close the input file */
    if ((result = EAS_CloseFile(easData,handle)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_Close returned %ld\n", result); */ }
        if (reportResult == EAS_SUCCESS)
            result = EAS_FAILURE;
    }

    return reportResult;
} /* end PlayFile */

/*----------------------------------------------------------------------------
 * main()
 *----------------------------------------------------------------------------
 * Purpose: The entry point for the EAS sample application
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
int main( int argc, char **argv )
{
    EAS_DATA_HANDLE easData;
    const S_EAS_LIB_CONFIG *pLibConfig;
    void *buffer;
    EAS_RESULT result, playResult;
    EAS_I32 bufferSize;
    int i;
    int temp;
    FILE *debugFile;
    char *outputFile = NULL;

    /* set the error reporting level */
    EAS_SetDebugLevel(_EAS_SEVERITY_INFO);
    debugFile = NULL;

    /* process command-line arguments */
    for (i = 1; i < argc; i++)
    {
        /* check for switch */
        if (argv[i][0] == '-')
        {
            switch (argv[i][1])
            {
            case 'd':
                temp = argv[i][2];
                if ((temp >= '0') || (temp <= '9'))
                    EAS_SetDebugLevel(temp);
                else
                    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "Invalid debug level %d\n", temp); */ }
                break;
            case 'f':
                if ((debugFile = fopen(&argv[i][2],"w")) == NULL)
                    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "Unable to create debug file %s\n", &argv[i][2]); */ }
                else
                    EAS_SetDebugFile(debugFile, EAS_TRUE);
                break;
            case 'o':
                outputFile = &argv[i][2];
                break;
            case 'p':
                polyphony = atoi(&argv[i][2]);
                if (polyphony < 1)
                    polyphony = 1;
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "Polyphony set to %d\n", polyphony); */ }
                break;
            default:
                break;
            }
            continue;
        }
    }

    /* assume success */
    playResult = EAS_SUCCESS;

    /* get the library configuration */
    pLibConfig = EAS_Config();
    if (!EASLibraryCheck(pLibConfig))
        return -1;
    if (polyphony > pLibConfig->maxVoices)
        polyphony = pLibConfig->maxVoices;

    /* calculate buffer size */
    bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels * (EAS_I32)sizeof(EAS_PCM) * NUM_BUFFERS;

    /* allocate output buffer memory */
    buffer = malloc((EAS_U32)bufferSize);
    if (!buffer)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "Error allocating memory for audio buffer\n"); */ }
        return EAS_FAILURE;
    }

    /* initialize the EAS library */
    polyphony = pLibConfig->maxVoices;
    if ((result = EAS_Init(&easData)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "EAS_Init returned %ld - aborting!\n", result); */ }
        free(buffer);
        return result;
    }

    /*
     * Some debugging environments don't allow for passed parameters.
     * In this case, just play the default MIDI file "test.mid"
     */
    if (argc < 2)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "Playing '%s'\n", defaultTestFile); */ }
        if ((playResult = PlayFile(easData, defaultTestFile, NULL, pLibConfig, buffer, bufferSize)) != EAS_SUCCESS)
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Error %d playing file %s\n", playResult, defaultTestFile); */ }
        }
    }
    /* iterate through the list of files to be played */
    else
    {
        for (i = 1; i < argc; i++)
        {
            /* check for switch */
            if (argv[i][0] != '-')
            {

                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "Playing '%s'\n", argv[i]); */ }
                if ((playResult = PlayFile(easData, argv[i], outputFile, pLibConfig, buffer, bufferSize)) != EAS_SUCCESS)
                {
                    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Error %d playing file %s\n", playResult, argv[i]); */ }
                    break;
                }
            }
        }
    }

    /* shutdown the EAS library */
    if ((result = EAS_Shutdown(easData)) != EAS_SUCCESS)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "EAS_Shutdown returned %ld\n", result); */ }
    }

    /* free the output buffer */
    free(buffer);

    /* close the debug file */
    if (debugFile)
        fclose(debugFile);

    /* play errors take precedence over shutdown errors */
    if (playResult != EAS_SUCCESS)
        return playResult;
    return result;
} /* end main */

/*----------------------------------------------------------------------------
 * StrCopy()
 *----------------------------------------------------------------------------
 * Purpose:
 * Safe string copy
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
static void StrCopy(char *dest, const char *src, EAS_I32 size)
{
    int len;

    strncpy(dest, src, (size_t) size-1);
    len = (int) strlen(src);
    if (len < size)
        dest[len] = 0;
} /* end StrCopy */

/*----------------------------------------------------------------------------
 * ChangeFileExt()
 *----------------------------------------------------------------------------
 * Purpose:
 * Changes the file extension of a filename
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
static EAS_BOOL ChangeFileExt(char *str, const char *ext, EAS_I32 size)
{
    char *p;

    /* find the extension, if any */
    p = strrchr(str,'.');
    if (!p)
    {
        if ((EAS_I32)(strlen(str) + 5) > size)
            return EAS_FALSE;
        strcat(str,".");
        strcat(str,ext);
        return EAS_TRUE;
    }

    /* make sure there's room for the extension */
    p++;
    *p = 0;
    if ((EAS_I32)(strlen(str) + 4) > size)
        return EAS_FALSE;
    strcat(str,ext);
    return EAS_TRUE;
} /* end ChangeFileExt */

/*----------------------------------------------------------------------------
 * EASLibraryCheck()
 *----------------------------------------------------------------------------
 * Purpose:
 * Displays the library version and checks it against the header
 * file used to build this code.
 *
 * Inputs:
 * pLibConfig       - library configuration retrieved from the library
 *
 * Outputs:
 * returns EAS_TRUE if matched
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_BOOL EASLibraryCheck (const S_EAS_LIB_CONFIG *pLibConfig)
{

    /* display the library version */
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "EAS Library Version %d.%d.%d.%d\n",
        pLibConfig->libVersion >> 24,
        (pLibConfig->libVersion >> 16) & 0x0f,
        (pLibConfig->libVersion >> 8) & 0x0f,
        pLibConfig->libVersion & 0x0f); */ }

    /* display some info about the library build */
    if (pLibConfig->checkedVersion)
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tChecked library\n"); */ }
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tMaximum polyphony: %d\n", pLibConfig->maxVoices); */ }
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tNumber of channels: %d\n", pLibConfig->numChannels); */ }
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tSample rate: %d\n", pLibConfig->sampleRate); */ }
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tMix buffer size: %d\n", pLibConfig->mixBufferSize); */ }
    if (pLibConfig->filterEnabled)
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tFilter enabled\n"); */ }
#ifndef _WIN32_WCE
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tLibrary Build Timestamp: %s", ctime((time_t*)&pLibConfig->buildTimeStamp)); */ }
#endif
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "\tLibrary Build ID: %s\n", pLibConfig->buildGUID); */ }

    /* check it against the header file used to build this code */
    /*lint -e{778} constant expression used for display purposes may evaluate to zero */
    if (LIB_VERSION != pLibConfig->libVersion)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "Library version does not match header files. EAS Header Version %d.%d.%d.%d\n",
            LIB_VERSION >> 24,
            (LIB_VERSION >> 16) & 0x0f,
            (LIB_VERSION >> 8) & 0x0f,
            LIB_VERSION & 0x0f); */ }
        return EAS_FALSE;
    }
    return EAS_TRUE;
} /* end EASLibraryCheck */