/* =========================================================================

DESCRIPTION
   Netease && Xunfei speech processing main program
   MSC

Copyright (c) 2017 by Netease, Co,LTD.  All Rights Reserved.
============================================================================ */

/* =========================================================================

                             REVISION

when                       who why
--------                    ---------
-------------------------------------------
2017/06/28          wangzijiao                                  Created.
============================================================================ */
/* ------------------------------------------------------------------------
** Includes
** ------------------------------------------------------------------------ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "buffer.h"
#include "cae.h"
#include "cae_thread.h"
#include "dbus.h"
#include "error.h"
#include <uvdbus/libuv_dbus.h>

#include <alsa/asoundlib.h>

/* ------------------------------------------------------------------------
 *
 * Macros
 *
 * ------------------------------------------------------------------------ */
#define PCM_1S_SIZE 16000 * 16 / 8
#define PCM_10MS_SIZE PCM_1S_SIZE / 100

#define YUNXIN_PCM_BUFFER_BLOCK_SIZE PCM_10MS_SIZE

/* ------------------------------------------------------------------------
 *
 * Defines
 *
 * ------------------------------------------------------------------------ */

static char *alsa_out_dev = (char *)"plug:dmix";
static snd_pcm_hw_params_t *alsa_params = NULL;
static snd_pcm_t *alsa_handle = NULL;

/* ------------------------------------------------------------------------
 *
 * Types
 *
 * ------------------------------------------------------------------------ */

int Netease_player_open() {
    if (alsa_handle) {
        return NETEASE_SUCCESS;
    }
    unsigned int channel = 1;
    unsigned int rate = 16000; /* 16k rate*/

    unsigned int total_frames = 40 * (rate / 1000);  /* 40ms  */
    unsigned int period_frames = 10 * (rate / 1000); /* 10ms */
    unsigned int sample_bit = 16;

    unsigned int one_frame_size = sample_bit / 8 * channel;
    unsigned int buffer_time = total_frames * one_frame_size;
    unsigned int period_time = period_frames * one_frame_size;

    int ret, dir = 0;

    ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
    if (ret < 0) {
        n_error("Alsa initialization failed: unable to open pcm device: %s\n",
                snd_strerror(ret));
        return -1;
    }

    snd_pcm_hw_params_alloca(&alsa_params);

    /* choose all parameters */
    ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_any: %s\n", snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    /* open hardware resampling */
    snd_pcm_hw_params_set_rate_resample(alsa_handle, alsa_params, 1);
    /* set the interleaved read/write format */
    snd_pcm_hw_params_set_access(alsa_handle, alsa_params,
                                 SND_PCM_ACCESS_RW_INTERLEAVED);

    /* set the sample format */
    snd_pcm_hw_params_set_format(alsa_handle, alsa_params, SND_PCM_FORMAT_S16);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_set_format: %s\n", snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    /* set the count of channels */
    snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, channel);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_set_channels: %s\n", snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    /* set the stream rate */
    snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params,
                                    (unsigned int *)&rate, &dir);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_set_rate_near: %s\n", snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    /* set the buffer time */
    snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params,
                                           &buffer_time);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_set_buffer_time_near: %s\n",
                snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    /* set the period time */
    snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params,
                                           &period_time, &dir);
    if (ret < 0) {
        n_error("snd_pcm_hw_params_set_period_time_near: %s\n",
                snd_strerror(ret));
        goto err_pcm_hw_params;
    }

    ret = snd_pcm_hw_params(alsa_handle, alsa_params);
    if (ret < 0) {
        n_error("unable to set hw parameters: %s\n", snd_strerror(ret));
        goto err_pcm_hw;
    }

    return 0;

err_pcm_hw:
err_pcm_hw_params:
    snd_pcm_close(alsa_handle);
    alsa_handle = NULL;

    return -1;
}

/**
 * xrun_recovery - Underrun and suspend recovery
 * @handle:
 * @err:
 */
static int xrun_recovery(snd_pcm_t *handle, int err) {
    if (err == -EPIPE) { /* under-run */
        err = snd_pcm_prepare(handle);
        if (err < 0)
            printf("Can't recovery from underrun, prepare failed: %s\n",
                   snd_strerror(err));
        return 0;
    } else if (err == -ESTRPIPE) {
        while ((err = snd_pcm_resume(handle)) == -EAGAIN)
            sleep(1); /* wait until the suspend flag is released */

        if (err < 0) {
            err = snd_pcm_prepare(handle);
            if (err < 0)
                printf("Can't recovery from suspend, prepare failed: %s\n",
                       snd_strerror(err));
        }
        return 0;
    }

    return err;
}

/**
 * play-
 * @buf:
 * @samples
 */
void Netease_player_play(short buf[], int samples) {
    signed short *ptr = buf;
    int len = samples;
    int err;

    while (len > 0) {
        err = snd_pcm_writei(alsa_handle, ptr, len);

        if (err == -EAGAIN || err == -EINTR)
            continue;

        if (err < 0) {
            /* this might be an error, or an exception */
            err = xrun_recovery(alsa_handle, err);
            if (err < 0) {
                n_error("Failed to write to PCM device: %s\n",
                        snd_strerror(err));
                return;
            } else
                continue;
        }

        /* decrement the sample counter */
        len -= err;
        /* adjust the start pointer */
        ptr += err * 2;
    }
}

void Netease_player_close(void) {
    if (alsa_handle) {
        snd_pcm_drain(alsa_handle);
        snd_pcm_close(alsa_handle);
        alsa_handle = NULL;
    }
}
