/*---------------------------------------------------------------------------*
 *  parseStringTest.c  *
 *                                                                           *
 *  Copyright 2007, 2008 Nuance Communciations, Inc.                               *
 *                                                                           *
 *  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.                                           *
 *                                                                           *
 *---------------------------------------------------------------------------*/



#include "pstdio.h"
#include "pmemory.h"
#include "plog.h"


#include "HashMap.h"
#include "SR_Grammar.h"
#include "SR_SemanticResult.h"
#include "ESR_Session.h"
#include "ESR_Locale.h"
#include "LCHAR.h"

#include "PFileSystem.h"
#include "PANSIFileSystem.h"

/* for testing RecognizerImpl.c, see below */
#include"buildopt.h"
#include"setting.h"
#include"srec_sizes.h"
#include"SR_GrammarImpl.h"

/* defines */
#define MAX_LINE_LENGTH 256
#define MAX_STR_LENGTH  512
#define MAX_SEM_RESULTS   3
#define MAX_KEYS         30

/* protos */
ESR_ReturnCode process_single_key_line(SR_Grammar* grammar, PFile* fin, PFile* fout);
ESR_ReturnCode process_multi_key_line(SR_Grammar* grammar, const LCHAR* rootrule, PFile* fin, PFile* fout);

/* struct */
typedef struct Opts
{
  int use_parse_by_string_ids;
  int do_check_all_ids;
}
Opts;

int usage(LCHAR* exename)
{
  pfprintf(PSTDOUT, "usage: %s -base <basefilename> [-in <input file>] [-out <output file>] [-itest <testfilename>]\n", exename);
  return 1;
}

void lstr_strip_multiple_spaces(LCHAR* trans)
{
  char *src=trans, *dst=trans;
  for( ;(*dst = *src)!=L('\0'); src++) {
    if(*dst != ' ') dst++;
    else if(src[1] != ' ') dst++;
  }
}

/**
 * Display the Semantic Result
 */
void display_results(SR_SemanticResult *result, PFile* fout)
{
  size_t i, size, len;
  LCHAR* keys[MAX_KEYS]; /* array of pointers to strings */
  LCHAR  value[MAX_STR_LENGTH];
  ESR_ReturnCode rc;
  
  size = MAX_KEYS;
  rc = result->getKeyList(result, (LCHAR**) & keys, &size); /* get the key list */
  if (rc == ESR_SUCCESS)
  {
    for (i = 0; i < size; i++)
    {
      len = MAX_STR_LENGTH;
      if ((rc = result->getValue(result, keys[i], value, &len)) == ESR_SUCCESS)
        pfprintf(fout, "{%s : %s}\n", keys[i], value);
      else
        pfprintf(fout, "Error: %s\n", ESR_rc2str(rc));
    }
    pfprintf(fout, "--Done--\n");
  }
  else
    pfprintf(fout, "Error: %s\n", ESR_rc2str(rc));
}

ESR_ReturnCode Parse(SR_Grammar* grammar, LCHAR* trans, PFile* fout, Opts* opts)
{
  ESR_ReturnCode rc = ESR_SUCCESS;
  size_t i, result_count, key_count;
  SR_SemanticResult* semanticResults[MAX_SEM_RESULTS];
  wordID wordIDs[32], *wordIDptr;
  SR_GrammarImpl* pgrammar = (SR_GrammarImpl*)grammar;
  wordmap* wmap;
  
  if (opts->do_check_all_ids)
  {
    wordID id;
    Opts myopts;
    memcpy(&myopts, opts, sizeof(myopts));
    myopts.do_check_all_ids = 0;
    wmap = pgrammar->syntax->synx->olabels;
    /* start at word 4 because "eps, -pau- -pau2- @root */
    for (id = 4; id < wmap->num_words; id++)
    {
      trans = wmap->words[id];
      Parse(grammar, trans, fout, &myopts);
    }
    return 0;
  }
  
  result_count = MAX_SEM_RESULTS; /* initially not greater than MAX */
  for (i = 0; i < result_count; i++)
    SR_SemanticResultCreate(&semanticResults[i]); /* create the result holders */
  lstrtrim(trans);
  /* check for multiple space separators! */
  lstr_strip_multiple_spaces(trans);
  
  if (!opts->use_parse_by_string_ids)
  {
    rc = grammar->checkParse(grammar, trans, semanticResults, (size_t*) & result_count);
  }
  else
  {
    char copy_of_trans[256], *p;
    strcpy(copy_of_trans, trans);
    wmap = pgrammar->syntax->synx->olabels;
    wordIDs[0] = wordIDs[1] = MAXwordID;
    wordIDptr = &wordIDs[0];
    for (p = strtok(copy_of_trans, " "); p; p = strtok(NULL, " "))
    {
      for (i = 0; i < wmap->num_words; i++)
        if (!strcmp(wmap->words[i], p))
        {
          *wordIDptr++ = (wordID)i;
          break;
        }
      if (i == wmap->num_words)
      {
        wordIDs[0] = MAXwordID;
        break;
      }
    }
    *wordIDptr++ = MAXwordID;
    
    /* printf("wordids:");
       for(wordIDptr=&wordIDs[0]; *wordIDptr!=MAXwordID; wordIDptr++) 
       printf(" %d/%s", *wordIDptr, wmap->words[*wordIDptr]);
       printf("\n"); */
    
    if (wordIDs[0] == MAXwordID)
    {
      result_count = 0;
      rc = ESR_SUCCESS;
    }
    else
    {
      rc = pgrammar->semproc->flush(pgrammar->semproc);
      rc = pgrammar->semproc->setParam(pgrammar->semproc, L("literal"), trans);
      rc = pgrammar->semproc->checkParseByWordID(pgrammar->semproc, pgrammar->semgraph,
           wordIDs, semanticResults, &result_count);
    }
  }
  if (rc != ESR_SUCCESS)
  {
    pfprintf(fout, "error (%s)\n\n", trans);
    return rc;
  }
  
  if (result_count < 1)
  {
    pfprintf(fout, "no parse (%s)\n\n", trans);
  }
  else
  {
    key_count = 0xffff;
    rc = SR_SemanticResultGetKeyCount(semanticResults[0], &key_count);
    pfprintf(fout, "parse ok (%d results) (%s) (%d)\n", result_count, trans, key_count);
    for (i = 0; i < result_count; i++)
      display_results(semanticResults[i], fout);
      
    for (i = 0; i < MAX_SEM_RESULTS; i++)
    {
      rc = semanticResults[i]->destroy(semanticResults[i]);
      if (rc != ESR_SUCCESS)
        return rc;
    }
  }
  return ESR_SUCCESS;
}

/* tests the transcription against the grammar and then decided based on what was expected of the test
whether or not is it considered a pass or fail */
ESR_ReturnCode ParseTestSet(SR_Grammar* grammar, LCHAR* trans, LCHAR* key, LCHAR* ref, LCHAR* result, PFile* fout)
{
  size_t len;
  ESR_ReturnCode rc;
  int i, result_count;
  SR_SemanticResult* semanticResults[MAX_SEM_RESULTS];
  LCHAR  value[MAX_STR_LENGTH];
  
  result_count = MAX_SEM_RESULTS;
  for (i = 0; i < result_count; i++)
    SR_SemanticResultCreate(&semanticResults[i]);
    
  lstrtrim(trans);
  /* check for multiple space separators! */
  lstr_strip_multiple_spaces(trans);
  
  pfprintf(fout, "checking (%s) ref(%s) res(%s)\n", trans, ref, result);
  rc = grammar->checkParse(grammar, trans, semanticResults, (size_t*) & result_count);
  if (rc != ESR_SUCCESS)
    return rc;
    
  /*result file will contain
  transcription | key | reference | result | PASSESD/FAILED */
  
  if (result_count < 1) /*failed to parse, but this could still be a pass if you expected a failure*/
  {
    pfprintf(fout, "NO PARSE FOR: %s|%s|%s|  |", trans, key, ref);
    if (strcmp("FAIL", result) == 0)
      pfprintf(fout, "PASSED (%s)\n", trans);
    else
      pfprintf(fout, "FAILED (%s)\n", trans);
  }
  else /*parsed, look at what was expected, what was returned and which of PASS/FAIL is expected */
  {
    for (i = 0; i < result_count; i++)
    {
      len = MAX_STR_LENGTH;
      if ((rc = semanticResults[i]->getValue(semanticResults[i], key, value, &len)) == ESR_SUCCESS)
      {
        pfprintf(fout, "%s|%s|%s|%s|", trans, key, ref, value);
        
        if (strcmp(value, ref) == 0 && strcmp("PASS", result) == 0)
          pfprintf(fout, "PASSED\n");
        else
          pfprintf(fout, "FAILED\n");
      }
      else
      {
        pfprintf(fout, "ERROR: %s, while checking key='%s'\n", ESR_rc2str(rc), key);
      }
    }
    
    /*deallocate semantic results*/
    for (i = 0; i < MAX_SEM_RESULTS; i++)
    {
      rc = semanticResults[i]->destroy(semanticResults[i]);
      if (rc != ESR_SUCCESS)
        return rc;
    }
  }
  return ESR_SUCCESS;
}

int main(int argc, char **argv)
{
  LCHAR trans[MAX_LINE_LENGTH];
  SR_Grammar* grammar = NULL;
  ESR_ReturnCode rc;
  LCHAR base[P_PATH_MAX] = L("");
  LCHAR infilename[P_PATH_MAX] = L("");
  LCHAR inRTfilename[P_PATH_MAX] = L("");
  LCHAR outfilename[P_PATH_MAX] = L("");
  PFile *fin = NULL, *fout = NULL;
  int i;
  LCHAR *rootrule = L("myRoot"), *p;
  Opts opts = { 0, 0 };
  
  /*
   * Initialize portable library.
   */
  CHKLOG(rc, PMemInit());

  fin = PSTDIN;
  fout = PSTDOUT;
  
  if (argc < 3)
  {
    usage(argv[0]);
    exit(EXIT_FAILURE);
  }
  for (i = 1; i < argc; ++i)
  {
    if (!LSTRCMP(argv[i], L("-base")))
    {
      ++i;
      LSTRCPY(base, argv[i]);
    }
    else if (!LSTRCMP(argv[i], L("-in")))
    {
      ++i;
      LSTRCPY(infilename, argv[i]);
    }
    else if (!LSTRCMP(argv[i], L("-out")))
    {
      ++i;
      LSTRCPY(outfilename, argv[i]);
    }
    else if (!LSTRCMP(argv[i], L("-itest")))
    {
      ++i;
      LSTRCPY(inRTfilename, argv[i]);
    }
    else if (!LSTRCMP(argv[i], L("-ids")))
    {
      opts.use_parse_by_string_ids = 1;
    }
    else if (!LSTRCMP(argv[i], L("-allids")))
    {
      opts.do_check_all_ids = 1;
      opts.use_parse_by_string_ids = 1;
    }
    else
      return usage(argv[0]);
  }
  
  CHK(rc, PLogInit(NULL, 0));
    
  rc = SR_GrammarLoad(base, &grammar);
  if (rc != ESR_SUCCESS)
    goto CLEANUP;
    
  if (*outfilename)
  {
    if ((fout = pfopen(outfilename, "w")) == NULL)
    {
      pfprintf(PSTDOUT, "Could not open file: %s\n", outfilename);
      rc = 1;
      goto CLEANUP;
    }
  }
  
  if (opts.do_check_all_ids)
  {
    rc = Parse(grammar, NULL, fout, &opts);
  }
  else if (*infilename)
  {
    if (LSTRCMP(infilename, "-") == 0)
    {
      fin = PSTDIN;
    }
    else if ((fin = pfopen(infilename, "r")) == NULL)
    {
      pfprintf(PSTDOUT, "Could not open file: %s\n", infilename);
      rc = 1;
      goto CLEANUP;
    }
    for (;;)
    {
      if (pfgets(trans, MAX_LINE_LENGTH, fin) == NULL)
      {
        if (!pfeof(fin))
        {
          rc = ESR_READ_ERROR;
          PLogError(ESR_rc2str(rc));
        }
        break;
      }
      if (trans[0] == '#') continue;
      lstrtrim(trans);
      /* check for multiple space separators! */
      lstr_strip_multiple_spaces(trans);
      pfprintf(fout, "Transcription: %s\n", trans);
      if ((rc = Parse(grammar, trans, fout, &opts)) != ESR_SUCCESS)
        goto CLEANUP;
      pfprintf(fout, "\n");
    }
  }
  else if (*inRTfilename) /*using a test file*/
  {
    if ((fin = pfopen(inRTfilename, "r")) == NULL)
    {
      pfprintf(PSTDOUT, "Could not open test file: %s\n", inRTfilename);
      rc = 1;
      goto CLEANUP;
    }
    
    /*read through the test file parsing it into the variables
     FORMAT: "the transciption" key "value" 
    */
    while (ESR_TRUE)
    {
      if (0) rc = process_single_key_line(grammar, fin, fout);
      else  rc = process_multi_key_line(grammar, rootrule, fin, fout);
      if (rc == ESR_READ_ERROR)
      {
        rc = ESR_SUCCESS;
        break;
      }
    }
  }
  else
  {
    /* get some transcriptions from the user */
    pfprintf(PSTDOUT, "\nSemantic Parser Test Program for esr (Nuance Communicaitions, 2007)\n");
    pfprintf(PSTDOUT, "'qqq' to quit\n");
    
    while (ESR_TRUE)
    {
      pfprintf(PSTDOUT, "> ");

      if (!fgets(trans, MAX_LINE_LENGTH, PSTDIN))
        break;
      // remove trailing whitespace
      for(p=&trans[0]; *p!=0 && *p!='\n' && *p!='\r'; p++) {}
      *p=0;

      if (!LSTRCMP("qqq", trans))
        break;
      else
        if ((rc = Parse(grammar, trans, fout, &opts)) != ESR_SUCCESS)
          goto CLEANUP;
    }
  }
CLEANUP:
  if (fin && fin != PSTDIN)
    pfclose(fin);
  if (fout && fout != PSTDOUT)
    pfclose(fout);
  if (grammar) grammar->destroy(grammar);
  PLogShutdown();
/*  PANSIFileSystemDestroy();
  PFileSystemDestroy();*/
  PMemShutdown();
  return rc;
}

ESR_ReturnCode process_single_key_line(SR_Grammar* grammar, PFile* fin, PFile* fout)
{
  LCHAR* position;
  LCHAR line[MAX_LINE_LENGTH];
  LCHAR trans[MAX_LINE_LENGTH];
  LCHAR key[MAX_LINE_LENGTH];
  LCHAR refValue[MAX_LINE_LENGTH];
  LCHAR result[MAX_LINE_LENGTH];
  ESR_ReturnCode rc;
  
  position = pfgets(line, MAX_LINE_LENGTH, fin);
  if (line[0] == '#')
    return ESR_SUCCESS;
  if (!strncmp(line, "__END__", 7))
    return ESR_READ_ERROR;
  if (position == NULL)
  {
    if (pfeof(fin))
      return ESR_READ_ERROR;
    else
    {
      PLogError(L("ESR_READ_ERROR"));
      return ESR_READ_ERROR;
    }
  }
  
  //get the transcription to test
  if ((position = strtok(line, "\"")) != NULL)
  {
    LSTRCPY(trans, position);
  }
  else
  {
    pfprintf(fout, "INVALID FORMAT for input line 1 \n");
    rc = ESR_INVALID_ARGUMENT;
    goto CLEANUP;
  }
  
  //get the key (meaning)
  if ((position = strtok(NULL, " \t")) != NULL)
  {
    LSTRCPY(key, position);
  }
  else
  {
    pfprintf(fout, "INVALID FORMAT for input line 2\n");
    rc = ESR_INVALID_ARGUMENT;
    goto CLEANUP;
  }
  
  //get the expected return string
  if ((position = strtok(NULL, "\"")) != NULL)
  {
    LSTRCPY(refValue, position);
  }
  else
  {
    pfprintf(fout, "INVALID FORMAT for input line 3\n");
    rc = ESR_INVALID_ARGUMENT;
    goto CLEANUP;
  }
  
  //get the expected result PASS/FAIL
  //there is no need to write PASS, if nothing is written PASS is assumed
  if ((position = strtok(NULL, " \t\r\n\"")) != NULL)
  {
    LSTRCPY(result, position);
    
    if (strcmp(result, "PASS") != 0 && strcmp(result, "FAIL") != 0)
    {
      pfprintf(fout, "INVALID FORMAT for input line, use either PASS or FAIL\n");
      rc = ESR_INVALID_ARGUMENT;
      goto CLEANUP;
    }
    
    if ((rc = ParseTestSet(grammar, trans, key, refValue, result, fout)) != ESR_SUCCESS)
      goto CLEANUP;
  }
  else
  {
    if ((rc = ParseTestSet(grammar, trans, key, refValue, "PASS", fout)) != ESR_SUCCESS)
      goto CLEANUP;
  }
  rc = ESR_SUCCESS;
CLEANUP:
  return rc;
}

ESR_ReturnCode process_multi_key_line(SR_Grammar* grammar, const LCHAR* rootrule, PFile* fin, PFile* fout)
{
  LCHAR *position, *p;
  LCHAR line[MAX_LINE_LENGTH];
  LCHAR trans[MAX_LINE_LENGTH];
  LCHAR keyvals[MAX_LINE_LENGTH];
  ESR_ReturnCode rc;
  SR_SemanticResult* semanticResults[MAX_SEM_RESULTS];
  LCHAR refkey[MAX_LINE_LENGTH];
  LCHAR refval[MAX_LINE_LENGTH], value[MAX_STR_LENGTH];
  size_t i, j, len;
  size_t result_count;
  
  position = pfgets(line, MAX_LINE_LENGTH, fin);
  if (line[0] == '#')
    return ESR_SUCCESS;
  if (!strncmp(line, "__END__", 7))
    return ESR_READ_ERROR;
  if (position == NULL)
  {
    if (pfeof(fin))
      return ESR_READ_ERROR;
    else
    {
      PLogError(L("ESR_READ_ERROR"));
      return ESR_READ_ERROR;
    }
  }
  
  /* we're trying to parse
    Hello there : BONJOUR
   */
  p = strtok(line, ":");
  LSTRCPY(trans, p);
  /* strip trailing spaces */
  for (len = strlen(trans); len > 0 && trans[len-1] == ' '; len--)
    trans[len-1] = 0;
    
  p = strtok(NULL, "\n\r");
  /* strip leading spaces */
  while (*p == ' ' || *p == '\t')  p++;
  LSTRCPY(keyvals, p);
  
  result_count = MAX_SEM_RESULTS;
  for (i = 0; i < result_count; i++)
    SR_SemanticResultCreate(&semanticResults[i]);
    
  /* pfprintf(fout,"checking (%s) ref(%s)\n", trans, keyvals); */
  rc = grammar->checkParse(grammar, trans, semanticResults, (size_t*) & result_count);
  if (rc != ESR_SUCCESS)
    return rc;
    
  /*result file will contain
  transcription | key | reference | result | PASSESD/FAILED */
  
  if (result_count < 1) /*failed to parse, but this could still be a pass if you expected a failure*/
  {
    pfprintf(fout, "%s|%s|  |", trans, keyvals);
    if (!strcmp("FAIL", keyvals) || !strcmp(keyvals, "-"))
      pfprintf(fout, "PASSED\n");
    else
      pfprintf(fout, "FAILED\n");
  }
  else /*parsed, look at what was expected, what was returned and which of PASS/FAIL is expected */
  {
    size_t size, len;
    LCHAR* keys_available[MAX_KEYS]; /* array of pointers to strings */
    size = MAX_KEYS;
    rc = semanticResults[0]->getKeyList(semanticResults[0], (LCHAR**) & keys_available, &size);
    
    for (p = strtok(keyvals, ";"); p; p = strtok(NULL, ";"))
    {
      sprintf(refkey, "%s.%s", rootrule, p);
      p = strchr(refkey, '=');
      assert(p);
      *p = 0;
      p++;
      if (*p == '\'') p++;
      LSTRCPY(refval, p);
      if (refval[ strlen(refval)-1] == '\'') refval[strlen(refval)-1] = 0;
      
      for (i = 0; i < result_count; i++)
      {
        len = MAX_STR_LENGTH;
        for (j = 0; j < size; j++)
          if (!strcmp(keys_available[j], refkey)) break;
        if (j < size)
          rc = semanticResults[i]->getValue(semanticResults[i], refkey, value, &len);
        else
        {
          LSTRCPY(value, "<NOSUCHKEY>");
          rc = ESR_NO_MATCH_ERROR;
        }
        pfprintf(fout, "%s|%s|%s|%s|", trans, refkey, refval, value);
        if (strcmp(value, refval) == 0)
          pfprintf(fout, "PASSED\n");
        else
          pfprintf(fout, "FAILED\n");
      }
    }
    
    /*deallocate semantic results*/
    for (i = 0; i < MAX_SEM_RESULTS; i++)
    {
      rc = semanticResults[i]->destroy(semanticResults[i]);
      if (rc != ESR_SUCCESS)
        PLogError("%s while destroying", ESR_rc2str(rc));
    }
  }
  return ESR_SUCCESS;
}