//client.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"

static const struct roar_dl_proto __proto_rpld = {
 .proto = ROAR_PROTO_RPLD,
 .description = "RPLD Simple",
 .flags = ROAR_DL_PROTO_FLAGS_NONE,
 .set_proto = NULL,
 .unset_proto = NULL,
 .handle = proto_simple_client_handle,
 .flush = NULL,
 .flushed = NULL,
 .status = NULL
};

static const struct roar_dl_proto __proto_mpd = {
 .proto = ROAR_PROTO_MPD,
 .description = "MPD",
 .flags = ROAR_DL_PROTO_FLAGS_NONE,
 .set_proto = proto_mpd_client_set_proto,
 .unset_proto = NULL,
 .handle = proto_mpd_client_handle,
 .flush = NULL,
 .flushed = NULL,
 .status = NULL
};

static struct rpld_proto g_proto[RPLD_PROTOS_MAX];

static struct rpld_client {
 enum rpld_client_state state;
 pli_t playlist;
 pli_t queue; // which playlist is the main queue for this client?
 struct rpld_playlist_pointer * tmpptr;
 struct roar_vio_calls con;
 enum rpld_client_acclev acclev;
 char * name;

 // for proto functions.
 const struct rpld_proto * proto;
 struct roar_buffer * obuffer;
 void * userdata;
} g_clients[RPLD_CLIENTS_MAX];

static int plugin_callback(enum roar_dl_fnreg_action action, int fn, int subtype, const void * object, size_t objectlen, int version, int options, void * userdata, struct roar_dl_lhandle * lhandle) {
 const struct roar_dl_proto * impl = object;
 size_t i, j;

 (void)fn, (void)subtype, (void)version, (void)options, (void)userdata;

 ROAR_DBG("plugin_callback(action=%i, fn=%i, subtype=%i, object=%p, objectlen=%lu, version=%i, options=%i, userdata=%p, lhandle=%p) = ?", (int)action, fn, subtype, object, (long unsigned int)objectlen, version, options, userdata, lhandle);

 if ( objectlen != ROAR_DL_PROTO_SIZE ) {
  ROAR_WARN("plugin_callback(*): Library %p tries to register protocol with bad object length.", lhandle);
  roar_err_set(ROAR_ERROR_BADLIB);
  return -1;
 }

 switch (action)  {
  case ROAR_DL_FNREG:
    for (i = 0; i < RPLD_PROTOS_MAX; i++) {
     if ( g_proto[i].impl == NULL ) {
      ROAR_DBG("plugin_callback(*): Registering as protocol number %u", (unsigned int)i);
      memset(&(g_proto[i]), 0, sizeof(g_proto[i]));
      g_proto[i].para = NULL;
      g_proto[i].paralen = -1;
      g_proto[i].pluginpara = NULL;
      g_proto[i].lhandle = lhandle;
      g_proto[i].impl = impl;
      ROAR_DBG("plugin_callback(action=%i, fn=%i, subtype=%i, object=%p, objectlen=%lu, version=%i, options=%i, userdata=%p, lhandle=%p) = 0", (int)action, fn, subtype, object, (long unsigned int)objectlen, version, options, userdata, lhandle);
      return 0;
     }
    }
   break;
  case ROAR_DL_FNUNREG:
    for (i = 0; i < RPLD_PROTOS_MAX; i++) {
     if ( g_proto[i].impl != NULL && g_proto[i].lhandle == lhandle ) {
      for (j = 0; j < RPLD_CLIENTS_MAX; j++) {
       if ( g_clients[j].state != CSTATE_UNUSED && g_clients[j].proto == &(g_proto[i]) ) {
        client_delete(j);
       }
      }
      g_proto[i].impl = NULL;
     }
    }
    return 0;
   break;
 }

 roar_err_set(ROAR_ERROR_NOSPC);
 return -1;
}

static const struct roar_dl_fnreg __fnreg_proto = {
 .fn = ROAR_DL_FN_PROTO,
 .subtype = ROAR_DL_PROTO_SUBTYPE,
 .version = ROAR_DL_PROTO_VERSION,
 .callback = plugin_callback,
 .userdata = NULL
};

static inline int __proto_run(int id, int (*func)(int client, struct roar_vio_calls * vio, struct roar_buffer ** obuffer, void ** userdata, const struct roar_keyval * protopara, ssize_t protoparalen, struct roar_dl_librarypara * pluginpara)) {
 const struct rpld_proto * proto = g_clients[id].proto;
 int ret;

 if ( func == NULL ) {
  roar_err_set(ROAR_ERROR_NOSYS);
  return -1;
 }

 if ( proto->lhandle != NULL )
  roar_dl_context_restore(proto->lhandle);

 ret = func(id, &(g_clients[id].con), &(g_clients[id].obuffer), &(g_clients[id].userdata), g_clients[id].proto->para, g_clients[id].proto->paralen, g_clients[id].proto->pluginpara);

 if ( proto->lhandle != NULL )
  roar_dl_context_store(proto->lhandle);

 return ret;
}

#define PROTO_RUN(id,func) __proto_run((id), (g_clients[(id)].proto == NULL || \
                                              g_clients[(id)].proto->impl->func == NULL) ? \
                                              NULL : g_clients[(id)].proto->impl->func)

void client_init(void) {
 size_t i;

 memset(g_clients, 0, sizeof(g_clients));

 for (i = 0; i < RPLD_PROTOS_MAX; i++)
  g_proto[i].impl = NULL;
 for (i = 0; i < RPLD_CLIENTS_MAX; i++)
  g_clients[i].state = CSTATE_UNUSED;

 ROAR_DL_RFNREG(ROAR_DL_HANDLE_APPLICATION, __fnreg_proto);

 roar_dl_register_fn(ROAR_DL_HANDLE_APPLICATION, ROAR_DL_FN_PROTO, ROAR_DL_PROTO_SUBTYPE,
                     &__proto_rpld, sizeof(__proto_rpld), ROAR_DL_PROTO_VERSION, ROAR_DL_FNREG_OPT_NONE);
 roar_dl_register_fn(ROAR_DL_HANDLE_APPLICATION, ROAR_DL_FN_PROTO, ROAR_DL_PROTO_SUBTYPE,
                     &__proto_mpd, sizeof(__proto_mpd), ROAR_DL_PROTO_VERSION, ROAR_DL_FNREG_OPT_NONE);
}

int  client_new(struct roar_vio_calls ** vio) {
 int i;

 if ( vio == NULL )
  return -1;

 for (i = 0; i < RPLD_CLIENTS_MAX; i++) {
  if ( g_clients[i].state == CSTATE_UNUSED ) {
   g_clients[i].state     =  CSTATE_USED;
   g_clients[i].playlist  =  0; /* 0=main queue */
   g_clients[i].queue     =  0; /* 0=main queue */
   g_clients[i].tmpptr    =  NULL;
   g_clients[i].acclev    =  ACCLEV_ALL; // currently all clients have fill access rights
   g_clients[i].name      =  NULL;
   g_clients[i].proto     =  NULL;
   g_clients[i].userdata  =  NULL;
   g_clients[i].obuffer   =  NULL;
   roar_vio_clear_calls(&(g_clients[i].con));
   *vio = &(g_clients[i].con);
   client_set_proto(i, ROAR_PROTO_RPLD);
   return i;
  }
 }

 return -1;
}

int  client_delete(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( g_clients[id].state == CSTATE_UNUSED )
  return 0;

 g_clients[id].state = CSTATE_UNUSED;

 PROTO_RUN(id, unset_proto);
 if ( g_clients[id].userdata != NULL ) {
  roar_mm_free(g_clients[id].userdata);
  g_clients[id].userdata = NULL;
 }

 if ( g_clients[id].name != NULL )
  roar_mm_free(g_clients[id].name);

 g_clients[id].proto = NULL;

 if ( g_clients[id].tmpptr != NULL ) {
  rpld_plp_unref(g_clients[id].tmpptr);
  g_clients[id].tmpptr = NULL;
 }

 roar_vio_close(&(g_clients[id].con));

 return 0;
}

int  client_get_proto(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return RPLD_PROTO_UNUSED;

 if ( g_clients[id].state == CSTATE_UNUSED )
  return RPLD_PROTO_UNUSED;

 if ( g_clients[id].proto == NULL )
  return RPLD_PROTO_UNUSED;

 return g_clients[id].proto->impl->proto;
}

int  client_set_proto(int id, int proto) {
 const struct rpld_proto * newproto = NULL;
 size_t i;
 int ret = 0;

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( g_clients[id].state != CSTATE_USED )
  return -1;

 for (i = 0; i < (sizeof(g_proto)/sizeof(*g_proto)); i++) {
  if ( g_proto[i].impl != NULL && g_proto[i].impl->proto == proto ) {
   newproto = &(g_proto[i]);
   break;
  }
 }

 if ( newproto == NULL )
  return -1;

 PROTO_RUN(id, unset_proto);

 if ( g_clients[id].userdata != NULL ) {
  roar_mm_free(g_clients[id].userdata);
  g_clients[id].userdata = NULL;
 }

 g_clients[id].proto = newproto;

 if ( newproto->impl->set_proto != NULL )
  ret = PROTO_RUN(id, set_proto);

 return ret;
}

struct roar_vio_calls * client_get_vio(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return NULL;

 if ( g_clients[id].state == CSTATE_UNUSED )
  return NULL;

 return &(g_clients[id].con);
}

enum rpld_client_acclev  client_get_acclev(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return ACCLEV_ERR;

 return g_clients[id].acclev;
}

int  client_set_acclev(int id, enum rpld_client_acclev acclev, int setter) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( setter != -1 ) {
  if ( setter != id )
   if ( setter < 0 || setter >= RPLD_CLIENTS_MAX )
    return -1;

  if ( acclev > g_clients[setter].acclev )
   return -1;
 }

 g_clients[id].acclev = acclev;

 return 0;
}

int  client_inc_acclev(int id, int inc, int setter) {
 int  newlev;
 enum rpld_client_acclev acclev;

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( setter != id )
  if ( setter < 0 || setter >= RPLD_CLIENTS_MAX )
   return -1;

 newlev = (int)g_clients[id].acclev + inc;

 if ( newlev < 0 ) {
  acclev = ACCLEV_NONE;
 } else {
  acclev = newlev;
 }

 if ( acclev > g_clients[setter].acclev )
  return -1;

 g_clients[id].acclev = acclev;

 return 0;
}

int  client_set_name(int id, const char * name) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( name != NULL && name[0] == 0 )
  name = NULL;

 if ( g_clients[id].name != NULL )
  roar_mm_free(g_clients[id].name);

 if ( name == NULL ) {
  g_clients[id].name = NULL;
 } else {
  g_clients[id].name = roar_mm_strdup(name);
 }

 return 0;
}

const char * client_get_name(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return NULL;

 return g_clients[id].name;
}

int  client_set_playlist(int id, pli_t pl) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 g_clients[id].playlist = pl;

 return 0;
}

pli_t client_get_playlist(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 return g_clients[id].playlist;
}

int  client_set_queue(int id, pli_t pl) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 // TODO: check if the playlist is a queue.
 g_clients[id].queue = pl;

 return 0;
}

pli_t client_get_queue(int id) {
 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 return g_clients[id].queue;
}

int  client_set_pointer(int id, int pointer, struct rpld_playlist_pointer * plp) {

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( pointer != POINTER_TEMP ) {
  roar_err_set(ROAR_ERROR_INVAL);
  return -1;
 }

 if ( g_clients[id].tmpptr != NULL )
  rpld_plp_unref(g_clients[id].tmpptr);

 g_clients[id].tmpptr = plp;

 if ( g_clients[id].tmpptr != NULL )
  rpld_plp_ref(g_clients[id].tmpptr);

 return 0;
}

struct rpld_playlist_pointer * client_get_pointer(int id, int pointer) {
 struct rpld_playlist_pointer * ret;

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return NULL;

 if ( pointer != POINTER_TEMP ) {
  roar_err_set(ROAR_ERROR_INVAL);
  return NULL;
 }

 ret = g_clients[id].tmpptr;
 if ( ret == NULL ) {
  roar_err_set(ROAR_ERROR_NOENT);
 } else {
  rpld_plp_ref(ret);
 }

 return ret;
}

static void __flush_client(int id, struct roar_vio_calls * vio, struct roar_buffer ** buffer) {
 size_t len;
 ssize_t ret;
 void * buf;

 if ( roar_buffer_get_datalen(*buffer, &buf, &len) == -1 )
  return;

 ret = roar_vio_write(vio, buf, len);

 if ( ret < 1 ) {
  client_delete(id);
  return;
 }

 if ( ret == (ssize_t)len ) {
  if ( roar_buffer_next(buffer) == -1 ) {
   client_delete(id);
   return;
  }
 } else {
  if ( roar_buffer_set_offset(*buffer, ret) == -1 ) {
   client_delete(id);
   return;
  }
 }

 roar_err_set(ROAR_ERROR_NONE);
}

int  client_handle(int id, int eventsa) {
 int ret = 0;

 ROAR_DBG("client_handle(id=%i, eventsa=%i) = ?", id, eventsa);

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return -1;

 if ( g_clients[id].state == CSTATE_UNUSED )
  return -1;

 roar_err_set(ROAR_ERROR_NONE);

 if ( eventsa & ROAR_VIO_SELECT_READ ) {
  ret = PROTO_RUN(id, handle);
 }

 if ( eventsa & ROAR_VIO_SELECT_WRITE ) {
  if ( g_clients[id].proto->impl->flush != NULL ) {
   PROTO_RUN(id, flush);
  } else {
   __flush_client(id, &(g_clients[id].con), &(g_clients[id].obuffer));
  }

  if ( g_clients[id].obuffer == NULL && g_clients[id].proto->impl->flushed != NULL ) {
   PROTO_RUN(id, flushed);
  }
 }

 ROAR_DBG("client_handle(id=%i, eventsa=%i) = %i", id, eventsa, ret);
 return ret;
}

int  client_get_eventsq(int id) {
 int ret = ROAR_VIO_SELECT_READ;

 if ( id < 0 || id >= RPLD_CLIENTS_MAX )
  return ROAR_VIO_SELECT_NO_RETEST;

 if ( g_clients[id].state == CSTATE_UNUSED )
  return ROAR_VIO_SELECT_NO_RETEST;

 if ( g_clients[id].obuffer != NULL )
  ret |= ROAR_VIO_SELECT_WRITE;

 return ret;
}

int  client_str2proto(const char * str) {
 if ( str == NULL || *str == 0 )
  return -1;

 if ( !strcasecmp(str, "simple") )
  return ROAR_PROTO_RPLD;
 if ( !strcasecmp(str, "rpld") )
  return ROAR_PROTO_RPLD;

 if ( !strcasecmp(str, "mpd") )
  return ROAR_PROTO_MPD;

 return roar_str2proto(str);
}

const char * client_proto2str(const int proto) {
 switch (proto) {
  case RPLD_PROTO_UNUSED: return "(unused client)"; break;
  case ROAR_PROTO_RPLD:   return "rpld"; break;
  case ROAR_PROTO_MPD:    return "mpd"; break;
  default: return roar_proto2str(proto); break;
 }
}

int  client_str2acclev(const char * str) {
 if ( !strcasecmp(str, "none") )
  return ACCLEV_NONE;

 if ( !strcasecmp(str, "conctl") )
  return ACCLEV_CONCTL;

 if ( !strcasecmp(str, "guest") )
  return ACCLEV_GUEST;

 if ( !strcasecmp(str, "user") )
  return ACCLEV_USER;

 if ( !strcasecmp(str, "all") )
  return ACCLEV_ALL;

 return -1;
}

const char * client_acclev2str(enum rpld_client_acclev acclev) {
 switch (acclev) {
  case ACCLEV_ERR:    return "(error)"; break;
  case ACCLEV_NONE:   return "none"; break;
  case ACCLEV_CONCTL: return "conctl"; break;
  case ACCLEV_GUEST:  return "guest"; break;
  case ACCLEV_USER:   return "user"; break;
  case ACCLEV_ALL:    return "all"; break;
  default:            return "(unknown acclev)"; break;
 }
}

//ll
