//playback-logging.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2012-2014
 *
 *  This file is part of RoarAudio PlayList Daemon,
 *  a playlist management daemon for RoarAudio.
 *  See README for details.
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  RoarAudio 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 software; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

/* API:
 *  Plugin Args:
 *   filename
 *    Name of the log file for the queueu. This is processed thru strftime().
 *   queue
 *    The queue to use.
 */

#include <roaraudio.h>
#include <rpldplugin.h>
#include <rpldnotify.h>

#define FILENAME_SIZE 1024

#define VCLT_PREFIX "X-" BASENAME "-"

struct instance {
       char   filename[FILENAME_SIZE];
 const char * filename_tpl;
 const char * queue;
 time_t now;
 struct tm tm;
 int new_file;
 struct roar_vio_calls * vio;
 struct roar_vio_calls vio_store;
 struct roar_subscriber * subscription;
 struct roar_dl_librarypara * para;
};

static struct instance * g_instance = NULL;
static struct instance   g_instance_init = {
 .filename = "",
 .filename_tpl = NULL,
 .queue = NULL,
 .now  = (time_t)0,
 .new_file = 1,
 .vio = NULL,
 .subscription = NULL,
 .para = NULL
};

#define __get_config(x) roar_keyval_lookup(para->argv, (x), para->argc, 0)

static inline void __get_time(void) {
 char   filename[FILENAME_SIZE];

 ROAR_DBG("__get_time(void) = ?");

 g_instance->now = time(NULL);
 localtime_r(&(g_instance->now), &(g_instance->tm));
 strftime(filename, sizeof(filename), g_instance->filename_tpl, &(g_instance->tm));

 if ( !!strncmp(filename, g_instance->filename, sizeof(filename)) ) {
  memcpy(g_instance->filename, filename, sizeof(filename));
  g_instance->new_file = 1;
  ROAR_DBG("__get_time(void): g_instance->new_file=1");
 }

 ROAR_DBG("__get_time(void) = (void)");
}

static inline int __reopen_file(void) {
 ROAR_DBG("__reopen_file(void) = ?");

 if ( g_instance->vio != NULL ) {
  roar_vio_close(g_instance->vio);
  g_instance->vio = NULL;
 }

 ROAR_DBG("__reopen_file(void) = ?");

 if ( roar_vio_open_dstr_simple(&(g_instance->vio_store), g_instance->filename, ROAR_VIOF_WRITE|ROAR_VIOF_CREAT|ROAR_VIOF_TRUNC) == -1 ) {
  ROAR_WARN("__reopen_file(void): Can not open file: %s: %s", g_instance->filename, roar_errorstring);
  return -1;
 }

 ROAR_DBG("__reopen_file(void) = ?");

 g_instance->vio = &(g_instance->vio_store);
 g_instance->new_file = 0;

 ROAR_DBG("__reopen_file(void) = 0");
 return 0;
}

static inline void __log_kv(const char * key, const char * value) {
 if ( key == NULL || value == NULL || value[0] == 0 )
  return;
 roar_vio_printf(g_instance->vio, "%s=%s\n", key, value);
}

static inline const char * __get_logtime(char * buffer, size_t len) {
 size_t i;

 if ( len < 26 )
  return NULL;

 asctime_r(&(g_instance->tm), buffer);
 // strip \n.
 for (i = 0; i < len && buffer[i]; i++)
  if (buffer[i] == '\n')
   buffer[i] = 0;
 return buffer;
}

static inline void __log_stream_pos(const struct roar_stream * stream, const char * prefix) {
 if ( stream == NULL || prefix == NULL )
  return;

 roar_vio_printf(g_instance->vio, "%s=%lu\n", prefix, (long unsigned int)stream->pos);

 if ( stream->info.rate && stream->info.channels ) {
  roar_vio_printf(g_instance->vio, "%s-Time=%.3fs\n", prefix, (float)stream->pos/(stream->info.rate*stream->info.channels));
 }
}

static inline void __log_length(const char * key, rpld_maxint_t l) {
 rpld_maxint_t h, m;

 if ( key == NULL )
  return;

 h  = l / 3600;
 l -= h * 3600;
 m  = l /   60;
 l -= m *   60;

 roar_vio_printf(g_instance->vio, "%s=%.2i:%.2i:%.2i\n", key, (int)h, (int)m, (int)l);
}

static inline int __log_ple(struct roar_dl_librarypara * para, struct rpld_playlist_entry * ple, const struct roar_stream * stream) {
 static const char * const keys[] = {
  // common keys:
  "TITLE", "ALBUM", "ARTIST", "PERFORMER", "VERSION",
  // URAS keys:
  "X-URAS-START", "X-URAS-MAPNUM"
 };
 size_t i;
 char buffer[37]; // minimum size: uuid=37, asctime=26
 int codec;
 rpld_maxint_t ret;

 __get_time();

 if ( g_instance->vio == NULL )
  __reopen_file();

 if ( g_instance->vio == NULL ) {
  // error still set by __reopen_file().
  return -1;
 }

 for (i = 0; i < (sizeof(keys)/sizeof(*keys)); i++)
  __log_kv(keys[i], rpldph_ple_get_meta(ple, keys[i]));

 __log_kv("UUID", rpldph_ple_get_uuid(ple, buffer));

 ret = rpldph_ple_get_meta_int(ple, "tracknumber");
 if ( ret != (rpld_maxint_t)-1 )
  roar_vio_printf(g_instance->vio, "TRACKNUMBER=%li\n", (long int)ret);

 ret = rpldph_ple_get_meta_int(ple, "discid");
 if ( ret != (rpld_maxint_t)-1 && ret != (rpld_maxint_t)0 )
  roar_vio_printf(g_instance->vio, "DISCID=0x%.8lx\n", (long int)ret);

 ret = rpldph_ple_get_meta_int(ple, "genre");
 if ( ret != (rpld_maxint_t)-1 )
  __log_kv("GENRE", roar_meta_strgenre(ret));

 ret = rpldph_ple_get_meta_int(ple, "length");
 if ( ret != (rpld_maxint_t)-1 && ret != (rpld_maxint_t)0 )
  __log_length("LENGTH", ret);

 if ( stream != NULL ) {
  codec = rpldph_ple_get_codec(ple);
  if ( codec == -1 ) {
   roar_vio_printf(g_instance->vio, "SIGNALINFO=rate:%i channels:%i\n", (int)stream->info.rate, (int)stream->info.channels);
  } else {
   roar_vio_printf(g_instance->vio, "SIGNALINFO=codec:%s rate:%i channels:%i\n", roar_codec2str(codec), (int)stream->info.rate, (int)stream->info.channels);
  }
 }

 __log_kv(VCLT_PREFIX "Start-Time", __get_logtime(buffer, sizeof(buffer)));

 __log_stream_pos(stream, VCLT_PREFIX "StartPos");

 return 0;
}

static inline int __log_end(const struct roar_stream * stream) {
 char buffer[26];

 __get_time();

 if ( g_instance->vio == NULL )
  __reopen_file();

 if ( g_instance->vio != NULL ) {
  __log_kv(VCLT_PREFIX "End-Time", __get_logtime(buffer, sizeof(buffer)));
  __log_stream_pos(stream, VCLT_PREFIX "EndPos");
  if ( roar_vio_write(g_instance->vio, "==\n", 3) != (ssize_t)3 ) {
   ROAR_WARN("__log_end(void): Can not write PLE end entry. Logfile may be corrupted: %s", roar_errorstring);
  }
 }

 if ( g_instance->new_file )
  __reopen_file();

 return 0;
}

static void __cb_subscription(struct roar_notify_core * core, struct roar_event * event, void * userdata) {
 struct instance * old_instance;
 rpld_pli_t queue = event->target;
 struct roar_dl_librarypara * para;

 (void)core;

 old_instance = g_instance;
 g_instance = userdata;
 para = g_instance->para;

 ROAR_DBG("__cb_subscription(core=%p, event=%p, userdata=%p) = ?", core, event, userdata);

 if ( queue != rpldph_playlist_get_by_name(g_instance->queue) )
  return;

 switch (event->event_proxy) {
  case RPLD_NOTIFY_PLAYBACK_STOP:
    __log_end(rpldph_playback_get_stream(queue));
   break;
  case RPLD_NOTIFY_PLAYBACK_PLAY:
    __log_ple(g_instance->para, rpldph_playback_get_ple(queue), rpldph_playback_get_stream(queue));
   break;
 }

 g_instance = old_instance;
}

static inline int __subscribe(void) {
 struct roar_event event;

 memset(&event, 0, sizeof(event));

 event.event = ROAR_EGRP_ANY_EVENT;

 event.emitter = -1;
 event.target = -1;
 event.target_type = -1;

 g_instance->subscription = roar_notify_core_subscribe(NULL, &event, __cb_subscription, g_instance);

 return 0;
}

static int _init (struct roar_dl_librarypara * para) {
 struct roar_keyval * kv;

 ROAR_DBG("_init(para=%p) = ?", para);

 roar_dl_para_ref(para);
 g_instance->para = para;

 kv = __get_config("filename");
 if ( kv == NULL || kv->value == NULL || kv->value[0] == 0 ) {
  ROAR_ERR("_init(para=%p): No filename given.", para);
 }

 g_instance->filename_tpl = kv->value;

 kv = __get_config("queue");
 if ( kv == NULL || kv->value == NULL || kv->value[0] == 0 ) {
  g_instance->queue = "Main Queue";
 } else {
  g_instance->queue = kv->value;
 }

 __get_time();
 __reopen_file();
 __subscribe();

 ROAR_DBG("_init(para=%p) = 0", para);
 return 0;
}

static int _free (struct roar_dl_librarypara * para) {
 (void)para;

 ROAR_DBG("_free(para=%p) = ?", para);

 roar_notify_core_unsubscribe(NULL, g_instance->subscription);

 if ( g_instance->vio != NULL ) {
  roar_vio_close(g_instance->vio);
  g_instance->vio = NULL;
 }

 roar_dl_para_unref(g_instance->para);
 g_instance->para = NULL;

 ROAR_DBG("_free(para=%p) = 0", para);
 return 0;
}

static struct roar_dl_appsched sched = {
 .init   = _init,
 .free   = _free,
 .update = NULL,
 .tick   = NULL,
 .wait   = NULL
};

ROAR_DL_PLUGIN_START(playback_logging) {
 ROAR_DL_PLUGIN_META_CONTACT_FLNE("Philipp", "Schafft", "ph3-der-loewe", "lion@lion.leolix.org");
 ROAR_DL_PLUGIN_META_LICENSE_TAG(GPLv3_0);
 ROAR_DL_PLUGIN_META_PRODUCT_NIV(BASENAME, ROAR_VID_ROARAUDIO, ROAR_VNAME_ROARAUDIO);
 ROAR_DL_PLUGIN_META_DESC("This plugin is used to log the activity of played tracks");

 RPLDPH_CHECK_VERSIONS();
 ROAR_DL_PLUGIN_REG_GLOBAL_DATA(g_instance, g_instance_init);
 ROAR_DL_PLUGIN_REG_APPSCHED(&sched);
} ROAR_DL_PLUGIN_END

//ll
