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

#include "server.h"
#include "rtp.h"


#define LOG_DOMAIN "server.filter"


/* */

/*
 *  bgplug
 */

static int probe_bgplug(int protocol,
                        const gavf_program_header_t * ph,
                        const gavl_dictionary_t * request)
  {
  return 1;
  }

static void * create_bgplug(server_t * s,
                            const gavf_program_header_t * ph,
                            const gavl_dictionary_t * req,
                            const gavl_dictionary_t * inline_metadata,
                            gavl_dictionary_t * res)
  {
  bg_plug_t * ret;
  const char * var;

  ret = bg_plug_create_writer(s->plugin_reg);

  if(!(var = gavl_dictionary_get_string(req, "User-Agent")) ||
     strcmp(var, bg_plug_app_id))
    {
    gavl_dictionary_set_string(res, "Content-Type", "video/gavf");
    }
  else
    {
    gavl_dictionary_set_string(res, "Content-Type", bg_plug_mimetype);
    }
  
  return ret;
  }

static int start_bgplug(void * priv,
                        const gavf_program_header_t * ph,
                        const gavl_dictionary_t * req,
                        const gavl_dictionary_t * inline_metadata,
                        int fd)
  {
  const char * var;
  int flags = 0;
  gavf_io_t * io;
  
  //  gavl_value_t val;

  bg_plug_t * p = priv;

  if(!(var = gavl_dictionary_get_string(req, "User-Agent")) ||
     strcmp(var, bg_plug_app_id))
    {
    /* No bgplug: Disable SHM */
    gavl_value_t val;
    gavl_value_init(&val);
    gavl_value_set_int(&val, 0);
    bg_plug_set_parameter(p, "shm", &val);
    }
  
  if(!(io = bg_plug_io_open_socket(fd, BG_PLUG_IO_METHOD_WRITE, &flags,
                                   CLIENT_TIMEOUT)))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Creating client I/O module failed");
//    bg_plug_response_set_status(res, BG_PLUG_IO_STATUS_406);
    return 0;
    }
  
  //  fprintf(stderr, "start_bgplug\n");

  //  val.val_i = 1024;
  //  gavl_dictionary_get_string_int(vars, "shm", &val.val_i);
  //  bg_plug_set_parameter(p, "shm", &val);

  if(!bg_plug_open(p, io, NULL, flags))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "bg_plug_open failed");
    return 0;
    }
  
  if(!bg_plug_set_from_ph(p, ph))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "bg_plug_set_from_ph failed");
    return 0;
    }
  if(inline_metadata)
    gavf_update_metadata(bg_plug_get_gavf(p), inline_metadata);

  return 1;  
  }

static int put_buffer_bgplug(void * priv, buffer_element_t * el)
  {
  bg_plug_t * p = priv;
  //  fprintf(stderr, "Put buffer bgplug\n");

  switch(el->type)
    {
    case BUFFER_TYPE_PACKET:
      if(bg_plug_put_packet(p, &el->p) != GAVL_SINK_OK)
        return 0;
      break;
    case BUFFER_TYPE_METADATA:
      if(!gavf_update_metadata(bg_plug_get_gavf(p),
                               &el->m))
        return 0;
      break;
    }
  return 1;
  }

static void destroy_bgplug(void * priv)
  {
  bg_plug_t * p = priv;
  bg_plug_destroy(p);
  }

/*
 *  Generic filters: raw or with muxer and optional
 *  icy metadata
 */

typedef struct
  {
  icy_t m;
  gavf_io_t * io_ext;
  gavf_io_t * io_int;
  
  bg_plugin_handle_t * h;
  bg_encoder_plugin_t * plugin;

  struct
    {
    const gavf_stream_header_t * h;
    int idx;
    gavl_packet_sink_t * psink;
    } * streams;
  
  int num_streams;
  
  } filter_generic_t;

static int supported_generic(bg_plugin_registry_t * plugin_reg, const char * plugin)
  {
  if(plugin && !bg_plugin_find_by_name(plugin_reg, plugin))
    return 0;
  return 1;
  }

static void * create_generic(server_t * s,
                             const gavf_program_header_t * ph,
                             const gavl_dictionary_t * req,
                             const gavl_dictionary_t * inline_metadata,
                             gavl_dictionary_t * res,
                             const char * plugin_name, int use_icy)
  {
  const bg_plugin_info_t * info;
  filter_generic_t * ret = calloc(1, sizeof(*ret));
  
  if(use_icy)
    icy_init(&ret->m, ph, req, inline_metadata, res);

  if(plugin_name)
    {
    /* Load and initialize plugin */
    info = bg_plugin_find_by_name(s->plugin_reg, plugin_name);
    ret->h = bg_plugin_load(s->plugin_reg, info);
    ret->plugin = (bg_encoder_plugin_t*)(ret->h->plugin);
    }
  
  //  v.val_str = "adts";
  //  ret->plugin->common.set_parameter(ret->h->priv, "format", &v);
  
  return ret; 
  }

static int write_icy(void * priv, const uint8_t * data, int len)
  {
  filter_generic_t * f = priv;
  return icy_write(&f->m, data, len, f->io_ext);
  }


static int start_generic(void * priv,
                         const gavf_program_header_t * ph,
                         const gavl_dictionary_t * req,
                         const gavl_dictionary_t * inline_metadata,
                         int fd)
  {
  filter_generic_t * m = priv;
  int flags = 0;
  int i;
  const gavf_stream_header_t * h;
  
  gavl_dictionary_t empty;
  gavl_dictionary_init(&empty);
  
  if(!(m->io_ext = bg_plug_io_open_socket(fd, BG_PLUG_IO_METHOD_WRITE, &flags,
                                          CLIENT_TIMEOUT)))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Creating client I/O module failed");
    return 0;
    }

  if(!m->plugin)
    return 1; // Raw
  
  if(m->m.metaint > 0)
    {
    m->io_int = gavf_io_create(NULL, write_icy, NULL, NULL, NULL, m);
    m->plugin->open_io(m->h->priv, m->io_int, &empty);
    }
  else
    m->plugin->open_io(m->h->priv, m->io_ext, &empty);
  
  m->streams = calloc(ph->num_streams, sizeof(*m->streams));

  for(i = 0; i < ph->num_streams; i++)
    {
    h = &ph->streams[i];
    switch(h->type)
      {
      case GAVF_STREAM_AUDIO:
        if(!m->plugin->writes_compressed_audio(m->h->priv,
                                               &h->format.audio,
                                               &h->ci))
          continue;
        
        m->streams[m->num_streams].idx = 
          m->plugin->add_audio_stream_compressed(m->h->priv,
                                                 &h->m,
                                                 &h->format.audio,
                                                 &h->ci);
        m->streams[m->num_streams].h = h;
        m->num_streams++;
        break;
      case GAVF_STREAM_VIDEO:
        if(!m->plugin->writes_compressed_video(m->h->priv,
                                               &h->format.video,
                                               &h->ci))
          continue;
        
        m->streams[m->num_streams].idx = 
          m->plugin->add_video_stream_compressed(m->h->priv,
                                                 &h->m,
                                                 &h->format.video,
                                                 &h->ci);
        m->streams[m->num_streams].h = h;
        m->num_streams++;
        break;
      case GAVF_STREAM_TEXT:
        break;
      case GAVF_STREAM_OVERLAY:
        break;
      case GAVF_STREAM_NONE:
      case GAVF_STREAM_MSG:
        break;
      }
    }
  
  if(m->plugin->update_metadata)
    m->plugin->update_metadata(m->h->priv, inline_metadata);
  
  if(!m->plugin->start(m->h->priv))
    return 0;

  for(i = 0; i < ph->num_streams; i++)
    {
    h = m->streams[i].h;
    switch(h->type)
      {
      case GAVF_STREAM_AUDIO:
        m->streams[i].psink = m->plugin->get_audio_packet_sink(m->h->priv, m->streams[i].idx);
        break;
      case GAVF_STREAM_VIDEO:
        m->streams[i].psink = m->plugin->get_video_packet_sink(m->h->priv, m->streams[i].idx);
        break;
      case GAVF_STREAM_TEXT:
        break;
      case GAVF_STREAM_OVERLAY:
        break;
      case GAVF_STREAM_NONE:
      case GAVF_STREAM_MSG:
        break;
      }
    }
  return 1;
  }

static int put_buffer_generic_raw(void * priv, buffer_element_t * el)
  {
  filter_generic_t * m = priv;
  
  //  fprintf(stderr, "Put buffer mp3\n");
  
  switch(el->type)
    {
    case BUFFER_TYPE_PACKET:
      if(m->m.metaint > 0)
        return icy_write(&m->m, el->p.data, el->p.data_len, m->io_ext);
      else
        return gavf_io_write_data(m->io_ext, el->p.data, el->p.data_len);
      break;
    case BUFFER_TYPE_METADATA:
      if(m->m.metaint > 0)
        icy_update_metadata(&m->m, &el->m);
      return 1;
      break;
    }
  return 1;
  }

static int put_buffer_generic_plugin(void * priv, buffer_element_t * el)
  {
  filter_generic_t * m = priv;

  //  fprintf(stderr, "Put buffer mp3\n");
  
  switch(el->type)
    {
    case BUFFER_TYPE_PACKET:
      {
      int i;
      for(i = 0; i < m->num_streams; i++)
        {
        if(m->streams[i].h->id == el->p.id)
          return (gavl_packet_sink_put_packet(m->streams[i].psink, &el->p) == GAVL_SINK_OK);
        }
      }
      break;
    case BUFFER_TYPE_METADATA:
      if(m->m.metaint > 0)
        icy_update_metadata(&m->m, &el->m);
      if(m->plugin->update_metadata)
        m->plugin->update_metadata(m->h->priv, &el->m);
      return 1;
      break;
    }
  return 1;
  }

static void destroy_generic(void * priv)
  {
  filter_generic_t * m = priv;

  if(m->h)
    {
    m->plugin->close(m->h->priv, 0);
    bg_plugin_unref(m->h);
    }
  
  if(m->io_int)
    gavf_io_destroy(m->io_int);
  if(m->io_ext)
    gavf_io_destroy(m->io_ext);
  
  icy_free(&m->m);

  if(m->streams)
    free(m->streams);
  
  free(m);
  }

static int probe_audio_generic(int protocol,
                               const gavf_program_header_t * ph,
                               gavl_codec_id_t id)
  {
  if(protocol != PROTOCOL_HTTP)
    return 0;
  if(ph->num_streams != 1)
    return 0;
  if(ph->streams[0].type != GAVF_STREAM_AUDIO)
    return 0;
  if(ph->streams[0].ci.id != id)
    return 0;
  //  if(ph->streams[0].ci.bitrate == GAVL_BITRATE_VBR)
  //    return 0;
  return 1;
  }

static int probe_generic(int protocol,
                         const gavf_program_header_t * ph,
                         const gavl_codec_id_t * ids)
  {
  int i, j;
  int supported;
  
  if(protocol != PROTOCOL_HTTP)
    return 0;

  for(i = 0; i < ph->num_streams; i++)
    {
    supported = 0;
    
    if((ph->streams[i].type != GAVF_STREAM_AUDIO) &&
       (ph->streams[i].type != GAVF_STREAM_VIDEO))
      continue;

    j = 0;
    while(ids[j] != GAVL_CODEC_ID_NONE)
      {
      if(ids[j] == ph->streams[i].ci.id)
        {
        supported = 1;
        break;
        }
      j++;
      }
    if(!supported)
      return 0;
    }
  return 1;
  }


/*
 *  mp3
 */

#if 0
typedef struct
  {
  icy_t m;
  gavf_io_t * io;
  } mp3_t;
#endif

static int supported_mp3(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, NULL);
  }

static int probe_mp3(int protocol,
                     const gavf_program_header_t * ph,
                     const gavl_dictionary_t * req)
  {
  return probe_audio_generic(protocol, ph, GAVL_CODEC_ID_MP3);
  }

static void * create_mp3(server_t * s,
                         const gavf_program_header_t * ph,
                         const gavl_dictionary_t * req,
                         const gavl_dictionary_t * inline_metadata,
                         gavl_dictionary_t * res)
  {
  void * ret = create_generic(s, ph, req, inline_metadata, res, NULL, 1);
  //  gavl_dictionary_set_string(res, "Content-Type", "audio/mpeg");
  return ret;
  }

/* ADTS */

static int supported_adts(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_ffmpeg_audio");
  }

static int probe_adts(int protocol,
                     const gavf_program_header_t * ph,
                     const gavl_dictionary_t * req)
  {
  return probe_audio_generic(protocol, ph, GAVL_CODEC_ID_AAC);
  }

static void * create_adts(server_t * s,
                         const gavf_program_header_t * ph,
                         const gavl_dictionary_t * req,
                         const gavl_dictionary_t * inline_metadata,
                         gavl_dictionary_t * res)
  {
  gavl_value_t v;
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_ffmpeg_audio", 1);
  gavl_value_init(&v);
  gavl_value_set_string(&v, "adts");
  m->plugin->common.set_parameter(m->h->priv, "format", &v);
  gavl_value_free(&v);
  return m;
  }

/* Vorbis */

static int supported_vorbis(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_vorbis");
  }

static int probe_vorbis(int protocol,
                        const gavf_program_header_t * ph,
                        const gavl_dictionary_t * req)
  {
  return probe_audio_generic(protocol, ph, GAVL_CODEC_ID_VORBIS);
  }

static void * create_vorbis(server_t * s,
                            const gavf_program_header_t * ph,
                            const gavl_dictionary_t * req,
                            const gavl_dictionary_t * inline_metadata,
                            gavl_dictionary_t * res)
  {
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_vorbis", 0);
  gavl_dictionary_set_string(res, "Content-Type", "audio/ogg");
  return m;
  }

/* Opus */

static int supported_opus(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_opus");
  }

static int probe_opus(int protocol,
                      const gavf_program_header_t * ph,
                      const gavl_dictionary_t * req)
  {
  return probe_audio_generic(protocol, ph, GAVL_CODEC_ID_OPUS);
  }

static void * create_opus(server_t * s,
                          const gavf_program_header_t * ph,
                          const gavl_dictionary_t * req,
                          const gavl_dictionary_t * inline_metadata,
                          gavl_dictionary_t * res)
  {
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_opus", 0);
  gavl_dictionary_set_string(res, "Content-Type", "audio/opus");
  return m;
  }

/* Flac */

static int supported_flac(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_flacogg");
  }

static int probe_flac(int protocol,
                      const gavf_program_header_t * ph,
                      const gavl_dictionary_t * req)
  {
  return probe_audio_generic(protocol, ph, GAVL_CODEC_ID_FLAC);
  }

static void * create_flac(server_t * s,
                          const gavf_program_header_t * ph,
                          const gavl_dictionary_t * req,
                          const gavl_dictionary_t * inline_metadata,
                          gavl_dictionary_t * res)
  {
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_flacogg", 0);
  gavl_dictionary_set_string(res, "Content-Type", "audio/ogg");
  return m;
  }

/* MPEG-2 transport stream */

static int supported_ts(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_ffmpeg");
  }

static const gavl_codec_id_t ts_codecs[] =
  {
    GAVL_CODEC_ID_MPEG1,
    GAVL_CODEC_ID_MPEG2,
    GAVL_CODEC_ID_AC3,
    GAVL_CODEC_ID_MP2,
    GAVL_CODEC_ID_NONE,
  };

static int probe_ts(int protocol,
                    const gavf_program_header_t * ph,
                    const gavl_dictionary_t * req)
  {
  return probe_generic(protocol, ph, ts_codecs);
  }

static void * create_ts(server_t * s,
                        const gavf_program_header_t * ph,
                        const gavl_dictionary_t * req,
                        const gavl_dictionary_t * inline_metadata,
                        gavl_dictionary_t * res)
  {
  gavl_value_t v;
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_ffmpeg", 0);
  

  gavl_value_init(&v);
  gavl_value_set_string(&v, "mpegts");
  m->plugin->common.set_parameter(m->h->priv, "format", &v);

  gavl_dictionary_set_string(res, "Content-Type", "video/MP2T");
  gavl_value_free(&v);
  return m;
  }

/* Theora */

static int supported_theora(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_theora");
  }

static const gavl_codec_id_t theora_codecs[] =
  {
    GAVL_CODEC_ID_VORBIS,
    GAVL_CODEC_ID_THEORA,
    GAVL_CODEC_ID_NONE,
  };

static int probe_theora(int protocol,
                         const gavf_program_header_t * ph,
                         const gavl_dictionary_t * req)
  {
  return probe_generic(protocol, ph, theora_codecs);
  }

static void * create_theora(server_t * s,
                            const gavf_program_header_t * ph,
                            const gavl_dictionary_t * req,
                            const gavl_dictionary_t * inline_metadata,
                            gavl_dictionary_t * res)
  {
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_theora", 0);
  gavl_dictionary_set_string(res, "Content-Type", "video/ogg");
  return m;
  }

/* Webm */

static int supported_webm(bg_plugin_registry_t * plugin_reg)
  {
  return supported_generic(plugin_reg, "e_ffmpeg");
  }

static const gavl_codec_id_t webm_codecs[] =
  {
    GAVL_CODEC_ID_VORBIS,
    GAVL_CODEC_ID_VP8,
    GAVL_CODEC_ID_NONE,
  };

static int probe_webm(int protocol,
                      const gavf_program_header_t * ph,
                      const gavl_dictionary_t * req)
  {
  return probe_generic(protocol, ph, webm_codecs);
  }

static void * create_webm(server_t * s,
                          const gavf_program_header_t * ph,
                          const gavl_dictionary_t * req,
                          const gavl_dictionary_t * inline_metadata,
                          gavl_dictionary_t * res)
  {
  gavl_value_t v;
  filter_generic_t * m = create_generic(s, ph, req, inline_metadata, res, "e_ffmpeg", 0);


  gavl_value_init(&v);
  gavl_value_set_string(&v, "webm");
  m->plugin->common.set_parameter(m->h->priv, "format", &v);
  
  gavl_dictionary_set_string(res, "Content-Type", "video/webm");
  return m;
  }


/* */

static filter_t filter_bgplug =
  {
    .is_supported = 1,
    .probe = probe_bgplug,
    .create = create_bgplug,
    .start = start_bgplug,
    .put_buffer = put_buffer_bgplug,
    .destroy = destroy_bgplug,
  };

static filter_t filters[] =
  {
    {
    .supported = supported_mp3,
    .probe = probe_mp3,
    .create = create_mp3,
    .start = start_generic,
    .put_buffer = put_buffer_generic_raw,
    .destroy = destroy_generic,
    },
    {
    .supported = rtp_supported,
    .probe = rtp_probe,
    .create = rtp_create,
    .start = rtp_start,
    .put_buffer = rtp_put_buffer,
    .destroy = rtp_destroy,
    },
    {
    .supported = supported_adts,
    .probe = probe_adts,
    .create = create_adts,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    {
    .supported = supported_vorbis,
    .probe = probe_vorbis,
    .create = create_vorbis,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    {
    .supported = supported_opus,
    .probe = probe_opus,
    .create = create_opus,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    {
    .supported = supported_flac,
    .probe = probe_flac,
    .create = create_flac,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    /* Audio / Video */
    {
    .supported = supported_ts,
    .probe = probe_ts,
    .create = create_ts,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    {
    .supported = supported_theora,
    .probe = probe_theora,
    .create = create_theora,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    {
    .supported = supported_webm,
    .probe = probe_webm,
    .create = create_webm,
    .start = start_generic,
    .put_buffer = put_buffer_generic_plugin,
    .destroy = destroy_generic,
    },
    { /* End */ },
  }; 

void filter_init(bg_plugin_registry_t * plugin_reg)
  {
  int i = 0;
  while(filters[i].supported)
    {
    filters[i].is_supported = filters[i].supported(plugin_reg);
    i++;
    }
  }

const filter_t * filter_find(int protocol,
                             const gavf_program_header_t * ph,
                             const gavl_dictionary_t * req)
  {
  int i = 0;
  const char * var;
  
  /* bgplug applications will always get a gavf stream */
  if((var = gavl_dictionary_get_string(req, "User-Agent")) &&
     !strcmp(var, bg_plug_app_id))
    return &filter_bgplug;
  
  while(filters[i].supported)
    {
    if(filters[i].is_supported &&
       filters[i].probe(protocol, ph, req))
      return &filters[i];
    i++;
    }
  
  /* Fallback */
  return &filter_bgplug;
  }
