/***************************************************************************
 *             __________               __   ___
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2014 by Ilia Sergachev: Initial Rockbox port to iBasso DX50
 * Copyright (C) 2014 by Mario Basister: iBasso DX90 port
 * Copyright (C) 2014 by Simon Rothen: Initial Rockbox repository submission, additional features
 * Copyright (C) 2014 by Udo Schläpfer: Code clean up, additional features
 *
 * 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 (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/


#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>

#include "config.h"
#include "debug.h"
#include "panic.h"
#include "pcm.h"
#include "pcm-internal.h"
#include "pcm_sampr.h"
#include "pcm_sink.h"

#include "sound/asound.h"
#include "tinyalsa/asoundlib.h"

#include "debug-ibasso.h"
#include "sysfs-ibasso.h"


/* Tiny alsa handle. */
static struct pcm* _alsa_handle = NULL;


/* Bytes left in the Rockbox PCM frame buffer. */
static size_t _pcm_buffer_size = 0;


/* Rockbox PCM frame buffer. */
static const void  *_pcm_buffer = NULL;


/*
    1: PCM thread suspended.
    0: PCM thread running.
    These are used by pcm_play_[lock|unlock] or pcm_play_dma_[start|stop|pause]. These need to be
    separated because of nested calls for locking and stopping.
*/
static volatile sig_atomic_t _dma_stopped = 1;
static volatile sig_atomic_t _dma_locked  = 1;


/* Mutex for PCM thread suspend/unsuspend. */
static pthread_mutex_t _dma_suspended_mtx = PTHREAD_MUTEX_INITIALIZER;


/* Signal condition for PCM thread suspend/unsuspend. */
static pthread_cond_t _dma_suspended_cond = PTHREAD_COND_INITIALIZER;


static void* pcm_thread_run(void* nothing)
{
    (void) nothing;

    DEBUGF("DEBUG %s: Thread start.", __func__);

    while(true)
    {
        pthread_mutex_lock(&_dma_suspended_mtx);
        while((_dma_stopped == 1) || (_dma_locked == 1))
        {
            DEBUGF("DEBUG %s: Playback suspended.", __func__);
            pthread_cond_wait(&_dma_suspended_cond, &_dma_suspended_mtx);
            DEBUGF("DEBUG %s: Playback resumed.", __func__);
        }
        pthread_mutex_unlock(&_dma_suspended_mtx);

        if(_pcm_buffer_size == 0)
        {
            /* Retrive a new PCM buffer from Rockbox. */
            if(! pcm_play_dma_complete_callback(PCM_DMAST_OK, &_pcm_buffer, &_pcm_buffer_size))
            {
                DEBUGF("DEBUG %s: No new buffer.", __func__);

                usleep( 10000 );
                continue;
            }
        }
        pcm_play_dma_status_callback(PCM_DMAST_STARTED);

        /* This relies on Rockbox PCM frame buffer size == ALSA PCM frame buffer size. */
        if(pcm_write(_alsa_handle, _pcm_buffer, _pcm_buffer_size) != 0)
        {
            DEBUGF("ERROR %s: pcm_write failed: %s.", __func__, pcm_get_error(_alsa_handle));

            usleep( 10000 );
            continue;
        }

        _pcm_buffer_size = 0;

        /*DEBUGF("DEBUG %s: Thread running.", __func__);*/
    }

    DEBUGF("DEBUG %s: Thread end.", __func__);

    return 0;
}


#ifdef DEBUG

/* https://github.com/tinyalsa/tinyalsa/blob/master/tinypcminfo.c */

static const char* const format_lookup[] =
{
    /*[0] =*/ "S8",
    "U8",
    "S16_LE",
    "S16_BE",
    "U16_LE",
    "U16_BE",
    "S24_LE",
    "S24_BE",
    "U24_LE",
    "U24_BE",
    "S32_LE",
    "S32_BE",
    "U32_LE",
    "U32_BE",
    "FLOAT_LE",
    "FLOAT_BE",
    "FLOAT64_LE",
    "FLOAT64_BE",
    "IEC958_SUBFRAME_LE",
    "IEC958_SUBFRAME_BE",
    "MU_LAW",
    "A_LAW",
    "IMA_ADPCM",
    "MPEG",
    /*[24] =*/ "GSM",
    [31] = "SPECIAL",
    "S24_3LE",
    "S24_3BE",
    "U24_3LE",
    "U24_3BE",
    "S20_3LE",
    "S20_3BE",
    "U20_3LE",
    "U20_3BE",
    "S18_3LE",
    "S18_3BE",
    "U18_3LE",
    /*[43] =*/ "U18_3BE"
};


static const char* pcm_get_format_name(unsigned int bit_index)
{
    return(bit_index < 43 ? format_lookup[bit_index] : NULL);
}

#endif


/* Thread that copies the Rockbox PCM buffer to ALSA. */
static pthread_t _pcm_thread;


/* ALSA card and device. */
static const unsigned int CARD   = 0;
static const unsigned int DEVICE = 0;


/* ALSA config. */
static struct pcm_config _config;


static void sink_dma_init(void)
{
    TRACE;

#ifdef DEBUG

    /*
        DEBUG sink_dma_init: Access: 0x000009
        DEBUG sink_dma_init: Format[0]: 0x000044
        DEBUG sink_dma_init: Format[1]: 0x000010
        DEBUG sink_dma_init: Format: S16_LE
        DEBUG sink_dma_init: Format: S24_LE
        DEBUG sink_dma_init: Format: S20_3LE
        DEBUG sink_dma_init: Subformat: 0x000001
        DEBUG sink_dma_init: Rate: min = 8000Hz, max = 192000Hz
        DEBUG sink_dma_init: Channels: min = 2, max = 2
        DEBUG sink_dma_init: Sample bits: min=16, max=32
        DEBUG sink_dma_init: Period size: min=8, max=10922
        DEBUG sink_dma_init: Period count: min=3, max=128
        DEBUG sink_dma_init: 0 mixer controls.
    */

    struct pcm_params* params = pcm_params_get(CARD, DEVICE, PCM_OUT);
    if(params == NULL)
    {
        DEBUGF("ERROR %s: Card/device does not exist.", __func__);
        panicf("ERROR %s: Card/device does not exist.", __func__);
        return;
    }

    struct pcm_mask* m = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
    if(m)
    {
        DEBUGF("DEBUG %s: Access: %#08x", __func__, m->bits[0]);
    }

    m = pcm_params_get_mask(params, PCM_PARAM_FORMAT);
    if(m)
    {
        DEBUGF("DEBUG %s: Format[0]: %#08x", __func__, m->bits[0]);
        DEBUGF("DEBUG %s: Format[1]: %#08x", __func__, m->bits[1]);

        unsigned int j;
        unsigned int k;
        const unsigned int bitcount = sizeof(m->bits[0]) * 8;
        for(k = 0; k < 2; ++k)
        {
            for(j = 0; j < bitcount; ++j)
            {
                const char* name;
                if(m->bits[k] & (1 << j))
                {
                    name = pcm_get_format_name(j + (k * bitcount));
                    if(name)
                    {
                        DEBUGF("DEBUG %s: Format: %s", __func__, name);
                    }
                }
            }
        }
    }

    m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT);
    if(m)
    {
        DEBUGF("DEBUG %s: Subformat: %#08x", __func__, m->bits[0]);
    }

    unsigned int min = pcm_params_get_min(params, PCM_PARAM_RATE);
    unsigned int max = pcm_params_get_max(params, PCM_PARAM_RATE) ;
    DEBUGF("DEBUG %s: Rate: min = %uHz, max = %uHz", __func__, min, max);

    min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
    max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
    DEBUGF("DEBUG %s: Channels: min = %u, max = %u", __func__, min, max);

    min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
    max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
    DEBUGF("DEBUG %s: Sample bits: min=%u, max=%u", __func__, min, max);

    min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
    max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
    DEBUGF("DEBUG %s: Period size: min=%u, max=%u", __func__, min, max);

    min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
    max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
    DEBUGF("DEBUG %s: Period count: min=%u, max=%u", __func__, min, max);

    pcm_params_free(params);

    struct mixer* mixer = mixer_open(CARD);
    if(! mixer)
    {
        DEBUGF("ERROR %s: Failed to open mixer.", __func__);
    }
    else
    {
        int num_ctls = mixer_get_num_ctls(mixer);

        DEBUGF("DEBUG %s: %d mixer controls.", __func__, num_ctls);

        mixer_close(mixer);
    }

#endif

    if(_alsa_handle != NULL)
    {
        DEBUGF("ERROR %s: Allready initialized.", __func__);
        panicf("ERROR %s: Allready initialized.", __func__);
        return;
    }

    /*
        Rockbox outputs 16 Bit/44.1kHz stereo by default.

        ALSA frame buffer size = config.period_count * config.period_size * config.channels * (16 \ 8)
                               = 4 * 256 * 2 * 2
                               = 4096
                               = Rockbox PCM buffer size
        pcm_thread_run relies on this size match. See pcm_mixer.h.
    */
    _config.channels          = 2;
    _config.rate              = hw_freq_sampr[HW_FREQ_DEFAULT];
    _config.period_size       = 256;
    _config.period_count      = 4;
    _config.format            = PCM_FORMAT_S16_LE;
    _config.start_threshold   = 0;
    _config.stop_threshold    = 0;
    _config.silence_threshold = 0;

    _alsa_handle = pcm_open(CARD, DEVICE, PCM_OUT, &_config);
    if(! pcm_is_ready(_alsa_handle))
    {
        DEBUGF("ERROR %s: pcm_open failed: %s.", __func__, pcm_get_error(_alsa_handle));
        panicf("ERROR %s: pcm_open failed: %s.", __func__, pcm_get_error(_alsa_handle));
        return;
    }

    DEBUGF("DEBUG %s: ALSA PCM frame buffer size: %d.", __func__, pcm_frames_to_bytes(_alsa_handle, pcm_get_buffer_size(_alsa_handle)));

    /* Create pcm thread in the suspended state. */
    pthread_mutex_lock(&_dma_suspended_mtx);
    _dma_stopped = 1;
    _dma_locked  = 1;
    pthread_create(&_pcm_thread, NULL, pcm_thread_run, NULL);
    pthread_mutex_unlock(&_dma_suspended_mtx);
}


static void sink_dma_start(const void *addr, size_t size)
{
    TRACE;

    /*
        DX50
        /sys/class/codec/mute
        Mute:   echo 'A' > /sys/class/codec/mute
        Unmute: echo 'B' > /sys/class/codec/mute

        DX90?
    */
    if(! sysfs_set_char(sysfs_paths[SYSFS_MUTE], 'B'))
    {
        DEBUGF("ERROR %s: Could not unmute.", __func__);
        panicf("ERROR %s: Could not unmute.", __func__);
    }

    _pcm_buffer      = addr;
    _pcm_buffer_size = size;

    pthread_mutex_lock(&_dma_suspended_mtx);
    _dma_stopped = 0;
    pthread_cond_signal(&_dma_suspended_cond);
    pthread_mutex_unlock(&_dma_suspended_mtx);
}

static void sink_dma_stop(void)
{
    TRACE;

    pthread_mutex_lock(&_dma_suspended_mtx);
    _dma_stopped = 1;
    pcm_stop(_alsa_handle);
    pthread_mutex_unlock(&_dma_suspended_mtx);
}


/* Unessecary play locks before sink_dma_postinit. */
static int _play_lock_recursion_count = -10000;


static void sink_dma_postinit(void)
{
    TRACE;

    _play_lock_recursion_count = 0;
}


static void sink_lock(void)
{
    TRACE;

    ++_play_lock_recursion_count;

    if(_play_lock_recursion_count == 1)
    {
        pthread_mutex_lock(&_dma_suspended_mtx);
        _dma_locked = 1;
        pthread_mutex_unlock(&_dma_suspended_mtx);
    }
}


static void sink_unlock(void)
{
    TRACE;

    --_play_lock_recursion_count;

    if(_play_lock_recursion_count == 0)
    {
        pthread_mutex_lock(&_dma_suspended_mtx);
        _dma_locked = 0;
        pthread_cond_signal(&_dma_suspended_cond);
        pthread_mutex_unlock(&_dma_suspended_mtx);
    }
}


static void sink_set_freq(uint16_t freq)
{
    unsigned int rate = hw_freq_sampr[freq];

    DEBUGF("DEBUG %s: Current sample rate: %u, next sampe rate: %u.", __func__, _config.rate, rate);

    if(( _config.rate != rate) && (rate >= 8000) && (rate <= 192000))
    {
        _config.rate = rate;

        pcm_close(_alsa_handle);
        _alsa_handle = pcm_open(CARD, DEVICE, PCM_OUT, &_config);

        if(! pcm_is_ready(_alsa_handle))
        {
            DEBUGF("ERROR %s: pcm_open failed: %s.", __func__, pcm_get_error(_alsa_handle));
            panicf("ERROR %s: pcm_open failed: %s.", __func__, pcm_get_error(_alsa_handle));
        }
    }
}

void pcm_close_device(void)
{
    TRACE;

    pthread_mutex_lock(&_dma_suspended_mtx);
    _dma_stopped = 1;
    pthread_mutex_unlock(&_dma_suspended_mtx);

    pcm_close(_alsa_handle);
    _alsa_handle = NULL;
}

struct pcm_sink builtin_pcm_sink = {
    .caps = {
        .samprs       = hw_freq_sampr,
        .num_samprs   = HW_NUM_FREQ,
        .default_freq = HW_FREQ_DEFAULT,
    },
    .ops = {
        .init     = sink_dma_init,
        .postinit = sink_dma_postinit,
        .set_freq = sink_set_freq,
        .lock     = sink_lock,
        .unlock   = sink_unlock,
        .play     = sink_dma_start,
        .stop     = sink_dma_stop,
    },
};
