/* myMPD
   (c) 2018-2019 Juergen Mang <mail@jcgames.de>
   This project's homepage is: https://github.com/jcorporation/mympd
   
   myMPD ist fork of:
   
   ympd
   (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
   This project's homepage is: http://www.ympd.org
   
   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; version 2 of the License.

   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.,
   Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <libgen.h>
#include <dirent.h>
#include <stdbool.h>

#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "mympd_api.h"
#include "mpd_client.h"
#include "../dist/src/frozen/frozen.h"

//private definitions
typedef struct t_mympd_state {
    //notifications
    bool notificationWeb;
    bool notificationPage;

    //jukebox
    enum jukebox_modes jukeboxMode;
    char *jukeboxPlaylist;
    int jukeboxQueueLength;

    bool autoPlay;

    //columns
    char *colsQueueCurrent;
    char *colsSearch;
    char *colsBrowseDatabase;
    char *colsBrowsePlaylistsDetail;
    char *colsBrowseFilesystem;
    char *colsPlayback;
    char *colsQueueLastPlayed;
    
    //system commands
    struct list syscmd_list;
} t_mympd_state;

static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request);
static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state);
static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd);
static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state);
static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer);


//public functions
void *mympd_api_loop(void *arg_config) {
    t_config *config = (t_config *) arg_config;
    
    //read myMPD states under config.varlibdir
    t_mympd_state mympd_state;
    mympd_api_read_statefiles(config, &mympd_state);

    //push jukebox settings to mpd_client queue
    t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
    mpd_client_request->conn_id = 0;
    mpd_client_request->cmd_id = MYMPD_API_SETTINGS_SET;
    mpd_client_request->length = snprintf(mpd_client_request->data, 1000, 
        "{\"cmd\":\"MYMPD_API_SETTINGS_SET\", \"data\":{\"jukeboxMode\": %d, \"jukeboxPlaylist\": \"%s\", \"jukeboxQueueLength\": %d}}",
        mympd_state.jukeboxMode,
        mympd_state.jukeboxPlaylist,
        mympd_state.jukeboxQueueLength
    );
    tiny_queue_push(mpd_client_queue, mpd_client_request);

    //read system command files
    list_init(&mympd_state.syscmd_list);
    bool rc = mympd_api_read_syscmds(config, &mympd_state);
    if (rc == true) {
        list_sort_by_value(&mympd_state.syscmd_list, true);
    }

    while (s_signal_received == 0) {
        struct t_work_request *request = tiny_queue_shift(mympd_api_queue, 0);
        if (request != NULL) {
            mympd_api(config, &mympd_state, request);
        }
    }

    list_free(&mympd_state.syscmd_list);
    free(mympd_state.jukeboxPlaylist);
    free(mympd_state.colsQueueCurrent);
    free(mympd_state.colsSearch);
    free(mympd_state.colsBrowseDatabase);
    free(mympd_state.colsBrowsePlaylistsDetail);
    free(mympd_state.colsBrowseFilesystem);
    free(mympd_state.colsPlayback);
    free(mympd_state.colsQueueLastPlayed);
    return NULL;
}

//private functions
static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request) {
    //t_work_request *request = (t_work_request *) arg_request;
    size_t len = 0;
    char buffer[MAX_SIZE];
    int je;
    char *p_charbuf1;
    char p_char[4];
    LOG_VERBOSE() printf("MYMPD API request: %.*s\n", request->length, request->data);
    
    if (request->cmd_id == MYMPD_API_SYSCMD) {
        if (config->syscmds == true) {
            je = json_scanf(request->data, request->length, "{data: {cmd: %Q}}", &p_charbuf1);
            if (je == 1) {
                len = mympd_api_syscmd(config, mympd_state, buffer, p_charbuf1);
                free(p_charbuf1);
            }
        } 
        else {
            len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System commands are disabled.\"}");
        }
    }
    else if (request->cmd_id == MYMPD_API_COLS_SAVE) {
        je = json_scanf(request->data, request->length, "{data: {table: %Q}}", &p_charbuf1);
        if (je == 1) {
            char column_list[800];
            snprintf(column_list, 800, "%.*s", request->length, request->data);
            char *cols = strchr(column_list, '[');
            int col_len = strlen(cols); 
            if (col_len > 1)
                cols[col_len - 2]  = '\0';
            if (strcmp(p_charbuf1, "colsQueueCurrent") == 0) {
                free(mympd_state->colsQueueCurrent);
                mympd_state->colsQueueCurrent = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsSearch") == 0) {
                free(mympd_state->colsSearch);
                mympd_state->colsSearch = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsBrowseDatabase") == 0) {
                free(mympd_state->colsBrowseDatabase);
                mympd_state->colsBrowseDatabase = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsBrowsePlaylistsDetail") == 0) {
                free(mympd_state->colsBrowsePlaylistsDetail);
                mympd_state->colsBrowsePlaylistsDetail = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsBrowseFilesystem") == 0) {
                free(mympd_state->colsBrowseFilesystem);
                mympd_state->colsBrowseFilesystem = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsPlayback") == 0) {
                free(mympd_state->colsPlayback);
                mympd_state->colsPlayback = strdup(cols);
            }
            else if (strcmp(p_charbuf1, "colsQueueLastPlayed") == 0) {
                free(mympd_state->colsQueueLastPlayed);
                mympd_state->colsQueueLastPlayed = strdup(cols);
            }
            else {
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown table %s\"}", p_charbuf1);
                printf("MYMPD_API_COLS_SAVE: Unknown table %s\n", p_charbuf1);
            }
            if (len == 0) {
                if (state_file_write(config, p_charbuf1, cols))
                    len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
            }
            free(p_charbuf1);
        }
    }
    else if (request->cmd_id == MYMPD_API_SETTINGS_SET) {
        je = json_scanf(request->data, request->length, "{data: {notificationWeb: %B}}", &mympd_state->notificationWeb);
        if (je == 1) {
            if (!state_file_write(config, "notificationWeb", (mympd_state->notificationWeb == true ? "true" : "false")))
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationWeb.\"}");
        }    
        je = json_scanf(request->data, request->length, "{data: {notificationPage: %B}}", &mympd_state->notificationPage);
        if (je == 1) {
            if (!state_file_write(config, "notificationPage", (mympd_state->notificationPage == true ? "true" : "false")))
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationPage.\"}");
        }
        je = json_scanf(request->data, request->length, "{data: {autoPlay: %B}}", &mympd_state->autoPlay);
        if (je == 1) {
            if (!state_file_write(config, "autoPlay", (mympd_state->autoPlay == true ? "true" : "false")))
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state autoPlay.\"}");
        }
        je = json_scanf(request->data, request->length, "{data: {jukeboxMode: %d}}", &mympd_state->jukeboxMode);
        if (je == 1) {
            snprintf(p_char, 4, "%d", mympd_state->jukeboxMode);
            if (!state_file_write(config, "jukeboxMode", p_char))
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxMode.\"}");
        }
        je = json_scanf(request->data, request->length, "{data: {jukeboxPlaylist: %Q}}", &mympd_state->jukeboxPlaylist);
        if (je == 1) {
            if (!state_file_write(config, "jukeboxPlaylist", mympd_state->jukeboxPlaylist))
                len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxPlaylist.\"}");
        }
        je = json_scanf(request->data, request->length, "{data: {jukeboxQueueLength: %d}}", &mympd_state->jukeboxQueueLength);
        if (je == 1) {
           snprintf(p_char, 4, "%d", mympd_state->jukeboxQueueLength);
           if (!state_file_write(config, "jukeboxQueueLength", p_char))
               len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxQueueLength.\"}");
        }
        if (len == 0) {
            len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
        }
        //push settings to mpd_client queue
        t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
        mpd_client_request->conn_id = request->conn_id;
        mpd_client_request->cmd_id = request->cmd_id;
        mpd_client_request->length = copy_string(mpd_client_request->data, request->data, 1000, request->length);
        tiny_queue_push(mpd_client_queue, mpd_client_request);
    }
    else if (request->cmd_id == MYMPD_API_SETTINGS_GET) {
        len = mympd_api_put_settings(config, mympd_state, buffer);
    }
    else {
        len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown cmd_id %u.\"}", request->cmd_id);
        printf("ERROR: Unknown cmd_id %u\n", request->cmd_id);    
    }

    if (len == 0) {
        len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"No response for cmd_id %u.\"}", request->cmd_id);
        printf("ERROR: No response for cmd_id %u\n", request->cmd_id);
    }
    LOG_DEBUG() fprintf(stderr, "DEBUG: Send http response to connection %lu (first 800 chars):\n%*.*s\n", request->conn_id, 0, 800, buffer);

    t_work_result *response = (t_work_result *)malloc(sizeof(t_work_result));
    response->conn_id = request->conn_id;
    response->length = copy_string(response->data, buffer, MAX_SIZE, len);
    tiny_queue_push(web_server_queue, response);

    free(request);
}

static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state) {
    DIR *dir;
    struct dirent *ent;
    char dirname[400];
    char *cmd;
    long order;

    if (config->syscmds == true) {
        snprintf(dirname, 400, "%s/syscmds", config->etcdir);
        printf("Reading syscmds: %s\n", dirname);
        if ((dir = opendir (dirname)) != NULL) {
            while ((ent = readdir(dir)) != NULL) {
                if (strncmp(ent->d_name, ".", 1) == 0)
                    continue;
                order = strtol(ent->d_name, &cmd, 10);
                if (strcmp(cmd, "") != 0)
                    list_push(&mympd_state->syscmd_list, strdup(cmd), order);
            }
            closedir(dir);
        }
        else {
            printf("ERROR: Can't read syscmds");
        }
    }
    else {
        printf("Syscmds are disabled\n");
    }
    return true;
}

static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd) {
    int len;
    char filename[400];
    char *line = NULL;
    char *crap;
    size_t n = 0;
    ssize_t read;
    
    const int order = list_get_value(&mympd_state->syscmd_list, cmd);
    if (order == -1) {
        printf("ERROR: Syscmd not defined: %s\n", cmd);
        len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System command not defined\"}");
        return len;
    }
    
    snprintf(filename, 400, "%s/syscmds/%d%s", config->etcdir, order, cmd);
    FILE *fp = fopen(filename, "r");    
    if (fp == NULL) {
        len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
        printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
        return len;
    }
    read = getline(&line, &n, fp);
    fclose(fp);
    if (read > 0) {
        strtok_r(line, "\n", &crap);
        const int rc = system(line);
        if ( rc == 0) {
            len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Executed cmd %s.\"}", cmd);
            LOG_VERBOSE() printf("Executed syscmd: \"%s\"\n", line);
        }
        else {
            len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Executing cmd %s failed.\"}", cmd);
            printf("ERROR: Executing syscmd \"%s\" failed.\n", cmd);
        }
    } else {
        len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
        printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
    }
    free(line);
    CHECK_RETURN_LEN();    
}

static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state) {
    char *crap;
    char value[400];

    LOG_INFO() printf("Reading states\n");
    if (state_file_read(config, "notificationWeb", value)) {
        if (strcmp(value, "true") == 0)
            mympd_state->notificationWeb = true;
        else
            mympd_state->notificationWeb = false;
    }
    else {
        mympd_state->notificationWeb = false;
        state_file_write(config, "notificationWeb", "false");
    }

    if (state_file_read(config, "notificationPage", value)) {
        if (strcmp(value, "true") == 0)
            mympd_state->notificationPage = true;
        else
            mympd_state->notificationPage = false;
    }
    else {
        mympd_state->notificationPage = true;
        state_file_write(config, "notificationPage", "true");
    }

    if (state_file_read(config, "autoPlay", value)) {
        if (strcmp(value, "true") == 0)
            mympd_state->autoPlay = true;
        else
            mympd_state->autoPlay = false;
    }
    else {
        mympd_state->autoPlay = false;
        state_file_write(config, "autoPlay", "false");
    }
    
    if (state_file_read(config, "jukeboxMode", value))
        mympd_state->jukeboxMode = strtol(value, &crap, 10);
    else {
        mympd_state->jukeboxMode = JUKEBOX_OFF;
        state_file_write(config, "jukeboxMode", "0");
    }

    if (state_file_read(config, "jukeboxPlaylist", value))
        mympd_state->jukeboxPlaylist = strdup(value);
    else {
        mympd_state->jukeboxPlaylist = strdup("Database");
        state_file_write(config, "jukeboxPlaylist", "Database");
    }

    if (state_file_read(config, "jukeboxQueueLength", value))
        mympd_state->jukeboxQueueLength = strtol(value, &crap, 10);
    else {
        mympd_state->jukeboxQueueLength = 1;
        state_file_write(config, "jukeboxQueueLength", "1");
    }
    
    if (state_file_read(config, "colsQueueCurrent", value))
        mympd_state->colsQueueCurrent = strdup(value);
    else {
        mympd_state->colsQueueCurrent = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
        state_file_write(config, "colsQueueCurrent", mympd_state->colsQueueCurrent);
    }
    
    if (state_file_read(config, "colsSearch", value))
        mympd_state->colsSearch = strdup(value);
    else {
        mympd_state->colsSearch = strdup("[\"Title\",\"Artist\",\"Album\",\"Duration\"]");
        state_file_write(config, "colsSearch", mympd_state->colsSearch);
    }
    
    if (state_file_read(config, "colsBrowseDatabase", value))
        mympd_state->colsBrowseDatabase = strdup(value);
    else {
        mympd_state->colsBrowseDatabase = strdup("[\"Track\",\"Title\",\"Duration\"]");
        state_file_write(config, "colsBrowseDatabase", mympd_state->colsBrowseDatabase);
    }
    
    if (state_file_read(config, "colsBrowsePlaylistsDetail", value))
        mympd_state->colsBrowsePlaylistsDetail = strdup(value);
    else {
        mympd_state->colsBrowsePlaylistsDetail = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
        state_file_write(config, "colsBrowsePlaylistsDetail", mympd_state->colsBrowsePlaylistsDetail);
    }
    
    if (state_file_read(config, "colsBrowseFilesystem", value))
        mympd_state->colsBrowseFilesystem = strdup(value);
    else {
        mympd_state->colsBrowseFilesystem = strdup("[\"Type\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
        state_file_write(config, "colsBrowseFilesystem", mympd_state->colsBrowseFilesystem);
    }
    
    if (state_file_read(config, "colsPlayback", value))
        mympd_state->colsPlayback = strdup(value);
    else {
        mympd_state->colsPlayback = strdup("[\"Artist\",\"Album\",\"Genre\"]");
        state_file_write(config, "colsPlayback", mympd_state->colsPlayback);
    }

    if (state_file_read(config, "colsQueueLastPlayed", value))
        mympd_state->colsQueueLastPlayed = strdup(value);
    else {
        mympd_state->colsQueueLastPlayed = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"LastPlayed\"]");
        state_file_write(config, "colsQueueLastPlayed", mympd_state->colsQueueLastPlayed);
    }
}

static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer) {
    size_t len;
    int nr = 0;
    struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
    
    len = json_printf(&out, "{type: mympdSettings, data: {mpdhost: %Q, mpdport: %d, passwort_set: %B, featSyscmds: %B, "
        "featLocalplayer: %B, streamport: %d, streamurl: %Q, featCoverimage: %B, coverimagename: %Q, coverimagesize: %d, featMixramp: %B, "
        "maxElementsPerPage: %d, notificationWeb: %B, notificationPage: %B, jukeboxMode: %d, jukeboxPlaylist: %Q, jukeboxQueueLength: %d, "
        "autoPlay: %B, backgroundcolor: %Q", 
        config->mpdhost, 
        config->mpdport, 
        config->mpdpass ? "true" : "false",
        config->syscmds,
        config->localplayer,
        config->streamport,
        config->streamurl,
        config->coverimage,
        config->coverimagename,
        config->coverimagesize,
        config->mixramp,
        config->max_elements_per_page,
        mympd_state->notificationWeb,
        mympd_state->notificationPage,
        mympd_state->jukeboxMode,
        mympd_state->jukeboxPlaylist,
        mympd_state->jukeboxQueueLength,
        mympd_state->autoPlay,
        config->backgroundcolor
    );
    
    if (config->syscmds == true) {
        len += json_printf(&out, ", syscmdList: [");
        nr = 0;
        struct node *current = mympd_state->syscmd_list.list;
        while (current != NULL) {
            if (nr++) 
                len += json_printf(&out, ",");
            len += json_printf(&out, "%Q", current->data);
            current = current->next;
        }
        len += json_printf(&out, "]");
    }
    len += json_printf(&out, ", colsQueueCurrent: %s, colsSearch: %s, colsBrowseDatabase: %s, colsBrowsePlaylistsDetail: %s, "
        "colsBrowseFilesystem: %s, colsPlayback: %s, colsQueueLastPlayed: %s}}",
        mympd_state->colsQueueCurrent,
        mympd_state->colsSearch,
        mympd_state->colsBrowseDatabase,
        mympd_state->colsBrowsePlaylistsDetail,
        mympd_state->colsBrowseFilesystem,
        mympd_state->colsPlayback,
        mympd_state->colsQueueLastPlayed
    );

    CHECK_RETURN_LEN();
}
