/****************************************************************************
 * apps/testing/mm/opus_ramtest/opus_ramtest_main.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <debug.h>
#include <malloc.h>
#include <pthread.h>
#include <sched.h>
#include <sys/param.h>
#include <netutils/md5.h>

#include "wav.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* The default folder to write to,
 * once there is a - f parameter passed, this value is invalid
 */

#define DEFAULT_ENCODE_FILE_PATH "/data/opus_encode_file"

/* Path for comparing MD5 gold generated by default testing */

#define DEFAULT_GENR_FILE_PATH "/tmp/golden"

#define DEFAULT_THREAD_NUM    5

#define OLOG(...) printf(__VA_ARGS__)

/****************************************************************************
 * Private Type
 ****************************************************************************/

struct opus_threads_arg_s
{
  char *path;
  unsigned char digest[16];
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/* This api is exported by the opus, see:
 * external/opus/opus/src/opus_demo.c
 */

int opus_demo_main(int argc, char *argv[]);

/****************************************************************************
 * Name: opus_decode_process
 ****************************************************************************/

static int opus_decode_process(char *encode_path,
                               char *genr_path, unsigned char *digest)
{
  int ret;
  char *argv[] =
  {
    "opus_demo",
    "voip",
    "48000",
    "1",
    "48000",
    DEFAULT_ENCODE_FILE_PATH,
    genr_path,
    NULL,
  };

  if (NULL != encode_path)
    {
      argv[5] = encode_path;
    }

  ret = opus_demo_main(nitems(argv) - 1, argv);
  if (ret < 0)
    {
      OLOG("opus file %s failed, ret=%d\n",
            genr_path, ret);
      return ret;
    }

  ret = md5_file(genr_path, digest);
  if (ret < 0)
    {
      OLOG("md5 file %s failed, ret=%d\n",
            genr_path, ret);
    }

  ret = remove(genr_path);
  if (ret < 0)
    {
      OLOG("remove file %s failed, ret=%d\n",
            genr_path, ret);
    }

  return ret;
}

/****************************************************************************
 * Name: opus_ramtest_thread
 ****************************************************************************/

static void *opus_ramtest_thread(void *arg)
{
  int ret;
  char *path;
  unsigned char digest[16];
  struct opus_threads_arg_s *t_arg = (struct opus_threads_arg_s *)arg;

  /* Set parameters passed in by opus */

  asprintf(&path, "/tmp/opus%d", pthread_self());

  for (; ; )
    {
      /* Decode the file */

      ret = opus_decode_process(t_arg->path, path, digest);
      if (ret < 0)
        {
          OLOG("opus decode process failed,"
               "pid: %d ret=%d\n", pthread_self(), ret);
          break;
        }

      /* Compare the standard value with the test value */

      if (memcmp(digest, t_arg->digest, sizeof(digest)) != 0)
        {
          OLOG("TEST FAILED, encode result not equal to the golden,"
               "pid=%d\n", pthread_self());
          lib_dumpbuffer("digest", digest, sizeof(digest));
          lib_dumpbuffer("std->digest", t_arg->digest,
                         sizeof(t_arg->digest));
          break;
        }

      memset(digest, 0, sizeof(digest));
    }

  free(path);
  return NULL;
}

int main(int argc, char *argv[])
{
  int c;
  int i;
  int fd;
  int ret;

  /* Used to determine whether a file needs to be written */

  char *path = NULL;
  bool is_created = false;

  /* Thread Information */

  int thread_num = DEFAULT_THREAD_NUM;
  pthread_t *threads;
  pthread_attr_t attr;
  struct sched_param param;
  struct opus_threads_arg_s t_arg;

  char *usage = "[-f <file>] [-n <thread num>] "
                "[-s <stack size>] [-r <priority>]\n";

  pthread_attr_init(&attr);
  sched_getparam(getpid(), &param);

  /* Extract incoming parameters */

  while ((c = getopt(argc, argv, "f:n:s:r:")) != ERROR)
    {
      switch (c)
        {
          case 'f':
            path = optarg;
            break;
          case 'n':
            thread_num = atoi(optarg);
            break;
          case 's':
            pthread_attr_setstacksize(&attr, atoi(optarg));
            break;
          case 'r':
            param.sched_priority = atoi(optarg);
            break;
          default:
            perror(usage);
            return EXIT_FAILURE;
        }
    }

  pthread_attr_setschedparam(&attr, &param);

  /* Dynamic application thread parameter information */

  threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_num);
  if (threads == NULL)
    {
      return EXIT_FAILURE;
    }

  /* If the conditions are met,
   * it is necessary to create a file,
   * and write the wav array information into the file
   */

  if (!path)
    {
      path = DEFAULT_ENCODE_FILE_PATH;
      fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
      if (fd < 0)
        {
            perror("open");
            goto exit;
        }

      if (write(fd, g_wav_data, g_wav_len) < 0)
        {
          perror("write");
          close(fd);
          goto exit;
        }

      is_created = true;
      close(fd);
    }

  /* Calculate the standard keyvalue */

  ret = opus_decode_process(path, DEFAULT_GENR_FILE_PATH, t_arg.digest);
  if (ret < 0)
    {
      OLOG("opus_decode_process failed, ret=%d\n", ret);
      goto exit;
    }

  t_arg.path = path;
  for (i = 0; i < thread_num; i++)
    {
      ret = pthread_create(&threads[i], &attr,
                           opus_ramtest_thread,
                           &t_arg);
      if (ret < 0)
        {
          OLOG("pthread create %d failed, ret=%d\n", i, ret);
          break;
        }
    }

  while (i--)
    {
      pthread_join(threads[i], NULL);
    }

exit:
  if (is_created)
    {
      remove(path);
    }

  free(threads);

  return EXIT_SUCCESS;
}