/*****************************************************************
 * gmerlin - a general purpose multimedia framework and applications
 *
 * Copyright (c) 2001 - 2024 Members of the Gmerlin project
 * http://github.com/bplaum
 *
 * 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, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 * *****************************************************************/


#include <string.h>
#include <unistd.h>

#include <gavl/gavltime.h>
#include <gavl/log.h>
#define LOG_DOMAIN "resourcemanager"

#include <gavl/utils.h>

#include <gmerlin/resourcemanager.h>
#include <gmerlin/pluginregistry.h>
#include <gmerlin/backend.h>

/*
 *  Unified manager for system resources (devices, removable media, media sources/sinks etc)
 */

typedef struct
  {
  pthread_t th;
  bg_controllable_t ctrl;

  bg_msg_sink_t * plugin_sink;
  
  gavl_array_t local;
  gavl_array_t remote;

  pthread_mutex_t mutex;

  int num_plugins;

  struct
    {
    bg_plugin_handle_t * h;
    
    } * plugins;
  
  } bg_resourcemanager_t;

static bg_resourcemanager_t * resman = NULL;
static pthread_mutex_t resman_mutex = PTHREAD_MUTEX_INITIALIZER;

static void forward_message(gavl_msg_t * msg);


static int find_resource_idx_by_string(int local, const char * tag, const char * val)
  {
  int i;
  const char * test_val;
  const gavl_dictionary_t * dict;
  gavl_array_t * arr = local ? &resman->local : &resman->remote;

  for(i = 0; i < arr->num_entries; i++)
    {
    
    if((dict = gavl_value_get_dictionary(&arr->entries[i])) &&
       (test_val = gavl_dictionary_get_string(dict, tag)) &&
       !strcmp(test_val, val))
      return i;
    }
  
  return -1;
  }

/*
 *  Create a unique ID of a backend. This is used to detect, that different frontends
 *  belong to the same backend (and favor the ones with the native protocol).
 * 
 *  The unique ID of a backend is: md5 of hostname-pid-type
 *  This assumes, that we never have more than one backend of the same type within the same process.
 */

char * bg_make_backend_id(const char * klass)
  {
  char * str;
  char hostname[HOST_NAME_MAX+1];
  char * hash = malloc(GAVL_MD5_LENGTH);
  
  gethostname(hostname, HOST_NAME_MAX+1);
  
  str = gavl_sprintf("%s-%d-%s", hostname, getpid(), klass);
  gavl_md5_buffer_str(str, strlen(str), hash);
  free(str);
  
  return hash;
  }

static void set_backend_id(gavl_dictionary_t * dict)
  {
  const char * klass;
  
  if(!(klass = gavl_dictionary_get_string(dict, GAVL_META_CLASS)) ||
     !gavl_string_starts_with(klass, "backend"))
    return;
  
  gavl_dictionary_set_string_nocopy(dict, GAVL_META_HASH, bg_make_backend_id(klass));
  }

static int resource_supported(gavl_dictionary_t * dict)
  {
  int ret = 0;
  const bg_plugin_info_t * info;
  const char * klass;
  int plugin_type = 0;
  const char * uri = gavl_dictionary_get_string(dict, GAVL_META_URI);
  
  if(!(klass = gavl_dictionary_get_string(dict, GAVL_META_CLASS)))
    return 0;

  if(!strcmp(klass, GAVL_META_CLASS_BACKEND_RENDERER))
    plugin_type = BG_PLUGIN_BACKEND_RENDERER;
  else if(!strcmp(klass, GAVL_META_CLASS_BACKEND_MDB))
    plugin_type = BG_PLUGIN_BACKEND_MDB;
  else if(!strcmp(klass, GAVL_META_CLASS_SINK_AUDIO))
    plugin_type = BG_PLUGIN_OUTPUT_AUDIO;
  else if(!strcmp(klass, GAVL_META_CLASS_SINK_VIDEO))
    plugin_type = BG_PLUGIN_OUTPUT_VIDEO;
  else if(gavl_string_starts_with(klass, "item.audio.recorder") ||
          gavl_string_starts_with(klass, "item.video.recorder") ||
          gavl_string_starts_with(klass, "container.root.removable.cd") ||
          gavl_string_starts_with(klass, "container.root.removable.dvd"))
    plugin_type = BG_PLUGIN_INPUT;
  
  if(!plugin_type || !(info = bg_plugin_find_by_protocol(uri, plugin_type)))
    {
    gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "No plugin found for resource %s, klass: %s type: %s", uri,
             klass, bg_plugin_type_to_string(plugin_type));
    goto fail;
    }
  
  ret = 1;
  
  fail:
  
  return ret;
  }

static void add(int local, gavl_dictionary_t * dict, const char * id)
  {
  gavl_value_t val;
  gavl_dictionary_t * dict_new;
  gavl_array_t * arr;
  
  if(local)
    arr = &resman->local;
  else
    arr = &resman->remote;

  gavl_value_init(&val);
  dict_new = gavl_value_set_dictionary(&val);
  
  gavl_dictionary_copy(dict_new, dict);
  gavl_dictionary_set_string(dict_new, GAVL_META_ID, id);

  /*
   *  Save the resource in the array *before* sending the messages because
   *  the plugins might want to access it
   */
  
  gavl_array_splice_val_nocopy(arr, -1, 0, &val);
      
  if(!local)
    {
    gavl_msg_t * msg;

    if(resource_supported(dict_new))
      {
      msg = bg_msg_sink_get(resman->ctrl.evt_sink);
      gavl_msg_set_id_ns(msg, GAVL_MSG_RESOURCE_ADDED, GAVL_MSG_NS_GENERIC);
      gavl_dictionary_set_string(&msg->header, GAVL_MSG_CONTEXT_ID, id);
      gavl_msg_set_arg_dictionary(msg, 0, dict_new);
      bg_msg_sink_put(resman->ctrl.evt_sink);
      }
    }
  else
    {
    gavl_msg_t msg;

    gavl_msg_init(&msg);
    set_backend_id(dict_new);
    gavl_msg_set_id_ns(&msg, GAVL_MSG_RESOURCE_ADDED, GAVL_MSG_NS_GENERIC);
    gavl_dictionary_set_string(&msg.header, GAVL_MSG_CONTEXT_ID, id);
    gavl_msg_set_arg_dictionary(&msg, 0, dict_new);
    forward_message(&msg);
    gavl_msg_free(&msg);
    }
  
  }

static void del(int local, int idx)
  {
  gavl_array_t * arr;
  if(local)
    arr = &resman->local;
  else
    arr = &resman->remote;

  if(!local)
    {
    const char * id;
    gavl_dictionary_t * dict;

    if((dict = gavl_value_get_dictionary_nc(&arr->entries[idx])) &&
       (id = gavl_dictionary_get_string(dict, GAVL_META_ID)) &&
       resource_supported(dict))
      {
      gavl_msg_t * msg;
      msg = bg_msg_sink_get(resman->ctrl.evt_sink);
      gavl_msg_set_id_ns(msg, GAVL_MSG_RESOURCE_DELETED, GAVL_MSG_NS_GENERIC);
      gavl_dictionary_set_string(&msg->header, GAVL_MSG_CONTEXT_ID, id);
      bg_msg_sink_put(resman->ctrl.evt_sink);
      }
    }
  
  gavl_array_splice_val(arr, idx, 1, NULL);
  }

static void forward_message(gavl_msg_t * msg)
  {
  int i;
  bg_controllable_t * ctrl;
  
  for(i = 0; i < resman->num_plugins; i++)
    {
    if(!resman->plugins[i].h ||
       !resman->plugins[i].h->plugin->get_controllable ||
       !(ctrl = resman->plugins[i].h->plugin->get_controllable(resman->plugins[i].h->priv)))
      continue;
    bg_msg_sink_put_copy(ctrl->cmd_sink, msg);
    }
  }


/* Handle message from application space */
static int handle_msg_external(void * data, gavl_msg_t * msg)
  {
  
  switch(msg->NS)
    {
    case GAVL_MSG_NS_GENERIC:
      switch(msg->ID)
        {
        case GAVL_MSG_RESOURCE_ADDED:
          {
          const char * id;
          gavl_dictionary_t dict;
          
          id = gavl_dictionary_get_string(&msg->header, GAVL_MSG_CONTEXT_ID);
          
          if(find_resource_idx_by_string(1, GAVL_META_ID, id) >= 0)
            return 1;
          gavl_dictionary_init(&dict);
          gavl_msg_get_arg_dictionary_c(msg, 0, &dict);
          add(1, &dict, id);
          gavl_dictionary_free(&dict);
          
          }
          break;
        case GAVL_MSG_RESOURCE_DELETED:
          {
          int idx;
          const char * id;

          id = gavl_dictionary_get_string(&msg->header, GAVL_MSG_CONTEXT_ID);
          
          if((idx = find_resource_idx_by_string(1, GAVL_META_ID, id)) < 0)
            return 1;
          
          forward_message(msg);
          del(1, idx);
          }
          break;
        case GAVL_FUNC_QUERY_RESOUCRES:
          {
          /* Send all resources to the requesting client */
          int i;

          //          fprintf(stderr, "Got query func\n");

          for(i = 0; i < resman->remote.num_entries; i++)
            {
            const char * id;
            gavl_msg_t * resp;

            const gavl_dictionary_t * dict;
            
            if(!(dict = gavl_value_get_dictionary(&resman->remote.entries[i])) ||
               !(id = gavl_dictionary_get_string(dict, GAVL_META_ID)))
              continue;

            resp = bg_msg_sink_get(resman->ctrl.evt_sink);
            
            gavl_msg_set_id_ns(resp, GAVL_RESP_QUERY_RESOUCRES, GAVL_MSG_NS_GENERIC);
            gavl_msg_set_resp_for_req(resp, msg);

            if(i == resman->local.num_entries-1)
              gavl_msg_set_last(resp, 1);
            else
              gavl_msg_set_last(resp, 0);
            
            gavl_dictionary_set_string(&resp->header, GAVL_MSG_CONTEXT_ID, id);
            gavl_msg_set_arg_dictionary(resp, 0, dict);
            bg_msg_sink_put(resman->ctrl.evt_sink);
            
            }
          

          }
          break;
        case GAVL_MSG_QUIT:
          return 0;
          break;
        }
      break;
    }
  return 1;
  }

/* Handle message from detector plugin */
static int handle_msg_plugin(void * data, gavl_msg_t * msg)
  {
  switch(msg->NS)
    {
    case GAVL_MSG_NS_GENERIC:
      switch(msg->ID)
        {
        case GAVL_MSG_RESOURCE_ADDED:
          {
          const char * id;
          const char * hash;
          const char * uri;
          gavl_dictionary_t dict;

          id = gavl_dictionary_get_string(&msg->header, GAVL_MSG_CONTEXT_ID);
          
          /* Ignore already added resources */
          if(find_resource_idx_by_string(0, GAVL_META_ID, id) >= 0)
            return 1;

          gavl_dictionary_init(&dict);
          gavl_msg_get_arg_dictionary(msg, 0, &dict);

          if((uri = gavl_dictionary_get_string(&dict, GAVL_META_URI)) &&
             (find_resource_idx_by_string(1, GAVL_META_URI, uri) >= 0))
            {
            gavl_dictionary_free(&dict);
            return 1;
            }
          /* Check for backends with the same ID */
          if((hash = gavl_dictionary_get_string(&dict, GAVL_META_HASH)))
            {
            int idx;
            const gavl_dictionary_t * test_dict;
            
            if(((idx = find_resource_idx_by_string(0, GAVL_META_HASH, hash)) >= 0) &&
               (test_dict = gavl_value_get_dictionary(&resman->remote.entries[idx])))
              {
              int prio = -1, test_prio = -1;

              if(gavl_dictionary_get_int(&dict, BG_RESOURCE_PRIORITY, &prio) &&
                 gavl_dictionary_get_int(test_dict, BG_RESOURCE_PRIORITY, &test_prio))
                {
                if(prio > test_prio)
                  {
                  gavl_log(GAVL_LOG_DEBUG, LOG_DOMAIN, "Replacing resource %s with %s",
                           gavl_dictionary_get_string(test_dict, GAVL_META_URI),
                           gavl_dictionary_get_string(&dict, GAVL_META_URI));
                  /* Replace entry */
                  del(0, idx);
                  }
                else if(prio < test_prio)
                  {
                  /* Leave entry */
                  gavl_log(GAVL_LOG_DEBUG, LOG_DOMAIN, "Not adding resource %s (%s is already there)",
                           gavl_dictionary_get_string(&dict, GAVL_META_URI),
                           gavl_dictionary_get_string(test_dict, GAVL_META_URI));
                  return 1;
                  }
                
                }
              
              }
            
            }
#if 0
          fprintf(stderr, "Got resource %s:\n", id);
          gavl_dictionary_dump(&dict, 2);
          fprintf(stderr, "\n");
#endif
          add(0, &dict, id);
          gavl_dictionary_free(&dict);
          }
          break;
        case GAVL_MSG_RESOURCE_DELETED:
          {
          const char * id;
          int idx;

          id = gavl_dictionary_get_string(&msg->header, GAVL_MSG_CONTEXT_ID);
          
          if((idx = find_resource_idx_by_string(0, GAVL_META_ID, id)) < 0)
            return 1;

#if 0
          fprintf(stderr, "Deleted resource: %s\n", id);
#endif
          del(0, idx);
          }
          break;
        }
      break;
      
    }
  return 1;
  }

static void add_windowsystem_resources()
  {
  gavl_dictionary_t dict;

  int have_x11 = 0;
  int have_wayland = 0;

  if(getenv("DISPLAY"))
    have_x11 = 1;

  if(getenv("WAYLAND_DISPLAY"))
    have_wayland = 1;

  if(have_wayland)
    {
    if(bg_plugin_find_by_name("ov_wayland"))
      {
      gavl_dictionary_init(&dict);

      gavl_dictionary_set_string(&dict, GAVL_META_LABEL, "Wayland output");
      gavl_dictionary_set_string(&dict, GAVL_META_URI, "wayland-sink:///");
      gavl_dictionary_set_string(&dict, GAVL_META_CLASS, GAVL_META_CLASS_SINK_VIDEO);
    
      add(0, &dict, "wayland-sink");
    
      gavl_dictionary_free(&dict);
      }
    
    }

  if(have_x11)
    {
    if(bg_plugin_find_by_name("i_x11"))
      {
      gavl_dictionary_init(&dict);

      gavl_dictionary_set_string(&dict, GAVL_META_LABEL, "X11 grabber");
      gavl_dictionary_set_string(&dict, GAVL_META_URI, "x11-src:///");
      gavl_dictionary_set_string(&dict, GAVL_META_CLASS, GAVL_META_CLASS_VIDEO_RECORDER);
    
      add(0, &dict, "x11-src");
    
      gavl_dictionary_free(&dict);
      }

    if(bg_plugin_find_by_name("ov_x11"))
      {
      gavl_dictionary_init(&dict);

      gavl_dictionary_set_string(&dict, GAVL_META_LABEL, "X11 output");
      gavl_dictionary_set_string(&dict, GAVL_META_URI, "x11-sink:///");
      gavl_dictionary_set_string(&dict, GAVL_META_CLASS, GAVL_META_CLASS_SINK_VIDEO);
    
      add(0, &dict, "x11-sink");
    
      gavl_dictionary_free(&dict);
      }
    
    }
  
  
  
  }

static void * thread_func(void * data)
  {
  int result;
  int i;
  gavl_time_t delay_time = GAVL_TIME_SCALE; // 1000 ms

  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Started thread");

  /* Delay for 1 sec to give other modules a chance to connect their
     message sinks */
  gavl_time_delay(&delay_time);

  add_windowsystem_resources();
  
  
  delay_time = GAVL_TIME_SCALE / 20; // 50 ms
  

  while(1)
    {
    pthread_mutex_lock(&resman->mutex);
    
    result = 0;
    
    /* Handle external messages */
    if(!bg_msg_sink_iteration(resman->ctrl.cmd_sink))
      {
      pthread_mutex_unlock(&resman->mutex);
      break;
      }
    
    result += bg_msg_sink_get_num(resman->ctrl.cmd_sink);
    
    /* Update plugins */
    for(i = 0; i < resman->num_plugins; i++)
      {
      bg_controllable_plugin_t * plugin;

      if(resman->plugins[i].h)
        {
        plugin = (bg_controllable_plugin_t *)resman->plugins[i].h->plugin;

        if(plugin->update)
          result += plugin->update(resman->plugins[i].h->priv);
        
        }
      }

    i = 0;
    /* Remove expired resouces */
    while(i < resman->remote.num_entries)
      {
      gavl_time_t t;
      const gavl_dictionary_t * d;

      if((d = gavl_value_get_dictionary(&resman->remote.entries[i])) &&
         gavl_dictionary_get_long(d, BG_RESOURCE_EXPIRE_TIME, &t) &&
         gavl_time_get_monotonic() > t)
        {
        const char * id;
        gavl_msg_t * msg;
        
        id = gavl_dictionary_get_string(d, GAVL_META_ID);
        
        /* TODO: Remove expired resource */
        gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Removing resource %s (expired)", id);

        msg = bg_msg_sink_get(resman->ctrl.evt_sink);
        gavl_msg_set_id_ns(msg, GAVL_MSG_RESOURCE_DELETED, GAVL_MSG_NS_GENERIC);
        gavl_dictionary_set_string(&msg->header, GAVL_MSG_CONTEXT_ID, id);
        bg_msg_sink_put(resman->ctrl.evt_sink);
        
        gavl_array_splice_val(&resman->remote, i, 1, NULL);
        
        result++;
        }
      else
        i++;
      }

    pthread_mutex_unlock(&resman->mutex);
    
    if(!result) // idle
      gavl_time_delay(&delay_time);
    
    }

  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Finished thread");
  
  return NULL;
  }


static void bg_resourcemanager_init()
  {
  int i;

  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Initializing resource manager");
  
  if(resman)
    return;
  resman = calloc(1, sizeof(*resman));

  bg_controllable_init(&resman->ctrl,
                       bg_msg_sink_create(handle_msg_external, NULL, 0),
                       bg_msg_hub_create(1));

  resman->plugin_sink = bg_msg_sink_create(handle_msg_plugin, NULL, 1);

  /* Load plugins */
  
  resman->num_plugins = bg_get_num_plugins(BG_PLUGIN_RESOURCE_DETECTOR, 0);
  resman->plugins = calloc(resman->num_plugins, sizeof(*resman->plugins));

  for(i = 0; i < resman->num_plugins; i++)
    {
    bg_controllable_t * plugin_ctrl;
    const bg_plugin_info_t * info = bg_plugin_find_by_index(i, BG_PLUGIN_RESOURCE_DETECTOR, 0);
    resman->plugins[i].h = bg_plugin_load(info);

    if(resman->plugins[i].h->plugin->get_controllable &&
       (plugin_ctrl = resman->plugins[i].h->plugin->get_controllable(resman->plugins[i].h->priv)))
      bg_msg_hub_connect_sink(plugin_ctrl->evt_hub, resman->plugin_sink);
    }
  
  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Loaded %d plugins", resman->num_plugins);

  pthread_mutex_init(&resman->mutex, NULL);
  
  pthread_create(&resman->th, NULL, thread_func, NULL);
  
  
  }


bg_controllable_t * bg_resourcemanager_get_controllable()
  {
  pthread_mutex_lock(&resman_mutex);

  
  
  if(!resman)
    bg_resourcemanager_init();
  
  pthread_mutex_unlock(&resman_mutex);

  return &resman->ctrl;
  }

void bg_resourcemanager_publish(const char * id, const gavl_dictionary_t * dict)
  {
  gavl_msg_t * msg;
  bg_controllable_t * ctrl = bg_resourcemanager_get_controllable();
  
  msg = bg_msg_sink_get(ctrl->cmd_sink);

  gavl_msg_set_id_ns(msg, GAVL_MSG_RESOURCE_ADDED, GAVL_MSG_NS_GENERIC);
  gavl_dictionary_set_string(&msg->header, GAVL_MSG_CONTEXT_ID, id);
  gavl_msg_set_arg_dictionary(msg, 0, dict);
  bg_msg_sink_put(ctrl->cmd_sink);
  }

void bg_resourcemanager_unpublish(const char * id)
  {
  gavl_msg_t * msg;
  bg_controllable_t * ctrl = bg_resourcemanager_get_controllable();

  msg = bg_msg_sink_get(ctrl->cmd_sink);
  
  gavl_msg_set_id_ns(msg, GAVL_MSG_RESOURCE_DELETED, GAVL_MSG_NS_GENERIC);
  gavl_dictionary_set_string(&msg->header, GAVL_MSG_CONTEXT_ID, id);
  
  bg_msg_sink_put(ctrl->cmd_sink);
  }

gavl_dictionary_t * bg_resource_get_by_id(int local, const char * id)
  {
  int idx;
  gavl_array_t * arr;

  if(!resman)
    return NULL;

  if(local)
    arr = &resman->local;
  else
    arr = &resman->remote;

  if((idx = find_resource_idx_by_string(local, GAVL_META_ID, id)) >= 0)
    return gavl_value_get_dictionary_nc(&arr->entries[idx]);
  else
    return NULL;
  }

gavl_dictionary_t * bg_resource_get_by_idx(int local, int idx)
  {
  gavl_array_t * arr;

  if(!resman)
    return NULL;
  
  if(local)
    arr = &resman->local;
  else
    arr = &resman->remote;

  if((idx < 0) || (idx >= arr->num_entries))
    return NULL;
  
  return gavl_value_get_dictionary_nc(&arr->entries[idx]);
  }

int bg_resource_idx_for_label(const gavl_array_t * arr, const char * label, int off)
  {
  int i;
  const char * test_label;
  const gavl_dictionary_t * test_dict;
  
  if(!(arr->num_entries - off))
    return off;

  for(i = off; i < arr->num_entries; i++)
    {
    if(!(test_dict = gavl_value_get_dictionary(&arr->entries[i])) ||
       !(test_label = gavl_dictionary_get_string(test_dict, GAVL_META_LABEL)))
      continue;

    if(strcoll(label, test_label) < 0)
      return i;
    
    }
  return arr->num_entries;
  }

static int compare_func(const void * val1, const void * val2, void * priv)
  {
  const gavl_dictionary_t * dict1;
  const gavl_dictionary_t * dict2;

  const char * str1;
  const char * str2;

  if(!(dict1 = gavl_value_get_dictionary(val1)) ||
     !(dict2 = gavl_value_get_dictionary(val2)))
    return 0;

  if(!(str1 = gavl_dictionary_get_string(dict1, GAVL_META_LABEL)) ||
     !(str2 = gavl_dictionary_get_string(dict2, GAVL_META_LABEL)))
    return 0;

  if(!strcmp(str1, str2))
    {

    if(!(str1 = gavl_dictionary_get_string(dict1, GAVL_META_URI)) ||
       !(str2 = gavl_dictionary_get_string(dict2, GAVL_META_URI)))
      return 0;
    }
  
  return strcmp(str1, str2);
  }

void bg_resource_get_by_class(const char * klass, int full_match, gavl_time_t timeout, gavl_array_t * arr)
  {
  int i;
  const gavl_dictionary_t  * test_dict;
  const char  * test_klass;

  if(!resman)
    bg_resourcemanager_init();
  
  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Scanning for resources for %.2f seconds please wait",
           gavl_time_to_seconds(timeout));
  gavl_time_delay(&timeout);
  
  pthread_mutex_lock(&resman->mutex);

  for(i = 0; i < resman->remote.num_entries; i++)
    {
    if((test_dict = gavl_value_get_dictionary(&resman->remote.entries[i])) &&
       (test_klass = gavl_dictionary_get_string(test_dict, GAVL_META_CLASS)))
      {
      if(full_match)
        {
        if(!strcmp(test_klass, klass))
          gavl_array_splice_val(arr, -1, 0, &resman->remote.entries[i]);
        }
      else
        {
        if(gavl_string_starts_with(test_klass, klass))
          gavl_array_splice_val(arr, -1, 0, &resman->remote.entries[i]);
        }
      }
    }
  
  pthread_mutex_unlock(&resman->mutex);
  gavl_array_sort(arr, compare_func, NULL);
  
  }


void bg_resource_get_by_protocol(const char * protocol, int full_match, gavl_time_t timeout, gavl_array_t * arr)
  {
  int i;
  const gavl_dictionary_t  * test_dict;
  const char  * test_uri;

  gavl_time_delay(&timeout);
  
  pthread_mutex_lock(&resman->mutex);

  for(i = 0; i < resman->remote.num_entries; i++)
    {
    if((test_dict = gavl_value_get_dictionary(&resman->remote.entries[i])) &&
       (test_uri = gavl_dictionary_get_string(test_dict, GAVL_META_CLASS)))
      {
      char *test_protocol = NULL;
      if(gavl_url_split(test_uri, &test_protocol, NULL, NULL, NULL, NULL, NULL))
        {
        if(full_match)
          {
          if(!strcmp(test_protocol, protocol))
            gavl_array_splice_val(arr, -1, 0, &resman->remote.entries[i]);
          }
        else
          {
          if(gavl_string_starts_with(test_protocol, protocol))
            gavl_array_splice_val(arr, -1, 0, &resman->remote.entries[i]);
          }
        }
      if(test_protocol)
        free(test_protocol);
      
      }
    }
  
  pthread_mutex_unlock(&resman->mutex);

  gavl_array_sort(arr, compare_func, NULL);
  
  }

static void list_resource_array(gavl_array_t * arr)
  {
  int i;
  const gavl_dictionary_t * dict;
  for(i = 0; i < arr->num_entries; i++)
    {
    if((dict = gavl_value_get_dictionary(&arr->entries[i])))
      {
      printf("# %s\n", gavl_dictionary_get_string(dict, GAVL_META_LABEL));
      printf("%s\n", gavl_dictionary_get_string(dict, GAVL_META_URI));
      }
    }
  }

void bg_resource_list_by_class(const char * klass, int full_match, gavl_time_t timeout)
  {
  gavl_array_t arr;
  gavl_array_init(&arr);
  bg_resource_get_by_class(klass, full_match, timeout, &arr);
  list_resource_array(&arr);
  gavl_array_free(&arr);
  }
  
void bg_resource_list_by_protocol(const char * protocol, int full_match, gavl_time_t timeout)
  {
  gavl_array_t arr;
  gavl_array_init(&arr);
  bg_resource_get_by_protocol(protocol, full_match, timeout, &arr);
  list_resource_array(&arr);
  gavl_array_free(&arr);
  }

void bg_opt_list_recording_sources(void * data, int * argc,
                                   char *** _argv, int arg)
  {
  bg_resource_list_by_class("item.recorder.", 0, 5 * GAVL_TIME_SCALE /* 1 sec */ );
  }

void bg_opt_list_audio_sinks(void * data, int * argc,
                            char *** _argv, int arg)
  {
  bg_resource_list_by_class(GAVL_META_CLASS_SINK_AUDIO, 1, 5 * GAVL_TIME_SCALE /* 1 sec */ );
  }

void bg_opt_list_video_sinks(void * data, int * argc,
                             char *** _argv, int arg)
  {
  bg_resource_list_by_class(GAVL_META_CLASS_SINK_VIDEO, 1, 5 * GAVL_TIME_SCALE /* 1 sec */ );
  }

void bg_resourcemanager_cleanup()
  {
  int i;
  
  if(resman)
    {
    gavl_msg_t * msg;
    
    gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Freeing resource manager");

    msg = bg_msg_sink_get(resman->ctrl.cmd_sink);
    gavl_msg_set_id_ns(msg, GAVL_MSG_QUIT, GAVL_MSG_NS_GENERIC);
    bg_msg_sink_put(resman->ctrl.cmd_sink);

    pthread_join(resman->th, NULL);
    
    /* Finish thread */

    for(i = 0; i < resman->num_plugins; i++)
      {
      if(resman->plugins[i].h)
        {
        bg_plugin_unref(resman->plugins[i].h);
        }
      }

    if(resman->plugins)
      free(resman->plugins);
    
    gavl_array_free(&resman->local);
    gavl_array_free(&resman->remote);
    bg_controllable_cleanup(&resman->ctrl);

    bg_msg_sink_destroy(resman->plugin_sink);

    pthread_mutex_destroy(&resman->mutex);
    
    free(resman);
    }
  
  }

