/*****************************************************************
 * 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 <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


#include "server.h"



#include <string.h>
#include <gmerlin/bgsocket.h>
#include <gmerlin/http.h>

#include <unistd.h>

#define LOG_DOMAIN "server"

#define TIMEOUT GAVL_TIME_SCALE/2000
#define ID3_CACHE_SIZE 50
#define ONDEMAND_IDLE_TIME (30*GAVL_TIME_SCALE)

#define VAR_PREFIX "mediaserver"

const char * storage_vars[] =
  {
    "tracklist",
    "bookmarks",
    "cfg",
    NULL,
  };

// #define DUMP_REQUESTS

static const bg_upnp_icon_t icons[] =
  {
    {
    .mimetype = "image/png",
    .width  = 16,
    .height = 16,
    .depth = 32,
    .location = "/static/icons/server_16.png"
    },
    {
    .mimetype = "image/png",
    .width  = 48,
    .height = 48,
    .depth = 32,
    .location = "/static/icons/server_48.png"
    },
    {
    .mimetype = "image/bmp",
    .width  = 48,
    .height = 48,
    .depth = 24,
    .location = "/static/icons/server_48.bmp"
    },
    {
    .mimetype = "image/jpeg",
    .width  = 48,
    .height = 48,
    .depth = 24,
    .location = "/static/icons/server_48.jpg"
    },
    {
    .mimetype = "image/png",
    .width  = 96,
    .height = 96,
    .depth = 32,
    .location = "/static/icons/server_96.png"
    },
    {
    .mimetype = "image/bmp",
    .width  = 96,
    .height = 96,
    .depth = 24,
    .location = "/static/icons/server_96.bmp"
    },
    {
    .mimetype = "image/jpeg",
    .width  = 96,
    .height = 96,
    .depth = 24,
    .location = "/static/icons/server_96.jpg"
    },
    { /* End */ },
  };

static const bg_parameter_info_t parameters[] =
  {
    {
      .name =      "loglevel",
      .type = BG_PARAMETER_INT,
      .long_name =  TRS("Log level"),
      .val_min = GAVL_VALUE_INIT_INT(0),
      .val_max = GAVL_VALUE_INIT_INT(4),
      .val_default = GAVL_VALUE_INIT_INT(3),
    },
    {
      .name =      "syslog",
      .long_name = TRS("Syslog name"),
      .type = BG_PARAMETER_STRING,
    },
    {
      .name =      "name",
      .long_name = TRS("upnp name"),
      .type = BG_PARAMETER_STRING,
      .val_default = GAVL_VALUE_INIT_STRING("Gmerlin server"),
    },
    {
      .name =      "db",
      .long_name = TRS("Database path"),
      .type = BG_PARAMETER_DIRECTORY,
      .val_default = GAVL_VALUE_INIT_STRING("."),
    },
    {
      .name =      "uuid",
      .type = BG_PARAMETER_STRING,
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .long_name =  TRS("UUID"),
    },
    {
      .name =      "vardir",
      .type = BG_PARAMETER_DIRECTORY,
      .long_name =  TRS("Storage directory"),
    },
    {
      .name =      "max_client_ids",
      .type = BG_PARAMETER_INT,
      .flags = BG_PARAMETER_HIDE_DIALOG,
      .long_name =  TRS("Maximum number of clients which can store data"),
      .val_default = GAVL_VALUE_INIT_INT(16),
      .val_min     = GAVL_VALUE_INIT_INT(0),
      .val_max     = GAVL_VALUE_INIT_INT(65535),
    },
    { /* End */ },
  };


const bg_parameter_info_t * server_get_parameters(server_t * s)
  {
  if(!s->parameters)
    {
    const bg_parameter_info_t * arr[3];
    arr[0] = bg_http_server_get_parameters(s->s);
    arr[1] = parameters;
    arr[2] = NULL;
    s->parameters = bg_parameter_info_concat_arrays(arr);
    }
  return s->parameters;
  }

static time_t get_db_mtime(server_t * s)
  {
  struct stat st;
  if(stat(bg_db_get_filename(s->db), &st))
    return 0;
  return st.st_mtime;
  }

void server_set_parameter(void * priv, const char * name,
                          const gavl_value_t * val)
  {
  server_t * s = priv;
  if(!name)
    {
    bg_http_server_set_parameter(s->s, NULL, NULL);
    return;
    }
  if(!strcmp(name, "db"))
    s->dbpath = gavl_strrep(s->dbpath, val->v.str);
  if(!strcmp(name, "vardir"))
    s->vardir = gavl_strrep(s->vardir, val->v.str);
  else if(!strcmp(name, "uuid"))
    s->uuid_str = gavl_strrep(s->uuid_str, val->v.str);
  else if(!strcmp(name, "name"))
    s->upnp_name = gavl_strrep(s->upnp_name, val->v.str);
  else if(!strcmp(name, "max_client_ids"))
    s->max_client_ids = val->v.i;
  else if(!strcmp(name, "syslog"))
    {
    if(val->v.str)
      gavl_log_syslog_init(val->v.str);
    }
  else if(!strcmp(name, "loglevel"))
    gavl_log_set_verbose_level(val->v.i);
  else
    bg_http_server_set_parameter(s->s, name, val);
  }

int server_get_parameter(void * priv, const char * name,
                          gavl_value_t * val)
  {
  server_t * s = priv;
  if(!name)
    return 0;
  if(!strcmp(name, "uuid"))
    {
    val->v.str = gavl_strrep(val->v.str, s->uuid_str);
    return 1;
    }
  else 
    return bg_http_server_get_parameter(s->s, name, val);
  }

void server_init(server_t * s)
  {
  memset(s, 0, sizeof(*s));
  pthread_mutex_init(&s->clients_mutex, NULL);
  s->remote_addr = bg_socket_address_create(); 
  s->s = bg_http_server_create();
  s->remote_reg = bg_remote_device_registry_create();
  }

static int server_handle_root(bg_http_connection_t * conn, void * data)
  {
  if(!strcmp(conn->path, "/") || !strncmp(conn->path, "/?", 2))
    {
    char * new_path = NULL;
    const char * url_vars;

    if(!(url_vars = strchr(conn->path, '?')))
      url_vars = "";

    new_path = bg_sprintf("/static/app.html%s", url_vars);

    bg_http_request_set_path(&conn->req, new_path);
    conn->path = bg_http_request_get_path(&conn->req);
    free(new_path);
    }
  return 0;
  }


int server_start(server_t * s)
  {
  char * tmp_path;
  bg_cfg_section_t * cfg_section;
  int i;

  /* Create vardir */
  if(!s->vardir)
    s->vardir = bg_search_var_dir(VAR_PREFIX);
  
  /* Create plugin registry */

  s->cfg_reg = bg_cfg_registry_create();
  tmp_path =  bg_search_file_read("generic", "cfg.xml");
  bg_cfg_registry_load(s->cfg_reg, tmp_path);
  if(tmp_path)
    free(tmp_path);

  cfg_section = bg_cfg_registry_find_section(s->cfg_reg, "plugins");
  s->plugin_reg = bg_plugin_registry_create(cfg_section);
  
  /* Create listen socket: After that we'll have the root URL we can
     advertise */
 
  bg_http_server_start(s->s);
  
  s->root_url = bg_http_server_get_root_url(s->s);

  /* Remember UUID */

  if(s->uuid_str)
    uuid_parse(s->uuid_str, s->uuid);
  else
    {
    uuid_clear(s->uuid);
    uuid_generate(s->uuid);
    s->uuid_str = calloc(37, 1);
    uuid_unparse(s->uuid, s->uuid_str);
    gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Generated UUID: %s",
           s->uuid_str);
    }
  
  /* Create DB */

  if(s->dbpath)
    {
    s->db = bg_db_create(s->dbpath, s->plugin_reg, 0);

    if(!s->db)
      gavl_log(GAVL_LOG_INFO, LOG_DOMAIN,
             "Database in %s couldn't be opened, disabling upnp", s->dbpath);
    }
  else
    gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "No database given, disabling upnp");

  
  if(s->db && s->vardir)
    {
    
    if(s->max_client_ids)
      {
      tmp_path = bg_sprintf("%s/storage", s->vardir);
      if(tmp_path)
        {
        s->storage = bg_server_storage_create(tmp_path,
                                              s->max_client_ids,
                                              storage_vars);

        bg_http_server_add_handler(s->s, bg_server_storage_handle_http,
                                   BG_HTTP_PROTO_HTTP, "/storage/",
                                   s->storage);
        
        free(tmp_path);
        }
      }
    }
  
  bg_http_server_add_handler(s->s, server_handle_root, BG_HTTP_PROTO_HTTP, NULL, s);

  //  s->ws = bg_websocket_context_create(BG_REMOTE_DEVICE_MEDIASERVER, s->s, NULL);
  
  if(s->db) 
    {
    /* Create UPNP device(s) */
    s->dev = bg_upnp_create_media_server(s->s,
                                         s->uuid,
                                         s->upnp_name,
                                         icons,
                                         s->db);
    s->server_string = bg_upnp_device_get_server_string(s->dev);
    s->id3_cache = calloc(ID3_CACHE_SIZE, sizeof(*s->id3_cache));
    s->id3_cache_size = ID3_CACHE_SIZE;
        
    for(i = 0; i < ID3_CACHE_SIZE; i++)
      {
      s->id3_cache[i].last_used = GAVL_TIME_UNDEFINED;
      s->id3_cache[i].id = -1;
      }
   
    bg_http_server_add_handler(s->s, server_handle_media, BG_HTTP_PROTO_HTTP, "/media/", s);
    bg_http_server_add_handler(s->s, server_handle_transcode, BG_HTTP_PROTO_HTTP, "/transcode/", s);
    bg_http_server_add_handler(s->s, server_handle_ondemand, BG_HTTP_PROTO_HTTP|BG_HTTP_PROTO_RTSP, "/ondemand/", s);
    bg_http_server_add_handler(s->s, server_handle_recompress, BG_HTTP_PROTO_HTTP, "/recompress/", s);
    bg_http_server_add_handler(s->s, server_handle_pls, BG_HTTP_PROTO_HTTP, "/pls/", s);
    bg_http_server_add_handler(s->s, server_handle_m3u, BG_HTTP_PROTO_HTTP, "/m3u/", s);
    bg_http_server_add_handler(s->s, server_handle_xspf, BG_HTTP_PROTO_HTTP, "/xspf/", s);
    bg_http_server_add_handler(s->s, handle_xml, BG_HTTP_PROTO_HTTP, "/xml/", s);
    bg_http_server_add_handler(s->s, handle_json, BG_HTTP_PROTO_HTTP, "/json/", s);
    bg_http_server_add_handler(s->s, device_proxy_handle, BG_HTTP_PROTO_HTTP, BG_REMOTE_DEVICE_PATH, s);
    
    if(s->vardir)
      {
      tmp_path = bg_sprintf("%s/storage", s->vardir);
      //  bg_http_server_set_client_storage(s->s, "/storage/", tmp_path);
      free(tmp_path);
      }
    
    
    }
  
  bg_http_server_add_handler(s->s, server_handle_source, BG_HTTP_PROTO_HTTP, NULL, s);
  bg_http_server_add_handler(s->s, server_handle_stream, BG_HTTP_PROTO_HTTP|BG_HTTP_PROTO_RTSP, NULL, s);

  bg_http_server_set_static_path(s->s, "/static/");
  return 1;
  }

void server_attach_client(server_t * s, client_t*cl)
  {
  pthread_mutex_lock(&s->clients_mutex);
  if(s->num_clients + 1 >= s->clients_alloc)
    {
    s->clients_alloc += 16;
    s->clients = realloc(s->clients, s->clients_alloc * sizeof(*s->clients));
    memset(s->clients + s->num_clients, 0,
           (s->clients_alloc - s->num_clients) * sizeof(*s->clients));
    }
  s->clients[s->num_clients] = cl;
  s->num_clients++;
  pthread_mutex_unlock(&s->clients_mutex);
  }


static void server_destroy_client(server_t * s, int idx)
  {
  int i = idx+1;
  // We assume that sinks always come after their source
  while(i < s->num_clients) 
    {
    if(s->clients[i]->source == s->clients[idx])
      server_destroy_client(s, i);
    else
      i++;
    }
  client_destroy(s, s->clients[idx]);

  if(idx < s->num_clients - 1)
    {
    memmove(s->clients + idx,
            s->clients + idx + 1,
           (s->num_clients - 1 - idx) * sizeof(*s->clients));
    }
  s->num_clients--;
  }


int server_iteration(server_t * s)
  {
  int ret = 0;
  int i, j;
  
  gavl_time_t delay_time = GAVL_TIME_SCALE / 100; // 10 ms

  s->current_time = bg_http_server_get_time(s->s);
  
  /* Remove dead clients */
  i = 0;
  while(i < s->num_clients)
    {
    if(client_get_status(s->clients[i]) == CLIENT_STATUS_DONE)
      {
      server_destroy_client(s, i);
      ret++;
      }
    i++;
    }
  
  /* Remove on-demand sources with zero sinks */

  i = 0;
  while(i < s->num_clients)
    {
    if(s->clients[i]->type == CLIENT_TYPE_SOURCE_ONDEMAND)
      {
      int have_clients = 0;
      j = 0;
      
      while(j < s->num_clients)
        {
        if(s->clients[j]->source == s->clients[i])
          {
          have_clients = 1;
          break;
          }
        j++;
        }
      if(!have_clients)
        {
        if(s->clients[i]->idle_time == GAVL_TIME_UNDEFINED)
          s->clients[i]->idle_time = s->current_time;
        else if(s->current_time - s->clients[i]->idle_time > ONDEMAND_IDLE_TIME)
          {
          gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Shutting down ondemand stream (idle timeout)");
          server_destroy_client(s, i);
          continue;
          }
        }
      else
        s->clients[i]->idle_time = GAVL_TIME_UNDEFINED;
      }
    i++;
    }
  
  ret += bg_http_server_iteration(s->s);

  /* Ping upnp device so it can do it's ssdp stuff */

  if(s->dev)
    ret += bg_upnp_device_ping(s->dev);
  
  if(!ret)
    gavl_time_delay(&delay_time);
  
  return 1;
  }

void server_cleanup(server_t * s)
  {
  int i;
  if(s->dev)
    bg_upnp_device_destroy(s->dev);
  if(s->db)
    bg_db_destroy(s->db);
  if(s->dbpath)
    free(s->dbpath);
  if(s->vardir)
    free(s->vardir);


  if(s->storage)
    bg_server_storage_destroy(s->storage);
  
  bg_socket_address_destroy(s->remote_addr);
  
  for(i = 0; i < s->num_clients; i++)
    {
    if((s->clients[i]->type != CLIENT_TYPE_SOURCE_ONDEMAND) &&
       (s->clients[i]->type != CLIENT_TYPE_SOURCE_STREAM))
      {
      client_destroy(s, s->clients[i]);
      s->clients[i] = NULL;
      }
    }
  for(i = 0; i < s->num_clients; i++)
    {
    if(s->clients[i])
      client_destroy(s, s->clients[i]);
    }
  
  if(s->clients)
    free(s->clients);

  if(s->id3_cache)
    {
    for(i = 0; i < s->id3_cache_size; i++)
      {
      if(s->id3_cache[i].data)
        free(s->id3_cache[i].data);
      }
    free(s->id3_cache);
    }
  
  if(s->uuid_str)
    free(s->uuid_str);
  if(s->upnp_name)
    free(s->upnp_name);
  
  bg_plugin_registry_destroy(s->plugin_reg);
  bg_cfg_registry_destroy(s->cfg_reg);

  pthread_mutex_destroy(&s->clients_mutex);

  //  if(s->ws)
  //    bg_websocket_context_destroy(s->ws);
  
  bg_http_server_destroy(s->s);
  
  if(s->parameters)
    bg_parameter_info_destroy_array(s->parameters);

  if(s->remote_reg)
    bg_remote_device_registry_destroy(s->remote_reg);
  }

char * server_extract_mimetype(const char * pos)
  {
  const char * pos2;
  if(*pos == '/')
    {
    pos++;
    pos2 = strchr(pos, '/');
    
    if(pos2) // Have mimetype at pos
      {
      pos2 = strchr(pos2 + 1, '/');
      if(pos2)
        return gavl_strndup(pos, pos2);
      else
        return gavl_strdup(pos);
      }
    }
  return NULL;
  }

int server_get_protocol(const gavl_dictionary_t * req)
  {
  const char * protocol = bg_http_request_get_protocol(req);
  
  if(!protocol)
    return 0;
  
  if(!strncmp(protocol, "HTTP/", 5))
    return PROTOCOL_HTTP;
  else if(!strncmp(protocol, "RTSP/", 5))
    return PROTOCOL_RTSP;
  return 0;
  }

