/**
 * TestLac controller module.
 *
 * @copyright 2008, Research In Motion Ltd.
 *
 * @version $Id: //depot/main/dsp/ecall/c/testlab/control.c#18 $
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <stdarg.h>

#include "rimtypes.h"
#include "msg.h"
#include "ipc.h"
#include "testlab.h"
#include "error.h"

#define PATTERN_MAX_LENGTH (100)

typedef struct {
    /* Per run test config info */
    int  index;
    int  msdN;
    Ord8 msd[MSD_MAX_LENGTH];
    int  codecType;
    int  codecMode;
    int  vad;
    int  ulDelay1;
    int  ulDelay2;
    char ulErrorPattern[PATTERN_MAX_LENGTH+1];
    int  ulErrorPatternIndex;
    int  dlDelay1;
    int  dlDelay2;
    char dlErrorPattern[PATTERN_MAX_LENGTH+1];
    int  dlErrorPatternIndex;
} Config;

typedef struct {
    int msdReceived;
    int msdMatch;
    int msdN;
    Ord8 msd[MSD_MAX_LENGTH];
    int startTime;
    int ivsStarted;
    int ivsStartFrame;
    int ivsStartTime;
    int endFrame;
    int endTime;
} Result;

typedef struct {
    IpcConn *ul;
    IpcConn *dl;
    IpcConn *ivs;
    IpcConn *psap;
    const char *pcm;
    const char *campaign;
    const char *results;
    int pcmMaxFrames;
    int psapTxStartFrame;
} Control;

static Control control;

static void SendChReset(IpcConn *dst, int codec, int codecMode, int vad,
                                  int delay1, int delay2,
                                  char *errorPattern, int errorPatternIndex) {
    Msg msg;

    memset(&msg, 0, sizeof(msg));
    msg.id                  = MsgChReset;
    msg.u.chReset.codec     = codec;
    msg.u.chReset.codecMode = codecMode;
    msg.u.chReset.vad       = vad;
    msg.u.chReset.delay1    = delay1;
    msg.u.chReset.delay2    = delay2;
    strcpy(msg.u.chReset.errorPattern, errorPattern);
    msg.u.chReset.errorPatternIndex = errorPatternIndex;
    IpcSend(dst, &msg);
}

static void SendIvsReset(IpcConn *dst, int n, Ord8 *msd) {
    Msg msg;

    memset(&msg, 0, sizeof(msg));
    msg.id = MsgIvsReset;
    msg.u.ivsReset.n = n;
    memcpy(msg.u.ivsReset.msd, msd, n);
    IpcSend(dst, &msg);
}

static void SendPsapReset(IpcConn *dst) {
    Msg msg;

    memset(&msg, 0, sizeof(msg));
    msg.id = MsgPsapReset;
    IpcSend(dst, &msg);
}

static void SendPsapTx(IpcConn *dst) {
    Msg msg;

    memset(&msg, 0, sizeof(msg));
    msg.id = MsgPsapTx;
    IpcSend(dst, &msg);
}

static void SendPcmData(IpcConn *dst, Int16 *pcm) {
    Msg msg;

    memset(&msg, 0, sizeof(msg));
    msg.id = MsgPcmData;
    memcpy(msg.u.pcmData.pcm, pcm, sizeof(msg.u.pcmData.pcm));
    IpcSend(dst, &msg);
}

/* Return current time in ms */
static int GetTime(void) {
    double t = (clock() * 1000.0) / CLOCKS_PER_SEC;
    return t;
}

static int AllZero(const Int16 *pcm, int n) {
    while (n-- > 0) {
        if (*pcm++ != 0) return FALSE;
    }
    return TRUE;
}

static int MsdMatch(int n1, const Ord8 *msd1,
                    int n2, const Ord8 *msd2) {
    if (n1 != n2) return FALSE;
    while (n1-- > 0) {
        if (*msd1++ != *msd2++) return FALSE;
    }
    return TRUE;
}

static FILE *LogPcmOpen(const char *base, const char *src, int index) {
    char name[200];
    FILE *file;

    sprintf(name, "%s%s%d.pcm", base, src, index);
    file = fopen(name, "wb");
    if (file == NULL) {
        LogError("Unable to open PCM Log file %s", name);
    }
    return file;

}

static void LogPcm(FILE *f, Int16 *s, int n) {
    if (f != NULL) {
        if (fwrite(s, sizeof(*s), n, f) != n) {
            LogError("Unable to write to PCM log file");
        }
    }
}

static void LogPcmClose(FILE *ulIn, FILE *ulOut, FILE *dlIn, FILE *dlOut) {
    if (ulIn  != NULL) fclose(ulIn);
    if (ulOut != NULL) fclose(ulOut);
    if (dlIn  != NULL) fclose(dlIn);
    if (dlOut != NULL) fclose(dlOut);
}

static int ControlRunTc(Config *config, Result *result) {
    BOOL ready;
    int k;
    int fn;
    Int16 ulIn[PCM_LENGTH];
    Int16 ulOut[PCM_LENGTH];
    Int16 dlIn[PCM_LENGTH];
    Int16 dlOut[PCM_LENGTH];
    FILE *ulInFile  = NULL;
    FILE *ulOutFile = NULL;
    FILE *dlInFile  = NULL;
    FILE *dlOutFile = NULL;
    IpcConn *conn[] = {control.ul, control.dl, control.ivs, control.psap};

    /* Open PCM files */
    if (control.pcm != NULL) {
        ulInFile  = LogPcmOpen(control.pcm, "ulin",  config->index);
        ulOutFile = LogPcmOpen(control.pcm, "ulout", config->index);
        dlInFile  = LogPcmOpen(control.pcm, "dlin",  config->index);
        dlOutFile = LogPcmOpen(control.pcm, "dlout", config->index);
        if ((ulInFile == NULL) || (ulOutFile == NULL) ||
            (dlInFile == NULL) || (dlOutFile == NULL)) {
            return FALSE;
        }
    }

    /* Reset everyone and wait for their ready indications */
    SendChReset(control.ul, config->codecType, config->codecMode, config->vad,
                config->ulDelay1, config->ulDelay2,
                config->ulErrorPattern, config->ulErrorPatternIndex);
    SendChReset(control.dl, config->codecType, config->codecMode, config->vad,
                config->dlDelay1, config->dlDelay2,
                config->dlErrorPattern, config->dlErrorPatternIndex);
    SendIvsReset(control.ivs, config->msdN, config->msd);
    SendPsapReset(control.psap);

    /* Wait for a response from each block */
    ready = TRUE;
    for(k = 0; k < LENGTH(conn); ++k) {
        Msg msg;
        if(!IpcRecv(conn[k], &msg)) {
            LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
            Abort("Timeout waiting for responses from UL/DL/IVS/PSAP");
        }
        if(msg.id == MsgError) {
            ready = FALSE;
        }
        else if(msg.id != MsgReady) {
            LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
            Abort("Unexpected msg <%d> from <0x%08x>", msg.id, conn[k]);
        }
    }

    if(!ready) {
        LogError("One or more of UL/DL/IVS/PSAP failed to reset");
        LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
        return FALSE;
    }

    /* Init result struct */
    result->msdReceived   = FALSE;
    result->msdMatch      = FALSE;
    result->startTime     = GetTime();
    result->ivsStarted    = FALSE;
    result->ivsStartFrame = -1;
    result->ivsStartTime  = -1;
    result->endFrame      = -1;
    result->endTime       = -1;

    /* Clear Ul/DL In/Out buffers */
    memset(ulIn,  0, sizeof(ulIn));
    memset(ulOut, 0, sizeof(ulOut));
    memset(dlIn,  0, sizeof(dlIn));
    memset(dlOut, 0, sizeof(dlOut));

    for (fn = 0; fn < control.pcmMaxFrames; fn += 1) {
        Msg      msg;
        IpcConn *src;

        /* Send signals to other tasks */
        if (fn == control.psapTxStartFrame) {
            SendPsapTx(control.psap);
        }
        SendPcmData(control.ul, ulIn);
        SendPcmData(control.dl, dlIn);
        SendPcmData(control.ivs,  dlOut);
        SendPcmData(control.psap, ulOut);

        /* Gather all the responses */
        for(k = 0; k < LENGTH(conn);) {
            Msg msg;
            if(!IpcRecv(conn[k], &msg)) {
                LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
                Abort("Timeout waiting for responses from UL/DL/IVS/PSAP");
            }
            if (msg.id == MsgPcmData) {
                if (conn[k] == control.ul) {
                    memcpy(ulOut, msg.u.pcmData.pcm, sizeof(msg.u.pcmData.pcm));
                    LogPcm(ulOutFile, ulOut, LENGTH(msg.u.pcmData.pcm));
                }
                else if (conn[k] == control.dl) {
                    memcpy(dlOut, msg.u.pcmData.pcm, sizeof(msg.u.pcmData.pcm));
                    LogPcm(dlOutFile, dlOut, LENGTH(msg.u.pcmData.pcm));
                }
                else if (conn[k] == control.ivs) {
                    memcpy(ulIn, msg.u.pcmData.pcm, sizeof(msg.u.pcmData.pcm));
                    LogPcm(ulInFile, ulIn, LENGTH(msg.u.pcmData.pcm));
                    if (!result->ivsStarted) {
                        int az = AllZero(ulIn, LENGTH(ulIn));
                        if (!az) {
                            result->ivsStarted = TRUE;
                            result->ivsStartFrame = fn;
                            result->ivsStartTime  = GetTime();
                        }
                    }
                }
                else {
                    memcpy(dlIn, msg.u.pcmData.pcm, sizeof(msg.u.pcmData.pcm));
                    LogPcm(dlInFile, dlIn, LENGTH(msg.u.pcmData.pcm));
                }
                /* Move to the next block: MsgPcmData is always sent last */
                k++;
            }
            else if ((msg.id == MsgPsapRx) && (conn[k] == control.psap)) {
                result->endTime = GetTime();
                result->endFrame = fn;
                result->msdReceived = TRUE;
                result->msdMatch = MsdMatch(config->msdN, config->msd,
                                            msg.u.psapRx.n, msg.u.psapRx.msd);
                result->msdN = msg.u.psapRx.n;
                if (result->msdN > MSD_MAX_LENGTH) result->msdN=MSD_MAX_LENGTH;
                memcpy(result->msd, msg.u.psapRx.msd, result->msdN);
            }
            else {
                LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
                Abort("Unexpected msg <%d> from <0x%08x>", msg.id, conn[k]);
            }
        }

        /* Early exit */
        if (result->msdReceived) break;
    }
    LogPcmClose(ulInFile, ulOutFile, dlInFile, dlOutFile);
    return TRUE;
}

static int ReadConfig(FILE *f, Config *config) {
    int n;
    char msd[500 + 1];
    char codecType[10 + 1];
    char codecMode[10 + 1];
    char vad[10 + 1];
    char ulErrorPattern[100 + 1];
    char dlErrorPattern[100 + 1];


    n = fscanf(f, "%d %500s %10s %10s %10s %d %d %100s %d %d %d %100s %d\n", 
            &config->index,
            msd,
            codecType,
            codecMode,
            vad,
            &config->ulDelay1,
            &config->ulDelay2,
            ulErrorPattern,
            &config->ulErrorPatternIndex,
            &config->dlDelay1,
            &config->dlDelay2,
            dlErrorPattern,
            &config->dlErrorPatternIndex);

    /* Check for end of file */
    if ((n <= 0) && feof(f)) {
        return FALSE;
    }

    if (n != 13) {
        LogError("Unable to read next config item from campaign file %d", n);
        return FALSE;
    }

    n = strlen(msd);;
    if ((n >> 1) << 1 != n) {
        LogError("MSD invalid MSD string length - %d", n);
    }
    n >>= 1;
    if (n > MSD_MAX_LENGTH) {
        LogError("MSD too long - %d", n);
    }

    /* Convert MSD */
    config->msdN = n;
    for (n = 0; n < config->msdN; n += 1) {
        int x;
        if (sscanf(&msd[n << 1], "%2x", &x) == 1) {
            config->msd[n] = x;
        }
        else {
            LogError("Invalid MSD characters %c%c", msd[n << 1],
                                                    msd[(n << 1) + 1]);
            return FALSE;
        }
    }

    /* Convert codecType */
    if (_stricmp(codecType, "fr") == 0) {
        config->codecType = CodecTypeFr;
    }
    else if (_stricmp(codecType, "amr") == 0) {
        config->codecType = CodecTypeAmr;
    }
    else if (_stricmp(codecType, "none") == 0) {
        config->codecType = CodecTypeNone;
    }
    else if (_stricmp(codecType, "bypass") == 0) {
        config->codecType = CodecTypeBypass;
    }
    else {
        LogError("Invalid codec type %s", codecType);
        return FALSE;
    }

    /* Convert codecMode */ 
    if (_stricmp(codecMode, "12_2") == 0) {
        config->codecMode = CodecModeAmr12_2;
    }
    else if (_stricmp(codecMode, "10_2") == 0) {
        config->codecMode = CodecModeAmr10_2;
    }
    else if (_stricmp(codecMode, "7_95") == 0) {
        config->codecMode = CodecModeAmr7_95;
    }
    else if (_stricmp(codecMode, "7_4") == 0) {
        config->codecMode = CodecModeAmr7_4;
    }
    else if (_stricmp(codecMode, "6_7") == 0) {
        config->codecMode = CodecModeAmr6_7;
    }
    else if (_stricmp(codecMode, "5_9") == 0) {
        config->codecMode = CodecModeAmr5_9;
    }
    else if (_stricmp(codecMode, "5_15") == 0) {
        config->codecMode = CodecModeAmr5_15;
    }
    else if (_stricmp(codecMode, "4_75") == 0) {
        config->codecMode = CodecModeAmr4_75;
    }
    else if (_stricmp(codecMode, "na") == 0) {
        config->codecMode = CodecModeNa;
    }
    else {
        LogError("Invalid codec mode %s", codecMode);
        return FALSE;
    }

    /* Convert VAD */
    if (_stricmp(vad, "0") == 0) {
        config->vad = VadTypeNone;
    }
    else if (_stricmp(vad, "1") == 0) {
        config->vad = VadType1;
    }
    else if (_stricmp(vad, "2") == 0) {
        config->vad = VadType2;
    }
    else {
        LogError("Invalid VAD type %s", vad);
        return FALSE;
    }

    if (strlen(ulErrorPattern) >= LENGTH(config->ulErrorPattern)) {
        LogError("UL Error Pattern file name too long %s", ulErrorPattern);
        return FALSE;
    }
    strcpy(config->ulErrorPattern, ulErrorPattern);

    if (strlen(dlErrorPattern) >= LENGTH(config->dlErrorPattern)) {
        LogError("DL Error Pattern file name too long %s", dlErrorPattern);
        return FALSE;
    }
    strcpy(config->dlErrorPattern, dlErrorPattern);
    return TRUE;
}

BOOL ControlRun(void) {
    Result result;
    Config config;
    FILE *campaign;
    FILE *results;
    
    /* get configuration file */
    campaign = fopen(control.campaign, "rt");
    if (campaign == NULL) {
        LogError("Unable to open campaign file <%s>", control.campaign);
        return FALSE;
    }

    if (strcmp(control.results, "[stdout]") == 0) {
        results = stdout;
    }
    else if (strcmp(control.results, "[stdout]") == 0) {
        results = stderr;
    }
    else {
        results = fopen(control.results, "wt");
    }
    if (results == NULL) {
        LogError("Unable to option results file <%s>", control.results);
        return FALSE;
    }

    while (TRUE) {
        int ok;
        int k;

        if (!ReadConfig(campaign, &config)) break;

        LogInfo("Running test case %d", config.index);

        ok = ControlRunTc(&config, &result);

        fprintf(results, "%d ", config.index);
        for (k = 0; k < config.msdN; k += 1) {
           fprintf(results, "%02x", config.msd[k]);
        }
        switch (config.codecType) {
            case 0: fprintf(results, " fr"); break;
            case 1: fprintf(results, " amr"); break;
            case 2: fprintf(results, " none"); break;
            case 3: fprintf(results, " bypass"); break;
            default: fprintf(results, " error<%d>", config.codecType); break;
        }
        switch (config.codecMode) {
            case CodecModeNa:      fprintf(results, " na"); break;
            case CodecModeAmr12_2: fprintf(results, " 12_2"); break;
            case CodecModeAmr10_2: fprintf(results, " 10_2"); break;
            case CodecModeAmr7_95: fprintf(results, " 7_95"); break;
            case CodecModeAmr7_4:  fprintf(results, " 7_4"); break;
            case CodecModeAmr6_7:  fprintf(results, " 6_7"); break;
            case CodecModeAmr5_9:  fprintf(results, " 5_9"); break;
            case CodecModeAmr5_15: fprintf(results, " 5_15"); break;
            case CodecModeAmr4_75: fprintf(results, " 4_75"); break;
            default: fprintf(results, " error<%d>", config.codecMode); break;
        }
        switch (config.vad) {
            case VadTypeNone:   fprintf(results, " 0"); break;
            case VadType1:      fprintf(results, " 1"); break;
            case VadType2:      fprintf(results, " 2"); break;
            default: fprintf(results, " error<%d>", config.vad); break;
        }
        fprintf(results, " %d %d %s %d %d %d %s %d ",
            config.ulDelay1, config.ulDelay2,
            config.ulErrorPattern, config.ulErrorPatternIndex,
            config.dlDelay1, config.dlDelay2,
            config.dlErrorPattern, config.dlErrorPatternIndex);

        if (ok) {
            if (result.msdReceived) {
                fprintf(results, "%d %s ",
                        result.endFrame-result.ivsStartFrame,
                        result.msdMatch ? "S" : "F");
                for (k = 0; k < result.msdN; k += 1) {
                    fprintf(results, "%02x", result.msd[k]);
                }
                fprintf(results, "\n");
            }
            else {
                fprintf(results, "-1 F\n");
            }
        }
        else {
            fprintf(results, "-1 U\n");
            LogError("Test <%d> was not completed!", config.index);
        }

        fflush(results);
    }
    fclose(campaign);
    return FALSE;
}

void ControlInit(const char *host,
                  const char *ul,
                  const char *dl,
                  const char *ivs,
                  const char *psap,
                  const char *pcm,
                  const char *campaign,
                  const char *results,
                  int pcmMaxFrames,
                  int psapTxStartFrame) {

    LogInfo("Starting CONTROL");
    control.ul   = IpcListen(host, ul);
    control.dl   = IpcListen(host, dl);
    control.ivs  = IpcListen(host, ivs);
    control.psap = IpcListen(host, psap);
    control.pcm  = pcm;
    control.campaign = campaign;
    control.results  = results;
    control.pcmMaxFrames = pcmMaxFrames;
    control.psapTxStartFrame = psapTxStartFrame;
}
