/*****************************************************************
 * 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 <unistd.h>

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

#include <server.h>
#include <gmerlin/http.h>
#include <gmerlin/translation.h>
#include <gmerlin/log.h>

#include <gavl/metatags.h>

// #define CLIENT_TIMEOUT 1000

// #define BUF_ELEMENTS 100
#define BUF_ELEMENTS 500

#define HAVE_HEADER    (1<<0)

#define LOG_DOMAIN "server.source"

typedef struct
  {
  gavl_dictionary_t m;
  int have_m; // 1 if we received a metadata update yet
  pthread_mutex_t metadata_mutex;
  
  bg_plug_t * in_plug;
  bg_mediaconnector_t conn;
  bg_plug_t * conn_plug;
  
  buffer_t * buf;
  
  int flags;
  gavf_program_header_t h;
  gavl_timer_t * timer;

  gavf_program_header_t ph;

  bg_subprocess_t * proc;
  int is_server; // Need to accept() first

  } source_t;

static void enable_streams(bg_plug_t * plug, gavf_stream_type_t type)
  {
  gavf_t * g;
  int num, i;
  const gavf_stream_header_t * sh;

  g = bg_plug_get_gavf(plug);

  num = gavf_get_num_streams(g, type);

  if(!num)
    return;

  for(i = 0; i < num; i++)
    {
    sh = gavf_get_stream(g, i, type);
    bg_plug_set_stream_action(plug, sh, BG_STREAM_ACTION_READRAW);
    }
  
  }

static int write_func(void * priv, const uint8_t * data, int len)
  {
  return len;
  }

static int conn_cb_func(void * priv, int type, const void * data)
  {
  buffer_element_t * el;
  source_t * p = priv;
  
  switch(type)
    {
    case GAVF_IO_CB_PROGRAM_HEADER_START:
      //      fprintf(stderr, "Got program header:\n");
      //      gavf_program_header_dump(data);

      if(!(p->flags & HAVE_HEADER))
        {
        gavf_program_header_copy(&p->ph, data);
        p->flags |= HAVE_HEADER;
        }
      break;
    case GAVF_IO_CB_PROGRAM_HEADER_END:
      break;
    case GAVF_IO_CB_PACKET_START:
      //      fprintf(stderr, "Got packet: %d\n", ((gavl_packet_t*)data)->id);
      //      gavl_packet_dump(data);

      el = buffer_get_write(p->buf);
      if(!el)
        return 0;
      
      buffer_element_set_packet(el, data);
      buffer_done_write(p->buf);
      
      break;
    case GAVF_IO_CB_PACKET_END:
      break;
    case GAVF_IO_CB_METADATA_START:
      //      fprintf(stderr, "Got metadata:\n");
      //      gavl_dictionary_dump(data, 2);

      pthread_mutex_lock(&p->metadata_mutex);
      gavl_dictionary_free(&p->m);
      gavl_dictionary_init(&p->m);
      gavl_dictionary_copy(&p->m, data);
      p->have_m = 1;
      pthread_mutex_unlock(&p->metadata_mutex);

      el = buffer_get_write(p->buf);
      if(!el)
        return 0;

      buffer_element_set_metadata(el, data);
      buffer_done_write(p->buf);

      break;
    case GAVF_IO_CB_METADATA_END:
      break;
    case GAVF_IO_CB_SYNC_HEADER_START:
      //      fprintf(stderr, "Got sync_header\n");

      if(!gavl_timer_is_running(p->timer))
        gavl_timer_start(p->timer);
      
      el = buffer_get_write(p->buf);
      if(!el)
        return 0;

      buffer_element_set_sync_header(el, data, p->ph.num_streams);
      buffer_done_write(p->buf);
      
      break;
    case GAVF_IO_CB_SYNC_HEADER_END:
      break;
    }
  
  return 1;
  }

static int source_client_start(client_t * cl)
  {
  gavf_io_t * io = NULL; 
  source_t * priv = cl->data;

  enable_streams(priv->in_plug, GAVF_STREAM_AUDIO);
  enable_streams(priv->in_plug, GAVF_STREAM_VIDEO);
  enable_streams(priv->in_plug, GAVF_STREAM_TEXT);
  enable_streams(priv->in_plug, GAVF_STREAM_OVERLAY);

  if(!bg_plug_start(priv->in_plug))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Starting plug failed");
    goto fail;
    }
  priv->buf = buffer_create(BUF_ELEMENTS);
  priv->timer = gavl_timer_create();

  /* Set up media connector */
  bg_plug_setup_reader(priv->in_plug, &priv->conn);
  bg_mediaconnector_create_conn(&priv->conn);

  /* Setup connection plug */
  io = gavf_io_create(NULL,
                      write_func,
                      NULL,
                      NULL,
                      NULL,
                      priv);

  gavf_io_set_cb(io, conn_cb_func, priv);

  priv->conn_plug = bg_plug_create_writer(cl->s->plugin_reg);
  
  bg_plug_open(priv->conn_plug, io,
               bg_plug_get_metadata(priv->in_plug), 0);

  bg_plug_transfer_metadata(priv->in_plug, priv->conn_plug);
  
  if(!bg_plug_setup_writer(priv->conn_plug, &priv->conn))
    goto fail;

  bg_mediaconnector_start(&priv->conn);
  
  /* Copy at least the station name */
  gavl_dictionary_set_string(&priv->m, GAVL_META_STATION,
                    gavl_dictionary_get_string(bg_plug_get_metadata(priv->conn_plug), GAVL_META_STATION));
  
  client_set_status(cl, CLIENT_STATUS_RUNNING);
  return 1;
  fail:
  return 0;
  
  }


static void source_destroy(void * data)
  {
  source_t * p = data;

  if(p->in_plug)
    bg_plug_destroy(p->in_plug);

  if(p->conn_plug)
    bg_plug_destroy(p->conn_plug);

  bg_mediaconnector_free(&p->conn);
    
  if(p->buf)
    buffer_stop(p->buf);

  if(p->timer)
    gavl_timer_destroy(p->timer);

  if(p->buf)
    buffer_destroy(p->buf);
  
  gavf_program_header_free(&p->ph);

  gavl_dictionary_free(&p->m);

  if(p->proc)
    {
    bg_subprocess_kill(p->proc, SIGTERM);
    bg_subprocess_close(p->proc);
    }
  pthread_mutex_destroy(&p->metadata_mutex);
  free(p);
  }


static void source_func(client_t * cl)
  {
  int num, max;
  gavl_time_t conn_time;
  gavl_time_t program_time;
  gavl_time_t buffer_time = 0;
  
  source_t * p = cl->data;

  int buffering;

  if(p->is_server)
    {
    int real_fd;
    gavf_io_t * io;
    int flags = 0;
    
    /* Wait until the process connects */
    real_fd = bg_listen_socket_accept(cl->fd, 30000, NULL);

    if(real_fd < 0)
      {
      gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Accepting failed: %s", strerror(errno));
      return;
      }
    close(cl->fd);
    cl->fd = real_fd;
    
    if(!bg_plug_io_server_handshake(cl->fd, BG_PLUG_IO_METHOD_READ, CLIENT_TIMEOUT))
      return;
    
    io = bg_plug_io_open_socket(cl->fd, BG_PLUG_IO_METHOD_READ,
                                &flags, CLIENT_TIMEOUT);
    if(!io)
      return;
    
    if(!bg_plug_open(p->in_plug, io, NULL, flags))
      {
      gavf_io_destroy(io);
      return;
      }
    io = NULL;
    source_client_start(cl);
    }
  buffering = 1;
  while(1)
    {
    /* Read packet from source */
    if(!bg_mediaconnector_iteration(&p->conn))
      break;

    conn_time = bg_mediaconnector_get_min_time(&p->conn);
    program_time = gavl_timer_get(p->timer);
    
    if(buffering)
      {
      buffer_get_fullness(p->buf, &num, &max);
      if(num == max)
        {
        buffer_time = conn_time - program_time;
        buffering = 0;
        }
      }
    
    if(!buffering)
      {
      if((conn_time != GAVL_TIME_UNDEFINED) &&
        (conn_time - program_time - buffer_time > GAVL_TIME_SCALE/2))
        {
        gavl_time_t delay_time;
        delay_time = conn_time - program_time -
          buffer_time - GAVL_TIME_SCALE/2;
        gavl_time_delay(&delay_time);
        //        fprintf(stderr, "Delay time: %f\n", gavl_time_to_seconds(delay_time));
        }
      }
    }
  }


client_t * source_client_create(server_t * s, int * fd,
                                const gavl_dictionary_t * req,
                                bg_plug_t * plug, int type,
                                bg_subprocess_t * proc, int is_server)
  {
  source_t * priv;
  client_t * ret;
  
  priv = calloc(1, sizeof(*priv));

  pthread_mutex_init(&priv->metadata_mutex, NULL);

  priv->in_plug = plug;
  priv->proc = proc;

  priv->is_server = is_server;
  
  ret = client_create(s, type, *fd, req, priv, source_destroy, source_func);
  
  *fd = -1;

  if(!priv->is_server && !source_client_start(ret))
    goto fail;

  return ret;
  
  fail:

  source_destroy(priv);  
  return NULL;
  }

int server_handle_source(bg_http_connection_t * conn, void * data)
  {
  const char * var;
  int i;
  int flags;
  client_t * cl;
  gavf_io_t * io = NULL; 
  bg_plug_t * plug = NULL;
  server_t * s = data;
  
  if(strcmp(conn->method, "PUT") ||
     !(var = gavl_dictionary_get_string(&conn->req, "Content-Type")) ||
     strcmp(var, bg_plug_mimetype))
    return 0;

  /* Check if a stream of that name already exists */
  
  if(!strcmp(conn->path, "/") || (conn->path[0] != '/'))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Invalid path \"%s\"", conn->path);
    bg_http_connection_init_res(conn, "HTTP/1.1", 404, "Not Found");
    goto fail;
    }

  if(bg_http_server_has_path(s->s, conn->path))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Invalid path \"%s\"", conn->path);
    bg_http_connection_init_res(conn, "HTTP/1.1", 404, "Not Found");
    goto fail;
    }
  
  for(i = 0; i < s->num_clients; i++)
    {
    if((s->clients[i]->type == CLIENT_TYPE_SOURCE_STREAM) &&
       !strcmp(s->clients[i]->name, conn->path + 1))
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 423, "Locked");
      goto fail;
      }
    }
  
  /* Send ok if everything is fine */

  if((var = gavl_dictionary_get_string(&conn->req, "Expect")) &&
     !strcmp(var, "100-continue"))
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 100, "Continue");
    if(!bg_http_server_write_res(s->s, conn))
      goto fail;
    }
  
  io = bg_plug_io_open_socket(conn->fd, BG_PLUG_IO_METHOD_READ, &flags, CLIENT_TIMEOUT);
  if(!io)
    goto fail;
  
  plug = bg_plug_create_reader(s->plugin_reg);

  if(!plug)
    goto fail;
  
  if(!bg_plug_open(plug, io, NULL, flags))
    goto fail;
  io = NULL;
  
  cl = source_client_create(s, &conn->fd, &conn->req, plug, CLIENT_TYPE_SOURCE_STREAM, NULL, 0);
  cl->name = gavl_strdup(conn->path);

  gavl_dictionary_free(&conn->res);

  gavl_log(GAVL_LOG_DEBUG, LOG_DOMAIN, "Created source %s", conn->path);
  
  server_attach_client(s, cl);
    
  return 1;
  
  fail:

  if(plug)
    bg_plug_destroy(plug);

  if(io)
    gavf_io_destroy(io);
  
  return 1;
  }

client_t * sink_create_from_source(server_t * s,
                                   int * fd, client_t * source,
                                   const gavl_dictionary_t * req,
                                   const char * method, int proto)
  {
  source_t * sp;
  client_t * sink;
  
  sp = source->data;
  pthread_mutex_lock(&sp->metadata_mutex);
  
  sink = sink_client_create(s, fd, req, 
                            &sp->ph,
                            &sp->m,
                            sp->buf, method, proto, source);
  pthread_mutex_unlock(&sp->metadata_mutex);
  return sink;
  }

int sink_init_from_source(client_t * sink, client_t * source)
  {
  int ret;
  source_t * sp;
  
  sp = source->data;
  pthread_mutex_lock(&sp->metadata_mutex);

  //  TODO
  ret = sink_client_init(sink, &sp->ph, &sp->m, sp->buf);
  
  pthread_mutex_unlock(&sp->metadata_mutex);
  return ret;
  }

int server_handle_stream(bg_http_connection_t * conn, void * data)
  {
  int i;
  client_t * source = NULL;
  client_t * sink;
  server_t * s = data;
  
  if(conn->protocol_i == BG_HTTP_PROTO_HTTP)
    {
    if(strcmp(conn->method, "GET") && strcmp(conn->method, "HEAD"))
      return 0;
    }
  
  for(i = 0; i < s->num_clients; i++)
    {
    if((s->clients[i]->type == CLIENT_TYPE_SOURCE_STREAM) &&
       s->clients[i]->name &&
       !strcmp(s->clients[i]->name, conn->path))
      {
      source = s->clients[i];
      break;
      }
    }
  if(!source)
    return 0; // 404

  sink = sink_create_from_source(s, &conn->fd, source, &conn->req, conn->method, conn->protocol_i);
  
  if(sink)
    server_attach_client(s, sink);
  return 1;
  }
