/******************************************************************************!
 * \file player-mpd.c
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright CeCILL 2.1 Free Software license
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mpd/client.h>
#include "player.h"
#include "mp3server.h"
#include "log.h"
#include "html.h"
#include "common.h"

const int32_t STATE_UNKNOWN = 0;
const int32_t STATE_PLAY = 2;
const int32_t STATE_PAUSE = 3;

struct mpd_connection* gConn = NULL;

/******************************************************************************!
 * \fn playerIsError
 ******************************************************************************/
int
playerIsError(const char* func)
{
    enum mpd_error err = mpd_connection_get_error(gConn);
    if (err != MPD_ERROR_SUCCESS) {
        if (err == MPD_ERROR_CLOSED ||
            err == MPD_ERROR_SYSTEM) {
            return err;
        }
        ERROR("%s: %d: %s", func, err, mpd_connection_get_error_message(gConn));
        mpd_connection_clear_error(gConn);
        return -1;
    }
    return 0;
}

/******************************************************************************!
 * \fn playerInit
 ******************************************************************************/
int
playerInit()
{
    if (gConn != NULL) {
        mpd_connection_free(gConn);
    }
    gConn = mpd_connection_new("/run/mpd.sock", 0, 0);
    if (gConn == NULL) {
        ERROR("gConn == NULL");
        return 1;
    }
    if (playerIsError(__FUNCTION__)) {
        mpd_connection_free(gConn);
        gConn = NULL;
        return 2;
    }
    if (! mpd_connection_set_keepalive(gConn, true)) {
        ERROR("set_keepalive");
        return 3;
    }
    return 0;
}

/******************************************************************************!
 * \fn playerGetMPDStatus
 ******************************************************************************/
struct mpd_status*
playerGetMPDStatus()
{
    int res;
    struct mpd_status* status = mpd_run_status(gConn);
    res = playerIsError(__FUNCTION__);
    if (res == MPD_ERROR_CLOSED ||
        res == MPD_ERROR_SYSTEM) {
        if (status != NULL) {
            mpd_status_free(status);
        }
        DEBUG("reconnect");
        playerInit();
        status = mpd_run_status(gConn);
        if (playerIsError(__FUNCTION__)) {
        }
    }
    return status;
}

/******************************************************************************!
 * \fn playerGetStatus
 ******************************************************************************/
int32_t
playerGetStatus()
{
    enum mpd_state res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return STATE_UNKNOWN;
    }

    res = mpd_status_get_state(status);
    mpd_status_free(status);

    /*  */ if (res == MPD_STATE_PLAY) {
        return STATE_PLAY;
    } else if (res == MPD_STATE_PAUSE) {
        return STATE_PAUSE;
    }
    return STATE_UNKNOWN;
}

/******************************************************************************!
 * \fn playerGetPlaytime
 ******************************************************************************/
int32_t
playerGetPlaytime()
{
    unsigned int res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return 0;
    }

    res = mpd_status_get_elapsed_ms(status);
    mpd_status_free(status);

    return res;
}

/******************************************************************************!
 * \fn playerGetPosition
 ******************************************************************************/
int
playerGetPosition()
{
    unsigned int res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return 0;
    }

    res = mpd_status_get_song_pos(status);
    mpd_status_free(status);
    DEBUG("pos = %u", res);

    return res;
}

/******************************************************************************!
 * \fn playerTitleList
 ******************************************************************************/
struct Buffer*
playerTitleList(struct Buffer* buffer, enum tFormat format)
{
    int32_t playtime;
    int pos;
    unsigned int duration;
    struct mpd_song* song = NULL;
    int count = 0;
    char href[LINE_SIZE];
    FILE* buffFile = bufferInit(buffer);
    int length;
    const char* tag;

    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return buffer;
    }

    pos = mpd_status_get_song_pos(status);
    DEBUG("pos = %d", pos);
    length = mpd_status_get_queue_length(status);
    DEBUG("length = %d", length);
    mpd_status_free(status);

    if (format == HTML) {
        fseek(buffFile, httpGetHttpOkSize(), SEEK_SET);
        fprintf(buffFile, "%s", htmlGetBegin());
    }

    for (count = 0; count < length; ++count) {
        song = mpd_run_get_queue_song_pos(gConn, count);
        if (playerIsError(__FUNCTION__)) {
            if (song != NULL) {
                mpd_song_free(song);
            }
            return buffer;
        }
        if (song == NULL) {
            return buffer;
        }
        DEBUG("id = %d", mpd_song_get_id(song));
        if (count == pos) {
            fprintf(buffFile, "->");
            DEBUG("count == pos");
        } else {
            fprintf(buffFile, "  ");
        }

        fprintf(buffFile, "[%02d] ", count + 1);
        tag = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
        if (tag != NULL) {
            fprintf(buffFile, "%s", tag);
        }
        fprintf(buffFile, " - ");
        tag = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
        if (tag != NULL) {
            if (count == pos) {
                fprintf(buffFile, "%s", tag);
            } else {
                if (format == HTML) {
                    sprintf(href, "<a href=\"/pos/%d\">%s</a>", count, tag);
                    fprintf(buffFile, "%s", href);
                } else {
                    fprintf(buffFile, "%s", tag);
                }
            }
            DEBUG("%s", tag);
        }
        duration = mpd_song_get_duration_ms(song);
        duration /= 1000;
        if (count == pos) {
            playtime = playerGetPlaytime();
            if (playtime != 0) {
                playtime /= 1000;
                fprintf(buffFile,
                        " (%02d:%02d/%02u:%02u)",
                        playtime / 60,
                        playtime % 60,
                        duration / 60,
                        duration % 60);
            } else {
                fprintf(buffFile, " (%02u:%02u)",
                        duration / 60,
                        duration % 60);
            }
        } else {
            fprintf(buffFile, " (%02u:%02u)",
                    duration / 60,
                    duration % 60);
        }
        if (format == HTML) {
            fprintf(buffFile, "<br/>");
        } else {
            fprintf(buffFile, "\n");
        }
        mpd_song_free(song);
    }

    if (format == HTML) {
        fprintf(buffFile, "%s", htmlGetEnd());
    }

    return buffer;
}

/******************************************************************************!
 * \fn playerStop
 ******************************************************************************/
void
playerStop()
{
    bool res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return;
    }
    mpd_status_free(status);

    res = mpd_run_stop(gConn);
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_stop");
    }
}

/******************************************************************************!
 * \fn playerStart
 ******************************************************************************/
void
playerStart()
{
    bool res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return;
    }
    mpd_status_free(status);

    res = mpd_run_play(gConn);
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_play");
    }
}

/******************************************************************************!
 * \fn playerStartId
 ******************************************************************************/
void
playerStartId(int pos)
{
    bool res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return;
    }
    mpd_status_free(status);

    if (pos < 0) {
        res = mpd_run_previous(gConn);
    } else {
        res = mpd_run_next(gConn);
    }
    if (playerIsError(__FUNCTION__)) {
        return;
    }
    if (! res) {
        ERROR("mpd_run_ previous next");
        return;
    }
    playerStart();
}

/******************************************************************************!
 * \fn playerStartRel
 ******************************************************************************/
void
playerStartRel(int pos)
{
    bool res;
    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        return;
    }
    mpd_status_free(status);

    if (pos > 0) {
        res = mpd_run_next(gConn);
    } else {
        res = mpd_run_previous(gConn);
    }
    if (playerIsError(__FUNCTION__)) {
        return;
    }
    if (! res) {
        ERROR("mpd_run_ next previous");
        return;
    }
    playerStart();
}

/******************************************************************************!
 * \fn playerPause
 ******************************************************************************/
void
playerPause()
{
    bool res;
    if (playerGetStatus() == STATE_PAUSE) {
        res = mpd_run_play(gConn);
    } else {
        res = mpd_run_pause(gConn, true);
    }
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_ play pause");
    }
}

/******************************************************************************!
 * \fn playerResume
 ******************************************************************************/
void
playerResume()
{
    bool res;
    int32_t status;
    char filename[LINE_SIZE];
    FILE* fp;
    int milliseconds;

    strcpy(filename, mp3serverGetMp3rootDir());
    strcat(filename, "/.mp3time");
    fp = fopen(filename, "r");
    if (fp == NULL) {
        return;
    }
    if (fscanf(fp, "%10d", &milliseconds) != 1) {
        fclose(fp);
        return;
    }
    fclose(fp);
    DEBUG("milliseconds = %d", milliseconds);

    // Status
    status = playerGetStatus();
    if (status != STATE_PLAY) {
        playerStart();
        sleep(2);
    }

    // Seek
    res = mpd_run_seek_current(gConn, milliseconds / 1000.0, false);
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_seek_current");
    }

    unlink(filename);
}

/******************************************************************************!
 * \fn playerM3u
 ******************************************************************************/
void
playerM3u(const char* m3u)
{
    bool res;

    playerStop();

    res = mpd_run_clear(gConn);
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_clear");
    }

    DEBUG("m3u = %s", m3u);
    res = mpd_run_load(gConn, m3u);
    if (playerIsError(__FUNCTION__)) {
    }
    if (! res) {
        ERROR("mpd_run_load");
    }

    playerStart();
}

/******************************************************************************!
 * \fn playerCurrentTitle
 ******************************************************************************/
struct Buffer*
playerCurrentTitle(struct Buffer* buffer)
{
    FILE* buffFile = bufferInit(buffer);

    int32_t status;
    int pos;
    struct mpd_song* song = NULL;
    const char* tag;
#   if defined(__arm__) || defined(__aarch64__)
    const char* c;
#   endif

    // Status
    status = playerGetStatus();
    if (status == STATE_PAUSE) {
        fprintf(buffFile, "PAUSE ");
    }

    // Position
    pos = playerGetPosition();
    fprintf(buffFile, "%02d ", pos + 1);
    if (pos < 0) {
        return buffer;
    }

    // Title
    song = mpd_run_get_queue_song_pos(gConn, pos);
    if (playerIsError(__FUNCTION__)) {
        if (song != NULL) {
            mpd_song_free(song);
        }
        return buffer;
    }
    if (song == NULL) {
        ERROR("mpd_run_get_queue_song_pos");
        return buffer;
    }
    tag = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
#   if defined(__arm__) || defined(__aarch64__)
    if (tag != NULL) {
        for (c = tag; *c != '\0'; ++c) {
            if (c[0] == ' ' &&
                c[1] == '-' &&
                c[2] == ' ') {
                tag = c + 3;
                break;
            }
        }
    }
#   endif
    if (tag != NULL) {
        fprintf(buffFile, "%s", tag);
    }
    mpd_song_free(song);

    return buffer;
}

/******************************************************************************!
 * \fn playerQuit
 ******************************************************************************/
void
playerQuit()
{
    if (gConn == NULL) {
        ERROR("gConn == NULL");
        return;
    }

    struct mpd_status* status = playerGetMPDStatus();
    if (status == NULL) {
        gConn = NULL;
        return;
    }
    mpd_status_free(status);

    mpd_connection_free(gConn);
    gConn = NULL;
}