/**************************************************************************
 * Copyright (C) 2012-2021  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : lvp_mode_kws.c
 * Author      : yzs.unisound.com
 * Date        : 2021.03.01
 *
 **************************************************************************/

#include <autoconf.h>
#include <stdio.h>
#include <types.h>
#include <driver/uni_delay.h>
#include "app_core/lvp_app_core.h"
#include "lvp_mode.h"
#include "uni_vui_interface.h"
#include "uni_nlu_content.h"
#include "uni_cust_config.h"

#define LOG_TAG "[UNI_KWS]"

static void _write_arpt_log(const char *command, float score, const char *json,
													  char is_wakeup, char is_basic, char is_rasr) {
  char buffer_log[256] = {0};
  char tag[16] = {0};
  char result[16] = {0};
  char kws[8] = {0};
  char pure_cmd[64] = {0};
  char is_more = !(is_basic || is_rasr);
  /* basic content */
  snprintf(tag, sizeof(tag), "%s_%s", is_wakeup ? "asr" : "wakeup",
               "normal");
  snprintf(kws, sizeof(kws), "%s", is_basic ? "KWS\t" : "");
  snprintf(result, sizeof(result), "%s", is_rasr ? "online_json" : "offline_result");
  if (is_rasr) {
    snprintf(buffer_log, sizeof(buffer_log), "%s%s:[%s]\t", kws, result, tag);
  } else {
    //StrSubEx(pure_cmd, command, ">", "</");
    snprintf(pure_cmd, sizeof(pure_cmd), "%s", command);
    snprintf(buffer_log, sizeof(buffer_log), "%s%s:[%s]\tcommand[%s]\tscore[%.02f]\t",
             kws, result, tag, pure_cmd, score);
  }
  ArptPrint(buffer_log);
  /* more content */
  if (!is_more) {
    ArptPrint(json);
  }
  ArptPrint("\n");
}

static unsigned int _bkdr_hash(const char* key) {
  unsigned int seed = 31; // 31 131 1313 13131 131313 etc..
  unsigned int hash = 0;
  unsigned char *cp = (unsigned char *)key;
  if (NULL == cp) {
    printf(LOG_TAG"hash input is NULL");
    return 0;
  }
  while (*cp) {
    hash = (hash * seed + (*cp++)) & 0xFFFFFFFF;
  }
  return hash;
}

static const uni_nul_content_t* _NluParseLasr(const char *key_word) {
  const uni_nul_content_t *result = NULL;
  unsigned int hashCode = _bkdr_hash(key_word);
  int hashTableSize = sizeof(g_nlu_content_mapping) / sizeof(g_nlu_content_mapping[0]);
  int i;

  for (i = 0; i < hashTableSize; i++) {
    /* find same hashcode as keyword's */
    if (hashCode == g_nlu_content_mapping[i].key_word_hash_code) {
      /* return immediately when no hash collision */
      if (NULL == g_nlu_content_mapping[i].hash_collision_orginal_str) {
        result = &g_nlu_content[g_nlu_content_mapping[i].nlu_content_str_index];
        if (result == NULL) {
          return NULL;
        }
        return result;
      }
      /* return when key_word equals hash_collision_orginal_str */
      if (0 == strcmp(key_word, g_nlu_content_mapping[i].hash_collision_orginal_str)) {
        result = &g_nlu_content[g_nlu_content_mapping[i].nlu_content_str_index];
        if (result == NULL) {
          return NULL;
        }
        return result;
      }
    }
  }
  return NULL;
}

static void _VuiResult(const char *word, float score, bool is_timeout) {
  APP_EVENT event = {0};
  const uni_nul_content_t *nlu_content = NULL;
  static uni_kws_result_t kws;
  uni_kws_result_t *attach = &kws;
  memset(attach, 0, sizeof(uni_kws_result_t));
  if (is_timeout) {
    event.event_id = UNI_KWS_SLEEP_EVENT_ID;
    attach->score = 0.0;
    attach->action = "asr_timeout_uni";
#ifdef DEFAULT_PCM_ASR_TIMEOUT
    attach->replay = DEFAULT_PCM_ASR_TIMEOUT;
#elif defined(DEFAULT_PCM_SLEEP)
    attach->replay = DEFAULT_PCM_SLEEP;
#endif
  } else {
    //printf(LOG_TAG"RESULT: word = %s, score = %.02f\n", word, score);
    nlu_content = _NluParseLasr(word);
    if (nlu_content) {
      if (0 == strcmp(nlu_content->action, "wakeup_uni")) {
        if (score < KWS_WAKEUP_SCORE_THRED) {
          _write_arpt_log(word, score, nlu_content->action, VuiRecognGetmodel(), 0, 0);
          return;
        }
        event.event_id = UNI_KWS_WAKEUP_EVENT_ID;
      } else {
        if (score < KWS_CMD_SCORE_THRED) {
          _write_arpt_log(word, score, nlu_content->action, VuiRecognGetmodel(), 0, 0);
          return;
        }
        event.event_id = UNI_KWS_CMD_EVENT_ID;
      }
      strncpy(attach->word, word, sizeof(attach->word) - 1);
      attach->action = nlu_content->action;
      attach->score = score;
      attach->replay = nlu_content->replay_pcm;
      _write_arpt_log(word, score, nlu_content->action, VuiRecognGetmodel(), 1, 0);
    }
  }
  event.ctx_index = 0;
  event.attach = attach;
  LvpTriggerAppEvent(&event);
}

static int _KWSModeInit(LVP_MODE_TYPE prev_mode)
{
  printf(LOG_TAG"Init KWS mode\n");
  if (0 != VuiRecognInit(_VuiResult)) {
    printf(LOG_TAG"VuiRecognInit failed.\n");
    return -1;
  }
  return 0;
}

static void _KWSModeDone(LVP_MODE_TYPE next_mode)
{
  printf(LOG_TAG"Exit KWS mode\n");
  VuiRecognDone();
}

static void _KWSModeTick(void) {
  VuiRecognProcessTick();
}

//-------------------------------------------------------------------------------------------------

const LVP_MODE_INFO lvp_kws_mode_info = {
  .type = LVP_MODE_KWS,
  .init = _KWSModeInit,
  .done = _KWSModeDone,
  .tick = _KWSModeTick,
};
