/*****************************************************************
 * 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 "server.h"
#include <gmerlin/http.h>
#include <gmerlin/xspf.h>
#include <gavl/metatags.h>

#include <string.h>
#include <signal.h>
#include <ctype.h>

#define LOG_DOMAIN "server.pls"

#define FORMAT_M3U  0
#define FORMAT_PLS  1
#define FORMAT_XSPF 2

typedef struct 
  {
  server_t * s;
  const char * path;
  bg_db_object_t * obj;
  int local;
  } generate_t;

static int64_t get_duration_seconds(bg_db_object_t * obj)
  {
  if(obj->duration > 0)
    return obj->duration / GAVL_TIME_SCALE;
  else
    return -1;
  }

static int do_browse_children(bg_db_object_t * obj)
  {
  int ret = 0;
  switch(obj->type)
    {
    case BG_DB_OBJECT_AUDIO_ALBUM:
    case BG_DB_OBJECT_PLAYLIST:
    case BG_DB_OBJECT_SEASON:
    case BG_DB_OBJECT_MOVIE_MULTIPART:
      ret = 1;
      break;
    default:
      break;
    }
  return ret;
  }

typedef struct
  {
  const server_t * s;
  char * ret;
  int index;
  int encode;

  xmlDocPtr doc;
  
  gavl_dictionary_t * m;
  
  int is_local;
  bg_db_object_type_t parent_type;
  } browse_t;

static char * get_object_name(void * o)
  {
  switch(bg_db_object_get_type(o))
    {
    case BG_DB_OBJECT_AUDIO_FILE:
      {
      bg_db_audio_file_t * af = o;
      if(af->title && af->artist)
        return bg_sprintf("%s - %s", af->artist, af->title);
      break;
      }
    default:
      break;
    }
  return gavl_strdup(bg_db_object_get_label(o));
  }

static char * make_location(browse_t * b, void * o, int local)
  {
  char * path;
  char * path_enc;
  char * ret;

  bg_db_object_t * obj = o;
  if(local)
    {
    if(obj->type & BG_DB_FLAG_FILE)
      {
      bg_db_file_t * f = o;
      return bg_sprintf("file://%s", f->path);
      }
    }

  if(obj->type & BG_DB_FLAG_FILE)
    {
    bg_db_file_t * f = o;
    path = bg_sprintf("media/%"PRId64"/%s.%s", obj->id,
                     obj->label, bg_mimetype_to_ext(f->mimetype));
    }
  if(!path)
    path = bg_sprintf("media/%"PRId64, obj->id);
  
  path_enc = bg_string_to_uri(path, -1);
  ret = bg_sprintf("%s/%s", b->s->root_url, path_enc);

  free(path_enc);
  free(path);

  return ret;
  }

static void query_child_pls(void * priv, void * o)
  {
  char * str = NULL;
  char * name;
  
  bg_db_object_t * obj = o;
  browse_t * b = priv;

  char * location = make_location(b, o, b->is_local);
  
  b->index++;

  name = get_object_name(o);
  
  str = bg_sprintf("File%d=%s\r\n"
                   "Title%d=%s\r\n"
                   "Length%d=%"PRId64"\r\n",
                   b->index,
                   location, b->index, name, b->index,
                   get_duration_seconds(obj));
  b->ret = gavl_strcat(b->ret, str);
  free(str);
  free(name);
  free(location);
  }

static void query_child_m3u(void * priv, void * o)
  {
  char * str = NULL;
  browse_t * b = priv;
  bg_db_object_t * obj = o;
  char * name;
  
  char * location = make_location(b, o, b->is_local);
  name = get_object_name(o);
  
  if(!str)
    str = bg_sprintf("#EXTINF:%"PRId64", %s\r\n"
                     "%s\r\n",
                     get_duration_seconds(obj), name, location);
  
  b->ret = gavl_strcat(b->ret, str);
  free(str);
  free(name);
  free(location);
  }

static void cover_th_query_callback(void * priv,  void * obj)
  {
  char * location;
  browse_t * b = priv;
  gavl_dictionary_t * m = b->m;
  bg_db_image_file_t * img = obj;

  location = make_location(b, obj, 0);
  
  gavl_metadata_add_image_uri(m,
                              GAVL_META_COVER_URL,
                              img->width, img->height,
                              img->file.mimetype,
                              location);
  free(location);
  }
  
static void query_child_xspf(void * priv, void * o)
  {
  //  gavl_time_t duration;
  gavl_dictionary_t m;
  browse_t * b = priv;
  bg_db_object_t * obj = o;
  char * location = make_location(b, o, b->is_local);

  gavl_dictionary_init(&m);
  gavl_metadata_add_src(&m, GAVL_META_SRC, NULL, location);
  
  if(obj->duration > 0)
    gavl_dictionary_set_long(&m, GAVL_META_APPROX_DURATION, obj->duration);

  if(obj->type == BG_DB_OBJECT_AUDIO_FILE)
    {
    char * label = NULL;
    bg_db_audio_file_t * song = o;
    if(song->artist)
      gavl_dictionary_set_string(&m, GAVL_META_ARTIST, song->artist);

    if(song->title)
      gavl_dictionary_set_string(&m, GAVL_META_TITLE, song->title);

    if(song->track > 0)
      gavl_dictionary_set_int(&m, GAVL_META_TRACKNUMBER, song->track);
    
    
    if(song->album_id > 0)
      {
      bg_db_audio_album_t * album =
        bg_db_object_query(b->s->db, song->album_id);
      gavl_dictionary_set_string(&m, GAVL_META_ALBUM, album->title);

      if(album->cover_id > 0)
        {
        b->m = &m;
        /* Get thumbnails */
        bg_db_browse_thumbnails(b->s->db, album->cover_id,
                                cover_th_query_callback, b);
        }
      bg_db_object_unref(album);
      }

    if(b->parent_type == BG_DB_OBJECT_PLAYLIST)
      label = bg_create_track_name(&m, "%p - %t");

    if(label)
      gavl_dictionary_set_string_nocopy(&m, GAVL_META_LABEL, label);
    else
      gavl_dictionary_set_string(&m, GAVL_META_LABEL, bg_db_object_get_label(o));
    }
  bg_xspf_add_track(b->doc, &m);
  gavl_dictionary_free(&m);
  }

static uint8_t *
generate_pls(void * data, int * len)
  {
  char * ret = NULL;
  generate_t * g = data;
  server_t * s = g->s;
  const char * path = g->path;
  bg_db_object_t * obj = g->obj;
  int local = g->local;
  
  if(!obj && !path)
    return NULL;
  
  if(obj)
    {
    if(!path)
      {
      browse_t b;
      b.is_local = local;
      b.ret = bg_sprintf("[playlist]\r\n"
                         "numberofentries=%d\r\n", obj->children);
      b.s = s;
      b.index = 0;
      b.parent_type = bg_db_object_get_type(obj);
      
      if(do_browse_children(obj)) 
        bg_db_query_children(s->db, obj, query_child_pls, &b,
                             0, 0, NULL);
      else
        query_child_pls(&b, obj);
      
      b.ret = gavl_strcat(b.ret, "Version=2\r\n");    
      return (uint8_t *)b.ret;
      }
    else // path
      {
      char * name;
      name = get_object_name(obj);
      
      ret = bg_sprintf("[playlist]\r\n"
                       "numberofentries=1\r\n"
                       "File1=%s/%s\r\n"
                       "Title1=%s\r\n"
                       "Length1=%"PRId64"\r\n"
                       "Version=2\r\n",
                       s->root_url, path, name, get_duration_seconds(obj));
      free(name);
      }
    }
  else
    ret = bg_sprintf("[playlist]\r\n"
                     "numberofentries=1\r\n"
                     "File1=%s/%s\r\n"
                     "Length1=-1\r\n"
                     "Version=2\r\n",
                     s->root_url, path);

  if(ret)
    *len = strlen(ret);
  else
    *len = 0;
  
  return (uint8_t*)ret;
  }

static uint8_t *
generate_xspf(void * data, int * len)
  {
  xmlDocPtr xspf;

  char * ret = NULL;
  generate_t * g = data;
  server_t * s = g->s;
  const char * path = g->path;
  bg_db_object_t * obj = g->obj;
  int local = g->local;

  xspf = bg_xspf_create();

  if(obj)
    {
    if(!path) // Browse children
      {
      browse_t b;
      b.is_local = local;
      b.doc = xspf;
      b.s = s;
      b.parent_type = bg_db_object_get_type(obj);
      
      if(do_browse_children(obj))
        bg_db_query_children(s->db, obj, query_child_xspf, &b,
                             0, 0, NULL);
      else
        query_child_xspf(&b, obj);
      }
    else // Add this object
      {
      
      }
    }
  else // Only path
    {
    
    }
    
      
  ret = bg_xml_save_to_memory(xspf);
  xmlFreeDoc(xspf);

  if(ret)
    *len = strlen(ret);
  else
    *len = 0;
  
  return (uint8_t*)ret;
  }

static uint8_t *
generate_m3u(void * data, int * len)
  {
  char * ret = NULL;
  generate_t * g = data;
  server_t * s = g->s;
  const char * path = g->path;
  bg_db_object_t * obj = g->obj;
  int local = g->local;
  
  if(!obj && !path)
    return NULL;
  
  if(obj)
    {
    if(!path)
      {
      browse_t b;
      b.ret = bg_sprintf("#EXTM3U\r\n");
      b.s = s;
      b.is_local = local;
      b.parent_type = bg_db_object_get_type(obj);
      
      if(do_browse_children(obj))
        bg_db_query_children(s->db, obj, query_child_m3u, &b,
                             0, 0, NULL);
      else
        query_child_m3u(&b, obj);
      ret = b.ret;
      }
     else
       {
      char * name;
      name = get_object_name(obj);

      ret = bg_sprintf("#EXTM3U\r\n"
                       "#EXTINF:%"PRId64", %s\r\n"
                       "%s/%s\r\n",
                       get_duration_seconds(obj), obj->label, s->root_url, path);
      
      free(name);
      }
    }
  else
    ret = bg_sprintf("#EXTM3U\r\n"
                     "#EXTINF:-1,\r\n"
                     "%s/%s\r\n",
                     s->root_url, path);

  if(ret)
    *len = strlen(ret);
  else
    *len = 0;
  
  return (uint8_t*)ret;
  }

// http://host:port/[pls|m3u]/id[/filename.ext]


static int handle_pls(server_t * s,
                      bg_http_connection_t * conn,
                      int format)
  {
  generate_t g;
  bg_db_object_t * o = NULL;
  char * rest;
  int64_t id;
  uint8_t * ret = NULL;
  int len = 0;
  int local = 0;
  const bg_upnp_client_t * cl;
  const char * mimetype;
  char * tmp_string;
  const char * var;
  
  gavl_dictionary_t url_vars;

  gavl_dictionary_init(&url_vars);

  tmp_string = gavl_strdup(conn->path);
  bg_url_get_vars(tmp_string, &url_vars);
  free(tmp_string);

  if((var = gavl_dictionary_get_string(&url_vars, "mode")) &&
     !strcmp(var, "local"))
    local = 1;
  
  if(!s->db)
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                404, "Not Found");
    goto fail;
    }

  /* Get the ID from the path */

  if(isdigit(*conn->path))
    {
    id = strtoll(conn->path, &rest, 10);

    if((*rest != '/') && (*rest != '\0'))
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 
                                 404, "Not Found");
      goto fail;
      }
    o = bg_db_object_query(s->db, id);

    if(!o)
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 
                                  404, "Not Found");
      goto fail;
      }
    conn->path = NULL;
    }
  
  g.s = s;
  g.path = conn->path;
  g.obj = o;
  g.local = local;
  
  switch(format)
    {
    case FORMAT_PLS:
      mimetype = "audio/x-scpls";
      ret = generate_pls(&g, &len);
      break;
    case FORMAT_M3U:
      mimetype = "audio/x-mpegurl";
      ret = generate_m3u(&g, &len);
      break;
    case FORMAT_XSPF:
      mimetype = "application/xspf+xml";
      ret = generate_xspf(&g, &len);
      break;
    }

  if(!ret)
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                404, "Not Found");
    goto fail;
    }
  
  bg_http_connection_init_res(conn, "HTTP/1.1", 200, "OK");

  cl = bg_upnp_detect_client(&conn->req);

  gavl_dictionary_set_string(&conn->res, "Content-Type", bg_upnp_client_translate_mimetype(cl, mimetype));

  if(s->server_string)
    gavl_dictionary_set_string(&conn->res, "Server", s->server_string);
  
  fail:
  
  if(o)
    bg_db_object_unref(o);
  
  if(ret)
    gavl_dictionary_set_int(&conn->res, "Content-Length", len);
  
  bg_http_connection_check_keepalive(conn);

  if(bg_http_server_write_res(s->s, conn))
    {
    if(ret)
      {
      if(!bg_socket_write_data(conn->fd, (const uint8_t*)ret, len))
        bg_http_connection_clear_keepalive(conn);
      free(ret);
      }
    }
    
  gavl_dictionary_free(&url_vars);
  
  return 1;
  }

int server_handle_pls(bg_http_connection_t * conn, void * data)
  {
  server_t * s = data;
  return handle_pls(s, conn, FORMAT_PLS);
  }

int server_handle_m3u(bg_http_connection_t * conn, void * data)
  {
  server_t * s = data;
  return handle_pls(s, conn, FORMAT_M3U);
  }

int server_handle_xspf(bg_http_connection_t * conn, void * data)
  {
  server_t * s = data;
  return handle_pls(s, conn, FORMAT_XSPF);
  }
