/*****************************************************************
 * gmerlin - a general purpose multimedia framework and applications
 *
 * Copyright (c) 2001 - 2012 Members of the Gmerlin project
 * gmerlin-general@lists.sourceforge.net
 * http://gmerlin.sourceforge.net
 *
 * 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 <config.h>

#include <gmerlin/mdb.h>
#include <gmerlin/utils.h>
#include <gmerlin/bggavl.h>

#include <mdb_private.h>
#include <gavl/metatags.h>

#include <md5.h>


#include <gmerlin/translation.h>
#include <gmerlin/log.h>
#define LOG_DOMAIN "mdb.radiobrowser"

#define STORAGE_DIR "radiobrowser"

#define RB_ID "rbid"

/* 24 hours */
#define MAX_CACHE_AGE (24*60*60)

// #define MAX_CACHE_AGE 0


/*
 * /webradio/radiobrowser
 *   /by-tag
 *      /jazz
 *         Blabla
 *   /by-country
 *      /Germany
 *        BlaBla
 *   /by-language
 *      /German
 *        BlaBla
 */

// #define RADIO_BROWSER_ROOT "http://www.radio-browser.info/webservice/"

#define RADIO_BROWSER_ROOT "https://de1.api.radio-browser.info/"

#define TAGS_URL     \
  RADIO_BROWSER_ROOT \
  "json/tags"

#define COUNTRIES_URL \
  RADIO_BROWSER_ROOT  \
  "json/countrycodes"

#define LANGUAGES_URL \
  RADIO_BROWSER_ROOT  \
  "json/languages"

#define ROOT_BY_LANGUAGE "/language"
#define ROOT_BY_COUNTRY  "/country"
#define ROOT_BY_TAG      "/tag"

#define GROUP_THRESHOLD 500

typedef struct
  {
  gavl_array_t tags;
  gavl_array_t languages;
  gavl_array_t countries;

  char * root_id;
  int root_id_len;
  } rb_t;

static const struct
  {
  const char * label;
  const char * id;
  }
root_folders[] =
  {
    { "Languages", ROOT_BY_LANGUAGE },
    { "Countries", ROOT_BY_COUNTRY  },
    { "Tags",      ROOT_BY_TAG      },
  };

static const int num_root_folders = sizeof(root_folders)/sizeof(root_folders[0]);

static const char * dict_get_string(json_object * obj, const char * tag);

static int dict_get_int(json_object * obj, const char * tag);

static gavl_dictionary_t * browse_object(bg_mdb_backend_t * be,
                                         const char * ctx_id);


static const char * get_root_label(const char * id)
  {
  int i;
  for(i = 0; i < num_root_folders; i++)
    {
    if(!strcmp(root_folders[i].id, id))
      return root_folders[i].label;
    }
  return NULL;
  }
  

static const char * dict_get_string(json_object * obj, const char * tag)
  {
  json_object * child;
  const char * ret;

  if(!json_object_object_get_ex(obj, tag, &child) ||
     !json_object_is_type(child, json_type_string) ||
     !(ret = json_object_get_string(child)))
    return NULL;
  return ret;
  }

static int dict_get_int(json_object * obj, const char * tag)
  {
  json_object * child;
  int ret;

  if(!json_object_object_get_ex(obj, tag, &child) ||
     !json_object_is_type(child, json_type_int) ||
     !(ret = json_object_get_int(child)))
    return -1;
  return ret;
  }

static void destroy_rb(bg_mdb_backend_t * b)
  {
  rb_t * rb = b->priv;
  gavl_array_free(&rb->tags);
  gavl_array_free(&rb->languages);
  gavl_array_free(&rb->countries);
  
  if(rb->root_id)
    free(rb->root_id);
  
  free(b->priv);
  }

/* Child of a "root folder", i.e. */

static void set_root_child(rb_t * rb, gavl_dictionary_t * dict, const gavl_array_t * arr,
                           int idx)
  {
  gavl_dictionary_t * m;
  const gavl_dictionary_t * arr_dict;
  const char * path = NULL;
  char * id;
  
  m = gavl_dictionary_get_dictionary_create(dict, GAVL_META_METADATA);

  if(arr == &rb->languages)
    {
    path = ROOT_BY_LANGUAGE;
    gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_LANGUAGE);
    }
  else if(arr == &rb->countries)
    {
    path = ROOT_BY_COUNTRY;
    gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_COUNTRY);
    }
  else if(arr == &rb->tags)
    {
    path = ROOT_BY_TAG;
    gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_TAG);
    }
  
  gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_AUDIO_BROADCAST);
  
  if(path && (arr_dict = gavl_value_get_dictionary(&arr->entries[idx])))
    {
    gavl_dictionary_copy(m, arr_dict);
    
    /* Replace ID */
    id = bg_sprintf("%s%s/%s", rb->root_id, path, gavl_dictionary_get_string(arr_dict, GAVL_META_ID));
    gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, id);
    }
  
  //  bg_mdb_set_group_parent(arr, dict, GROUP_THRESHOLD);
  
  }

#if 0
static const struct
  {
  const char * codec;
  const char * mimetype;
  }
codecs[] =
  {
    { "MP3",  "audio/mpeg"  },
    { "AAC",  "audio/x-aac"   },
    { "AAC+", "audio/aacp"  },
    { "FLV",  "video/x-flv" },
    { "OGG",  "audio/ogg"   },
    { /* end */             }
  };

static const char * get_mimetype(json_object * child)
  {
  int i = 0;
  const char * var = dict_get_string(child, "codec");

  // fprintf(stderr, "get_mimetype: %s\n", var);

  if(!var)
    return NULL;

  while(codecs[i].codec)
    {
    if(!strcmp(codecs[i].codec, var))
      return codecs[i].mimetype;
    i++;
    }
  return NULL;
  }
#endif

static int set_station(bg_mdb_backend_t * be,
                       const char * parent_id, gavl_dictionary_t * dict, json_object * child)
  {
  const char * v;
  gavl_dictionary_t * m;
  char * tmp_string;

  
  if(!json_object_is_type(child, json_type_object))
    return 0;
    
  m = gavl_dictionary_get_dictionary_create(dict, GAVL_META_METADATA);
  gavl_dictionary_set_string(m, GAVL_META_LABEL,            dict_get_string(child, "name"));
  gavl_dictionary_set_string(m, GAVL_META_STATION,          dict_get_string(child, "name"));
  gavl_dictionary_set_string(m, GAVL_META_LOGO_URL, dict_get_string(child, "favicon"));
  
  gavl_dictionary_set_string(m, GAVL_META_COUNTRY,     dict_get_string(child, "country"));
  gavl_dictionary_set_string(m, GAVL_META_STATION_URL, dict_get_string(child, "homepage"));
  gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_AUDIO_BROADCAST);

  if((v = dict_get_string(child, "tags")))
    {
    int i = 0;
    char ** arr;
    arr = bg_strbreak(v, ',');

    if(arr)
      {
      while(arr[i])
        {
        gavl_dictionary_append_string_array(m, GAVL_META_TAG, arr[i]);
        i++;
        }
      bg_strbreak_free(arr);
      }
    }
  
  v = dict_get_string(child, "stationuuid");
  
  tmp_string = bg_sprintf("%s/%s", parent_id, v);
  gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, tmp_string);

  /* Streams are set via a proxy mechanism */
  
  tmp_string = bg_sprintf("%sm3u/url/%s", RADIO_BROWSER_ROOT, v);
  gavl_metadata_add_src(m, GAVL_META_SRC, "audio/mpegurl", tmp_string);

  //  fprintf(stderr, "Adding mp3 url: %s\n", tmp_string);

  free(tmp_string);
  
  tmp_string = bg_sprintf("%spls/url/%s", RADIO_BROWSER_ROOT, v);
  gavl_metadata_add_src(m, GAVL_META_SRC, "audio/x-scpls", tmp_string);
  //  fprintf(stderr, "Adding pls url: %s\n", tmp_string);
  
  free(tmp_string);

 
  return 1;
  }

static void send_array(rb_t * rb, const char * path, const gavl_array_t * arr,
                       gavl_array_t * dst, int start, int num)
  {
  int i;
  gavl_value_t val;
  gavl_dictionary_t * dict;

  if(!bg_mdb_adjust_num(start, &num, arr->num_entries))
    return;
  
  for(i = 0; i < num; i++)
    {
    gavl_value_init(&val);
    dict = gavl_value_set_dictionary(&val);
    set_root_child(rb, dict, arr, i + start);
    gavl_array_splice_val_nocopy(dst, -1, 0, &val);
    }
  }

static void send_stations(bg_mdb_backend_t * be,
                          const char * url1, gavl_array_t * dst, const char * parent_id)
  {
  int i, num;
  json_object * obj;
  char * url;
  //  gavl_dictionary_t * cache_obj;
  //  gavl_value_t cache_val;
  
  //  rb_t * rb = be->priv;
  
  //  const gavl_array_t * cache_arr;

#if 0  
  if((cache_arr =
      bg_mdb_cache_get_children_max_age(be->db, parent_id, MAX_CACHE_AGE)))
    {
    gavl_array_copy(dst, cache_arr);
    bg_object_cache_done_get(be->db->cache);
    gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Got %s from cache", parent_id);
    return;
    }
#endif
  
  url = bg_string_to_uri(url1, -1);
  if(!(obj = bg_json_from_url(url, NULL)))
    {
    free(url);
    return;
    }

  free(url);
  
  if(!json_object_is_type(obj, json_type_array))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Wrong type (must be array): %s",
           json_object_to_json_string(obj));
    }
  
  num = json_object_array_length(obj);

  for(i = 0; i < num; i++)
    {
    gavl_value_t val;
    gavl_dictionary_t * dict;
    
    json_object * child = json_object_array_get_idx(obj, i);

    gavl_value_init(&val);

    dict = gavl_value_set_dictionary(&val);
    if(set_station(be, parent_id, dict, child))
      gavl_array_splice_val_nocopy(dst, -1, 0, &val);
    else
      gavl_value_free(&val);
    }

#if 0  
  
  /* Always cache objects, for which http requests are made */

  cache_obj = browse_object(be, parent_id);

  gavl_value_init(&cache_val);

  cache_val.type    = GAVL_TYPE_ARRAY;
  cache_val.v.array = dst;
  
  gavl_track_splice_children(cache_obj, 0, 0, &cache_val);
  
  gavl_value_init(&cache_val);

  cache_val.type         = GAVL_TYPE_DICTIONARY;
  cache_val.v.dictionary = cache_obj;

  /* value must be copied */
  bg_object_cache_put(be->db->cache, parent_id, &cache_val, -1);
#endif
  
  json_object_put(obj);
  }

static gavl_dictionary_t * create_root_folder(bg_mdb_backend_t * be, const char * id)
  {
  gavl_dictionary_t * m;
  const gavl_array_t * arr = NULL;
  gavl_dictionary_t * ret;
  
  rb_t * rb = be->priv;

  ret = gavl_dictionary_create();
  m = gavl_dictionary_get_dictionary_create(ret, GAVL_META_METADATA);
  
  if(!strcmp(id, ROOT_BY_LANGUAGE))
    {
    arr = &rb->languages;
    gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_LANGUAGE);
    }
  else if(!strcmp(id, ROOT_BY_COUNTRY))
    {
    arr = &rb->countries;
    gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_COUNTRY);
    }
  else if(!strcmp(id, ROOT_BY_TAG))
    {
    arr = &rb->tags;
    gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_TAG);
    }

  gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER);
  gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, bg_sprintf("/webradio/radiobrowser%s", id));
  gavl_dictionary_set_string(m, GAVL_META_LABEL, get_root_label(id));
  
  gavl_track_set_num_children(ret, arr->num_entries, 0);

  //  fprintf(stderr, "create_root_folder\n");
  //  gavl_dictionary_dump(ret, 2);
  
  return ret;
  }

static gavl_dictionary_t * create_root_folder_child(bg_mdb_backend_t * be,
                                                    gavl_array_t * arr,
                                                    const char * name)
  {
  int i;
  rb_t * rb = be->priv;
  
  for(i = 0; i < arr->num_entries; i++)
    {
    const gavl_dictionary_t * dict;
    const char * id;
    gavl_dictionary_t * ret;
    
    if((dict = gavl_value_get_dictionary(&arr->entries[i])) &&
       (id = gavl_dictionary_get_string(dict, GAVL_META_ID)) &&
       !strcmp(id, name))
      {
      ret = gavl_dictionary_create();
      set_root_child(rb, ret, arr, i);
      return ret;
      }
    }
  return NULL;
  }

static gavl_dictionary_t * browse_leaf(bg_mdb_backend_t * be,
                                       const char * ctx_id)
  {
  int num;
  json_object * obj = NULL;

  gavl_dictionary_t * ret = NULL;
  //  const gavl_dictionary_t * dict = NULL;
  char * url = NULL;
  const char * pos;
  json_object * child;
  char * parent_id = NULL;

#if 0  
  /* Got cache item */
  if((dict = bg_mdb_cache_get_object_max_age(be->db, ctx_id, MAX_CACHE_AGE)))
    {
    ret = gavl_dictionary_create();
    gavl_dictionary_copy(ret, dict);
    bg_object_cache_done_get(be->db->cache);
    return ret;
    }
#endif

  pos = strrchr(ctx_id, '/');
  if(!pos)
    goto fail;
  
  url = bg_sprintf(RADIO_BROWSER_ROOT "json/stations/byid/%s", pos+1);
  
  if(!(obj = bg_json_from_url(url, NULL)))
    goto fail;
  
  if(!json_object_is_type(obj, json_type_array))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Wrong type (must be array): %s",
           json_object_to_json_string(obj));
    goto fail;
    }
  
  if((num = json_object_array_length(obj)) != 1)
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Expected 1 array element, got %d", num);
    goto fail;
    }
  
  child = json_object_array_get_idx(obj, 0);
  
  ret = gavl_dictionary_create();

  parent_id = bg_mdb_get_parent_id(ctx_id);
  
  if(!set_station(be, parent_id, ret, child))
    {
    gavl_dictionary_destroy(ret);
    ret = NULL;
    goto fail;
    }

  fail:
  
  if(url)
    free(url);
  
  if(parent_id)
    free(parent_id);
  
  if(obj)
    json_object_put(obj);
  
  return ret;
  }

static gavl_dictionary_t * browse_object(bg_mdb_backend_t * be,
                                         const char * ctx_id)
  {
  gavl_dictionary_t * ret = NULL;
  const char * ctx_id_orig = ctx_id;
  
  const char * pos;
  rb_t * rb = be->priv;

  //  fprintf(stderr, "browse_object: %s\n", ctx_id);
  
  if(*ctx_id == '\0')
    {
    /* Should be handled by the core */
    return NULL;
    }
  else if(gavl_string_starts_with(ctx_id, ROOT_BY_LANGUAGE))
    {
    ctx_id += strlen(ROOT_BY_LANGUAGE);

    if(*ctx_id == '\0')
      {
      // /webradio/radiobrowser/language
      ret = create_root_folder(be, ROOT_BY_LANGUAGE);
      }
    else
      {
      ctx_id++;

      if((pos = strchr(ctx_id, '/')))
        {
        // /webradio/radiobrowser/language/BlaBla/id
        ret = browse_leaf(be, ctx_id_orig);
        }
      else
        {
        // /webradio/radiobrowser/language/Blabla
        ret = create_root_folder_child(be, &rb->languages, ctx_id);
        }
      }
    }
  else if(gavl_string_starts_with(ctx_id, ROOT_BY_COUNTRY))
    {
    ctx_id += strlen(ROOT_BY_COUNTRY);

    if(*ctx_id == '\0')
      {
      // /webradio/radiobrowser/country
      ret = create_root_folder(be, ROOT_BY_COUNTRY);
      }
    else
      {
      // /webradio/radiobrowser/country/Blabla
      ctx_id++;
      if((pos = strchr(ctx_id, '/')))
        {
        // /webradio/radiobrowser/country/BlaBla/id
        ret = browse_leaf(be, ctx_id_orig);
        }
      else
        {
        // /webradio/radiobrowser/country/BlaBla
        ret = create_root_folder_child(be, &rb->countries, ctx_id);
        }
      }
    }

  else if(gavl_string_starts_with(ctx_id, ROOT_BY_TAG))
    {
    ctx_id += strlen(ROOT_BY_TAG);

    if(*ctx_id == '\0')
      {
      // /webradio/radiobrowser/tag
      ret = create_root_folder(be, ROOT_BY_TAG);
      }
    else
      {
      ctx_id++;
      if((pos = strchr(ctx_id, '/')))
        {
        // /webradio/radiobrowser/tag/BlaBla/id
        ret = browse_leaf(be, ctx_id_orig);
        }
      else
        {
        // /webradio/radiobrowser/tag/BlaBla
        ret = create_root_folder_child(be, &rb->tags, ctx_id);
        }
      }
    }
  return ret;
  }

static const char * get_orig_id(const gavl_array_t * arr, const char * md5, int * num_children)
  {
  int i;
  const gavl_dictionary_t * dict;
  const char * md5_array;
  
  for(i = 0; i < arr->num_entries; i++)
    {
    if((dict = gavl_value_get_dictionary(&arr->entries[i])) &&
       (md5_array = gavl_dictionary_get_string(dict, GAVL_META_ID)) &&
       !strcmp(md5_array, md5))
      {
      if(num_children)
        {
        if(!gavl_dictionary_get_int(dict, GAVL_META_NUM_CHILDREN, num_children))
          *num_children = 0;
        }
      return gavl_dictionary_get_string(dict, RB_ID);
      }
    }
  return 0;
  }

static int handle_msg(void * priv, gavl_msg_t * msg)
  {
  gavl_msg_t * res;
  bg_mdb_backend_t * be = priv;
  rb_t * rb = be->priv;
  gavl_value_t val;
  gavl_dictionary_t * dict;
  gavl_dictionary_t * m;
  const char * pos;
  const char * orig_id;

  char * url;
  
  //  fprintf(stderr, "handle_msg_rb %d %d\n", msg->ID, msg->NS);
  
  switch(msg->NS)
    {
    case BG_MSG_NS_DB:
      {
      const char * ctx_id_orig;

      const char * ctx_id = gavl_dictionary_get_string(&msg->header,
                                                       GAVL_MSG_CONTEXT_ID);
      ctx_id_orig = ctx_id;
      
      ctx_id += rb->root_id_len;
      
      switch(msg->ID)
        {
        case BG_FUNC_DB_BROWSE_OBJECT:
          {
          //          bg_mdb_page_t page;
          
          //          bg_mdb_page_init_browse_object(be->db, &page, &ctx_id);
          
          if((dict = browse_object(be, ctx_id)))
            {
            // bg_mdb_page_apply_object(be->db, &page, dict);
            
            res = bg_msg_sink_get(be->ctrl.evt_sink);
            bg_mdb_set_browse_obj_response(res, dict, msg);
            bg_msg_sink_put(be->ctrl.evt_sink, res);
            gavl_dictionary_destroy(dict);
            }
          
          //          bg_mdb_page_free(&page);
          }
          break;
        case BG_FUNC_DB_BROWSE_CHILDREN:
          {
          gavl_array_t arr;
          
          int i;
          int start, num;
          const char * ctx_id;
          int total = 0;
          //          bg_mdb_page_t page;
          
          //          const char * ctx_id_orig;

          bg_mdb_get_browse_children_request(msg, &ctx_id, &start, &num);

          //          bg_mdb_page_init_browse_children(be->db, &page, &ctx_id, &start, &num);
          
          //          fprintf(stderr, "radiobrowser: Browse children %d %d\n", start, num);
          
          ctx_id_orig = ctx_id;
          ctx_id += rb->root_id_len;
          
          gavl_array_init(&arr);
          
          if(*ctx_id == '\0')
            {
            if(start >= num_root_folders)
              break;

            if(!bg_mdb_adjust_num(start, &num, num_root_folders))
              break;
            
            for(i = 0; i < num; i++)
              {
              gavl_value_init(&val);
              dict = gavl_value_set_dictionary(&val);
              m = gavl_dictionary_get_dictionary_create(dict, GAVL_META_METADATA);
              gavl_dictionary_set_string(m, GAVL_META_LABEL, root_folders[i + start].label);
              
              if(!strcmp(root_folders[i + start].id, ROOT_BY_LANGUAGE))
                {
                gavl_track_set_num_children(dict, rb->languages.num_entries, 0);
                gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_LANGUAGE);
                }
              else if(!strcmp(root_folders[i + start].id, ROOT_BY_COUNTRY))
                {
                gavl_track_set_num_children(dict, rb->countries.num_entries, 0);
                gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_COUNTRY);
                }
              else if(!strcmp(root_folders[i + start].id, ROOT_BY_TAG))
                {
                gavl_track_set_num_children(dict, rb->tags.num_entries, 0);
                gavl_dictionary_set_string(m, GAVL_META_CHILD_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER_TAG);
                }
              gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, bg_sprintf("%s%s", rb->root_id,
                                                                            root_folders[i + start].id));

              gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER);
              
              gavl_array_splice_val_nocopy(&arr, -1, 0, &val);
              }
            total = num_root_folders;
            }
          else if(gavl_string_starts_with(ctx_id, ROOT_BY_LANGUAGE))
            {
            ctx_id += strlen(ROOT_BY_LANGUAGE);

            if(*ctx_id == '\0')
              {
              // /webradio/radiobrowser/language
              send_array(rb, ROOT_BY_LANGUAGE, &rb->languages, &arr, start, num);
              total = rb->languages.num_entries;
              }
            else
              {
              ctx_id++;

              if((pos = strchr(ctx_id, '/')))
                {
                // /webradio/radiobrowser/language/BlaBla/id
                }
              else
                {
                int num_stations;
                
                orig_id = get_orig_id(&rb->languages, ctx_id, &num_stations);

                if(!bg_mdb_adjust_num(start, &num, num_stations))
                  break;
                
                // /webradio/radiobrowser/language/Blabla
                url = bg_sprintf("%s/json/stations/bylanguageexact/%s?offset=%d&limit=%d",
                                 RADIO_BROWSER_ROOT, orig_id, start, num);
                send_stations(be, url, &arr, ctx_id_orig);
                free(url);
                total = num_stations;
                }
              }
            }
          else if(gavl_string_starts_with(ctx_id, ROOT_BY_COUNTRY))
            {
            ctx_id += strlen(ROOT_BY_COUNTRY);

            if(*ctx_id == '\0')
              {
              send_array(rb, ROOT_BY_COUNTRY, &rb->countries, &arr, start, num);
              total = rb->countries.num_entries;
              }
            else
              {
              // /webradio/radiobrowser/country/Blabla
              ctx_id++;
              if((pos = strchr(ctx_id, '/')))
                {
                // /webradio/radiobrowser/country/BlaBla/id
                
                }
              else
                {
                int num_stations;
                
                orig_id = get_orig_id(&rb->countries, ctx_id, &num_stations);

                if(!bg_mdb_adjust_num(start, &num, num_stations))
                  break;
                
                // /webradio/radiobrowser/country/BlaBla
                url = bg_sprintf("%s/json/stations/bycountrycodeexact/%s?offset=%d&limit=%d",
                                 RADIO_BROWSER_ROOT, orig_id, start, num);
                send_stations(be, url, &arr, ctx_id_orig);
                free(url);
                total = num_stations;
                }
              
              }
            
            }
          else if(gavl_string_starts_with(ctx_id, ROOT_BY_TAG))
            {
            ctx_id += strlen(ROOT_BY_TAG);

            if(*ctx_id == '\0')
              {
              send_array(rb, ROOT_BY_TAG, &rb->tags, &arr, start, num);
              total = rb->tags.num_entries;
              }
            else
              {
              ctx_id++;
              if((pos = strchr(ctx_id, '/')))
                {
                // /webradio/radiobrowser/tag/BlaBla/id
                
                }
              else
                {
                int num_stations = 0;
                                
                orig_id = get_orig_id(&rb->tags, ctx_id, &num_stations);

                /*                
                if(bg_mdb_container_create_pages(bg->db, &arr, int start,
                                                 int num, int total_children,
                                                 const char * child_class,
                                                 const char * parent_id);
                */
                                                 
                if(!bg_mdb_adjust_num(start, &num, num_stations))
                  break;
                
                // /webradio/radiobrowser/tag/BlaBla
                url = bg_sprintf("%s/json/stations/bytagexact/%s?offset=%d&limit=%d",
                                 RADIO_BROWSER_ROOT, orig_id, start, num);
                send_stations(be, url, &arr, ctx_id_orig);
                free(url);
                total = num_stations;
                }
              }
            }
          
          //          fprintf(stderr, "RB Browse children\n");
          res = bg_msg_sink_get(be->ctrl.evt_sink);

          bg_mdb_set_browse_children_response(res, &arr, msg, &start, 1, total);
          bg_msg_sink_put(be->ctrl.evt_sink, res);
          gavl_array_free(&arr);
          }
          break;
        }
      
      break;
      }
    case GAVL_MSG_NS_GENERIC:
      switch(msg->ID)
        {
        case GAVL_CMD_QUIT:
          return 0;
        }
      break;
    }
  
  return 1;
  }

/* tags, languages, countries */

static int compare_country(const void * p1, const void * p2)
  {
  const char * s1;
  const char * s2;
  
  const gavl_dictionary_t * dict1;
  const gavl_dictionary_t * dict2;

  if(!(dict1 = gavl_value_get_dictionary(p1)) ||
     !(dict2 = gavl_value_get_dictionary(p2)) ||
     !(s1 = gavl_dictionary_get_string(dict1, GAVL_META_LABEL)) ||
     !(s2 = gavl_dictionary_get_string(dict2, GAVL_META_LABEL)))
    return 0;
  
  return strcoll(s1, s2);
  }

static void load_array(bg_mdb_backend_t * be, gavl_array_t * ret,
                       const char * url, const char * cache_name)
  {
  int i, num;
  json_object * obj;
  //  const gavl_value_t * val;
  gavl_value_t val1;

#if 0  
  if((val = bg_object_cache_get_max_age(be->db->cache, url, MAX_CACHE_AGE)))
    {
    const gavl_array_t * arr;
    if((arr = gavl_value_get_array(val)))
      {
      gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Got: %s from cache", url);
      gavl_array_copy(ret, arr);
      bg_object_cache_done_get(be->db->cache);
      return;
      }
    bg_object_cache_done_get(be->db->cache);
    }
#endif
  
  if(!(obj = bg_json_from_url(url, NULL)))
    return;
  
  if(!json_object_is_type(obj, json_type_array))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Wrong type (must be array): %s",
           json_object_to_json_string(obj));
    }
  
  num = json_object_array_length(obj);

  for(i = 0; i < num; i++)
    {
    char md5[33];
    const char * v;
    gavl_value_t val;
    gavl_dictionary_t * dict;
    int num;    
    json_object * child = json_object_array_get_idx(obj, i);

    if(!json_object_is_type(child, json_type_object))
      continue;

    gavl_value_init(&val);
    dict = gavl_value_set_dictionary(&val);

    v = dict_get_string(child, "name");

     if(!strcmp(cache_name, "countries"))
       {
       gavl_dictionary_set_string(dict, GAVL_META_LABEL, gavl_get_country_label(v));
       gavl_dictionary_set_string(dict, GAVL_META_COUNTRY_CODE_2, v);
       }
    else
      gavl_dictionary_set_string(dict, GAVL_META_LABEL, v);
   
    gavl_dictionary_set_string(dict, RB_ID, v);

    gavl_dictionary_set_string(dict, GAVL_META_ID, bg_md5_buffer_str(v, strlen(v), md5));
    
    if((num = dict_get_int(child, "stationcount")) > 0)
      {
      gavl_dictionary_set_int(dict, GAVL_META_NUM_CHILDREN, num);
      gavl_dictionary_set_int(dict, GAVL_META_NUM_ITEM_CHILDREN, num);
      gavl_dictionary_set_int(dict, GAVL_META_NUM_CONTAINER_CHILDREN, 0);
      }
    
    gavl_array_splice_val_nocopy(ret, -1, 0, &val);
    }

  if(!strcmp(cache_name, "countries"))
    gavl_array_sort(ret, compare_country);
  
    
  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Loaded %s, %d entries", url, num);

  
  //  fprintf(stderr, "Loaded %s, %d entries", url, num);
  //  gavl_array_dump(ret, 2);
  //  fprintf(stderr, "\n");
  
  json_object_put(obj);

  gavl_value_init(&val1);
  val1.type = GAVL_TYPE_ARRAY;
  val1.v.array = ret;
  bg_object_cache_put(be->db->cache, url, &val1, -1);
  }
  
static int ping_rb(bg_mdb_backend_t * be)
  {
  rb_t * rb = be->priv;
  
  load_array(be, &rb->tags,      TAGS_URL, "tags");
  load_array(be, &rb->languages, LANGUAGES_URL, "languages");
  load_array(be, &rb->countries, COUNTRIES_URL, "countries");
  
  be->ping_func = NULL;
  return 1;
  }

void bg_mdb_create_radio_browser(bg_mdb_backend_t * b)
  {
  rb_t * priv;
  gavl_dictionary_t * container;
  gavl_dictionary_t * child;
  gavl_dictionary_t * child_m;
  const gavl_dictionary_t * container_m;
  
  priv = calloc(1, sizeof(*priv));
  
  container = bg_mdb_get_root_container(b->db, GAVL_META_MEDIA_CLASS_ROOT_WEBRADIO);
  
  child = gavl_append_track(container, NULL);
  
  child_m = gavl_track_get_metadata_nc(child);
  
  container_m = gavl_track_get_metadata(container);
  
  gavl_dictionary_set_string(child_m, GAVL_META_LABEL, "radio-browser.info");
  gavl_dictionary_set_string(child_m, GAVL_META_MEDIA_CLASS, GAVL_META_MEDIA_CLASS_CONTAINER);

  gavl_dictionary_set_string_nocopy(child_m, GAVL_META_ID,
                                    bg_sprintf("%s/radiobrowser",
                                               gavl_dictionary_get_string(container_m, GAVL_META_ID)));

  /* Update container children. Must be done after setting the media class */
  gavl_track_update_children(container);
  
  /* languaue, country, tag */
  gavl_track_set_num_children(child, 3, 0);
  
  priv->root_id     = gavl_strdup(gavl_dictionary_get_string(child_m, GAVL_META_ID));
  priv->root_id_len = strlen(priv->root_id);
  
  bg_mdb_container_set_backend(child, MDB_BACKEND_RADIO_BROWSER);
  
  bg_controllable_init(&b->ctrl,
                       bg_msg_sink_create(handle_msg, b, 0),
                       bg_msg_hub_create(1));
  
  b->priv = priv;
  b->destroy = destroy_rb;
  b->ping_func = ping_rb;
  }
