//store.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2009-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.
 *
 */

#include "rpld.h"

struct index_lion {
// PL: $id:$parent:X:$name:$histsize:$volume:$role:$history_pli:$backend_name:$mixer_id:
 pli_t id;
 pli_t parent;
 const char * name;
 ssize_t histsize;
 uint16_t volume_mono;
 int role;
 pli_t history;
 const char * backend_name;
 int mixer_id;
};

static const char * _g_path = NULL;

static char * _astr(size_t * s) {
 size_t len;

 if ( _g_path == NULL )
  return NULL;

 len = strlen(_g_path);

 *s = len+1 /* slash */ + 32 /* pl-* */;

 return (char*) roar_mm_malloc(*s);
}

static void _mkfile(char * buf, size_t len, const char * path, const char * file) {
 snprintf(buf, len, "%s/%s", path, file);
 buf[len-1] = 0;
}

int store (void) {
 struct fformat_handle * format;
 struct rpld_playlist * pl;
 struct rpld_queue * plq;
 struct roar_vio_calls file;
 struct roar_vio_calls index, export;
 struct roar_vio_defaults def;
 struct rpld_playlist_pointer * plp;
 char * pointer_name;
 int pointer;
 size_t i;
 char * buf;
 size_t len;
 char uuid[37];

 // TODO: free format...

 if ( _g_path == NULL )
  return -1;

 if ( (format = fformat_handle_new(RPLD_FF_PLF_DEFAULT)) == NULL )
  return -1;

 if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, O_WRONLY|O_CREAT|O_TRUNC, 0644) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }

 if ( (buf = _astr(&len)) == NULL ) {
  fformat_handle_close(format);
  return -1;
 }

 _mkfile(buf, len, _g_path, "store-version");
 if ( roar_vio_open_dstr(&file, buf, &def, 1) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }
 roar_vio_printf(&file, "%i\n", STORE_VERSION_CURRENT);
 roar_vio_close(&file);

 _mkfile(buf, len, _g_path, "pointer");

 if ( roar_vio_open_dstr(&file, buf, &def, 1) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( g_playlists[i] == NULL )
   continue;

  if ( g_playlists[i]->queue == NULL )
   continue;

  for (pointer = 0; pointer < POINTER_NUM; pointer++) {
   pointer_name = rpld_pointer_get_name(pointer, g_playlists[i]->id, -1);
   plp = rpld_pointer_get(pointer, g_playlists[i]->id, -1);

   if ( plp == NULL )
    continue;

   switch (plp->pls.type) {
    case RPLD_PL_ST_TRACKNUM_LONG:
      roar_vio_printf(&file, "POINTER: \"%s\" -> long:0x0x%.16" __ll "X\n", pointer_name,
                                (__longlong unsigned int)plp->pls.subject.long_tracknum);
     break;
    case RPLD_PL_ST_TRACKNUM_SHORT:
      roar_vio_printf(&file, "POINTER: \"%s\" -> short:0x0x%.8lX\n", pointer_name,
                                (long unsigned int)plp->pls.subject.short_tracknum);
     break;
    case RPLD_PL_ST_UUID:
      roar_uuid2str(uuid, plp->pls.subject.uuid, sizeof(uuid));
      roar_vio_printf(&file, "POINTER: \"%s\" -> uuid:%s\n", pointer_name, uuid);
     break;
    case RPLD_PL_ST_RANDOM:
      roar_vio_printf(&file, "POINTER: \"%s\" -> random:%i\n", pointer_name,
                                plp->pls.subject.pl);
     break;
    case RPLD_PL_ST_RANDOM_LIKENESS:
      roar_vio_printf(&file, "POINTER: \"%s\" -> randomlike:%i\n", pointer_name,
                                plp->pls.subject.pl);
     break;
   }

   rpld_plp_unref(plp);
   roar_mm_free(pointer_name);
  }
 }

 roar_vio_close(&file);


 _mkfile(buf, len, _g_path, "index");

 if ( roar_vio_open_dstr(&index, buf, &def, 1) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  pl = rpld_pl_get_by_id(i);
  if ( pl != NULL ) {
   plq = pl->queue;

// format:
// PL: $id:$parent:X:$name:$histsize:$volume:$role:$history_pli:$backend_name:$mixer_id:
   roar_vio_printf(&index, "PL: %i:%i:X:%s:%li:",
                                        rpld_pl_get_id(pl),
                                        rpld_pl_get_parent(pl),
                                        rpld_pl_get_name(pl),
                                        (long int)rpld_pl_get_histsize(pl)
                  );
   if ( plq == NULL ) {
    roar_vio_printf(&index, "-1:unknown:-1:+invalid:-1:\n");
   } else {
    roar_vio_printf(&index, "%u:%s:%i:%s:%i:\n",
                            (unsigned int)plq->volume_mono,
                            roar_role2str(plq->role),
                            rpld_pl_get_id(plq->history),
                            backend_get_name(plq->backend),
                            plq->mixer
                   );
   }
/*
int                     fformat_playlist_export(struct fformat_handle * handle,
                                                struct roar_vio_calls * vio,
                                                struct rpld_playlist  * pl);
*/
   snprintf(buf, len, "%s/pl-%i", _g_path, rpld_pl_get_id(pl));
   buf[len-1] = 0;
   if ( roar_vio_open_dstr(&export, buf, &def, 1) == -1 ) {
    roar_vio_close(&index);
    fformat_handle_close(format);
    rpld_pl_unref(pl);
    return -1;
   }

   fformat_playlist_export(format, &export, pl);

   roar_vio_close(&export);
   rpld_pl_unref(pl);
  }
 }

 roar_vio_close(&index);

 roar_mm_free(buf);
 fformat_handle_close(format);
 return 0;
}

static inline int _parse_index_line(struct index_lion * index, char * lion) {
 char * div[11];
 int i;

//     0   1       2 3     4         5       6     7            8             9
// PL: $id:$parent:X:$name:$histsize:$volume:$role:$history_pli:$backend_name:$mixer_id:

 if ( !!strncmp(lion, "PL: ", 4) ) {
  roar_err_set(ROAR_ERROR_ILLSEQ);
  return -1;
 }

 div[0] = lion + 4;

 for (i = 1; i < 11; i++) {
  if ( div[i-1] == NULL ) {
   div[i] = NULL;
  } else {
   div[i] = strstr(div[i-1], ":");
  }
  if ( div[i] != NULL ) {
   *div[i] = 0;
    div[i]++;
   if ( *div[i] == '\r' || *div[i] == '\n' )
    div[i] = NULL;
  }
 }

#define _def(idx,val) if ( div[(idx)] == NULL ) div[(idx)] = (val)

 _def(0,  "-1");
 _def(1,  "0");
 _def(2,  "X"); // dummy.
 _def(3,  "NO NAME");
 _def(4,  "-1"); // this is not an history
 _def(5,  "65535"); // max volume.
 _def(6,  "unknown"); // unknown role.
 _def(7,  "-1"); // no history PLI for this stream.
 _def(8,  "+invalid");
 _def(9,  "-1"); // default mixer.
 _def(10, ""); // END OF RECORD.

 index->id           = atoi(div[0]);
 index->parent       = atoi(div[1]);
 index->name         = div[3];
 index->histsize     = atoi(div[4]);
 index->volume_mono  = atoi(div[5]);
 index->role         = roar_str2role(div[6]);
 index->history      = atoi(div[7]);
 index->backend_name = div[8];
 index->mixer_id     = atoi(div[9]);

 ROAR_DBG("_parse_index_line(index=%p{.id=%i, .parent=%i, .name='%s', .histsize=%zi, .volume_mono=%u, .role=%i, .history=%i, .backend_name='%s', .index=%i}, lion='%s') = 0", index,
          (int)index->id,
          (int)index->parent,
          index->name,
          index->histsize,
          (unsigned int)index->volume_mono,
          index->role,
          (int)index->history,
          index->backend_name,
          index->mixer_id,
          lion);

 return 0;
}

static inline int __flush_state(void) {
 struct rpld_playlist * pl;
 size_t i;

 for (i = 0; i < MAX_PLAYLISTS; i++) {
  if ( (pl = g_playlists[i]) == NULL )
   continue;

  rpld_pl_set_name(pl, NULL);
  rpld_pl_unregister(pl);
 }

 return 0;
}

int restore_pointers (const char * filename, struct roar_vio_defaults * def) {
 struct rpld_playlist_search    pls;
 struct rpld_playlist_pointer * plp;
 struct roar_vio_calls file;
 char lion[1024];
 char * div[3];
 pli_t queue;
 int pointer;
 int client;
 int i;

 if ( roar_vio_open_dstr(&file, filename, def, 1) == -1 )
  return -1;

 while (roar_vio_fgets(&file, lion, sizeof(lion)) != NULL) {
  if ( !strncmp(lion, "POINTER: \"", 10) ) {
   div[0] = lion+10;
   div[1] = strstr(div[0], "\" -> ");
   if ( div[1] == NULL )
    continue;

   ROAR_DBG("restore() = ?");

   *div[1]  = 0;
    div[1] += 5;

   if ( rpld_pointer_parse_name(&pointer, &queue, &client, -1, div[0]) == -1 )
    continue;

   ROAR_DBG("restore() = ?");

   div[2]  = strstr(div[1], ":");
   if ( div[2] == NULL )
    continue;

   ROAR_DBG("restore() = ?");

   *div[2]  = 0;
    div[2]++;

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

   ROAR_DBG("restore() = ?");

   i = strlen(div[2]) - 1;
   for (; i > -1; i--) {
    if ( div[2][i] == '\r' || div[2][i] == '\n' ) {
     div[2][i] = 0;
    } else {
     break;
    }
   }

   if ( !strcmp(div[1], "uuid") ) {
    ROAR_DBG("restore(): uuid='%s'", div[2]);
    pls.type = RPLD_PL_ST_UUID;
    if ( roar_str2uuid(pls.subject.uuid, div[2]) != 0 )
     continue;

    ROAR_DBG("restore() = ?");
   } else if ( !strcmp(div[1], "random") ) {
    pls.type = RPLD_PL_ST_RANDOM;
    pls.subject.pl = atoi(div[2]);
   } else if ( !strcmp(div[1], "randomlike") ) {
    pls.type = RPLD_PL_ST_RANDOM_LIKENESS;
    pls.subject.pl = atoi(div[2]);
   } else {
    ROAR_WARN("restore(): Unsupported search schema: %s", div[1]);
    continue;

    ROAR_DBG("restore() = ?");
   }

   plp = rpld_plp_init(NULL, &pls);
   rpld_pointer_set(pointer, queue, client, plp);
   rpld_plp_unref(plp);
  }
 }

 roar_vio_close(&file);

 return 0;
}

int restore (void) {
 struct fformat_handle * format;
 struct rpld_playlist * pl, * history;
 struct rpld_backend * backend;
 struct roar_vio_calls file;
 struct roar_vio_calls index, import;
 struct roar_vio_defaults def;
 struct index_lion index_lion;
 char * buf;
 char lion[1024];
 size_t len;
 int store_version = STORE_VERSION_DEFAULT;

 if ( __flush_state() == -1 )
  return -1;

 // TODO: free format...

 if ( _g_path == NULL )
  return -1;

 if ( (format = fformat_handle_new(RPLD_FF_PLF_DEFAULT)) == NULL )
  return -1;

 if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, O_RDONLY, 0644) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }

 if ( (buf = _astr(&len)) == NULL ) {
  fformat_handle_close(format);
  return -1;
 }

 _mkfile(buf, len, _g_path, "store-version");

 if ( roar_vio_open_dstr(&file, buf, &def, 1) != -1 ) {
  if ( roar_vio_fgets(&file, lion, sizeof(lion)) != NULL ) {
   store_version = atoi(lion);
  } else {
   store_version = STORE_VERSION_DEFAULT;
  }
  roar_vio_close(&file);
 } else {
  store_version = STORE_VERSION_DEFAULT;
 }


 if ( store_version > STORE_VERSION_CURRENT ) {
  fformat_handle_close(format);
  ROAR_ERR("restore(): Can not restore: store version too new: %i (%i is current)", store_version, STORE_VERSION_CURRENT);
  roar_err_set(ROAR_ERROR_NSVERSION);
  return -1;
 }

 switch (store_version) {
  case STORE_VERSION_OLD:
  case STORE_VERSION_V0_1RC1:
    history = rpld_pl_get_by_id(1);
    if ( history == NULL ) {
     history = rpld_pl_new();
     rpld_pl_set_name(history, "Main History");
     rpld_pl_register_id(history, 1);
    }
    rpld_pl_unref(history);

    pl = rpld_pl_get_by_id(0);
    if ( pl == NULL ) {
     pl = rpld_pl_new();
     rpld_pl_set_name(pl, "Main Queue");
     rpld_pl_register_id(pl, 0);
    }
    rpld_pl_unref(pl);
   break;
 }

 _mkfile(buf, len, _g_path, "index");

 if ( roar_vio_open_dstr(&index, buf, &def, 1) == -1 ) {
  fformat_handle_close(format);
  return -1;
 }

 while (roar_vio_fgets(&index, lion, sizeof(lion)) != NULL) {
  if ( _parse_index_line(&index_lion, lion) == 0 ) {

   if ( store_version < STORE_VERSION_V0_1RC7 ) {
    if ( (index_lion.id & 0xFF) == 1 && !!strcmp(index_lion.name, "Main History") ) {
     store_version = STORE_VERSION_OLD;

     // allow re-registering by unrefing.
     pl = rpld_pl_get_by_id(index_lion.id);
     if ( pl != NULL ) {
      rpld_pl_set_name(pl, NULL);
      rpld_pl_unref(pl);
     }
    }
   }

   pl = rpld_pl_get_by_id(index_lion.id);
   if ( pl == NULL ) {
    pl = rpld_pl_new();
    rpld_pl_register_id(pl, index_lion.id);
   }
   rpld_pl_set_name(pl, index_lion.name);
   rpld_pl_set_parent(pl, index_lion.parent);
   rpld_pl_unref(pl); // pl keeps valid as it is stored in the global register.

   if ( !!strcmp(index_lion.backend_name, "+invalid") ) {
    history = rpld_pl_get_by_id(index_lion.history);
    if ( history == NULL ) {
     history = rpld_pl_new();
     rpld_pl_register_id(history, index_lion.history);
    }

    // first pre-connect to the backend server.
    // this allows us to track connection errors a bit more easy.
    backend = backend_get_connection2(index_lion.backend_name, 1);
    if ( backend == NULL ) {
     ROAR_WARN("restore(void): Can not create backend object for server. BAD.: %s: %s", index_lion.backend_name, roar_errorstring);
    } else {
     if ( !(backend->flags & BACKEND_FLAG_CONNECTED) ) {
      ROAR_WARN("restore(void): Unabled to connect to server: %s: %s", index_lion.backend_name, roar_errorstring);
     }
     backend_unref(backend);
    }

    playback_add_queue(pl, index_lion.backend_name, index_lion.mixer_id, history);

    playback_set_volume_mu16(index_lion.id, index_lion.volume_mono);
    playback_set_role(index_lion.id, index_lion.role);
    rpld_pl_unref(history);
   }

   if ( pl != NULL ) {
    if ( index_lion.histsize != -1 )
     rpld_pl_set_histsize(pl, index_lion.histsize);

    snprintf(buf, len, "%s/pl-%i", _g_path, index_lion.id);
    buf[len-1] = 0;
    if ( roar_vio_open_dstr(&import, buf, &def, 1) == -1 ) {
     roar_vio_close(&index);
     fformat_handle_close(format);
     return -1;
    }

    fformat_playlist_import(format, &import, pl);

    roar_vio_close(&import);
   }
  }
 }

 roar_vio_close(&index);

 fformat_handle_close(format);

 _mkfile(buf, len, _g_path, "pointer");

 if ( restore_pointers(buf, &def) == -1 ) {
  ROAR_WARN("restore(): Loading pointers failed: %s", roar_error2str(roar_error));
 }

 switch (store_version) {
  case STORE_VERSION_OLD:
  case STORE_VERSION_V0_1RC1:
    if ( store_version < STORE_VERSION_V0_1RC1 ) {
     history = rpld_pl_new();
     rpld_pl_set_name(history, "Main History");
     rpld_pl_register(history);
    } else {
     history = rpld_pl_get_by_id(1);
    }

    pl = rpld_pl_get_by_id(0);

    if ( rpld_pl_get_histsize(history) == -1 )
     rpld_pl_set_histsize(history, 42); // TODO: FIXME: use global const here.

    playback_add_queue(pl, BACKEND_DEFAULT_NAME, -1, history);

    rpld_pl_unref(pl);
    rpld_pl_unref(history);
   break;
 }

 roar_mm_free(buf);

 return 0;
}

int setstorepath(const char * path) {

 if ( path == NULL )
  return -1;

 if ( *path == 0 )
  return -1;

 _g_path = path;

 return 0;
}

//ll
