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

#define LOG_DOMAIN "server.sink"

// #define CLIENT_TIMEOUT 1000

typedef struct
  {
  buffer_t * buf;
  int64_t seq;
  const filter_t * f;
  void * filter_priv;
  } sink_t;

static void sink_destroy(void * data)
  {
  sink_t * s = data;
  if(s->filter_priv && s->f && s->f->destroy)
    s->f->destroy(s->filter_priv);  
  free(s);
  }

static buffer_element_t * next_element(client_t * cl)
  {
  buffer_element_t * ret = NULL;
  int result;
  sink_t * s = cl->data;
  
  while(1)
    {
    if(client_get_status(cl) == CLIENT_STATUS_STOP)
      return 0;
    
    result = buffer_get_read(s->buf, &s->seq, &ret);
    
    if(!result)
      return NULL;

    if(ret)
      return ret;
    buffer_wait(s->buf);
    }
  
  return NULL;
  }

static void sink_func(client_t * cl)
  {
  buffer_element_t * el;
  sink_t * priv = cl->data;

  /* Wait until source is ready */
  
  client_set_status(cl, CLIENT_STATUS_WAIT_START);

  fprintf(stderr, "Sink func: %p\n", cl->source);
  
  if(client_wait_running(cl->source) != CLIENT_STATUS_RUNNING)
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Source not ready");
    return;
    }
  /* Initialize */

  if(!sink_init_from_source(cl, cl->source))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Initializing sink failed");
    return;
    }
  client_set_status(cl, CLIENT_STATUS_RUNNING);
  
  while(1)
    {
    el = next_element(cl);

    if(!el)
      {
      gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Could not get data");
      return;
      }
    if(el->type == BUFFER_TYPE_SYNC_HEADER)
      {
      buffer_done_read(priv->buf);
      break;
      }
    buffer_done_read(priv->buf);
    }
  
  /* Got sync header */

  while(bg_socket_can_write(cl->fd, 1000))
    {
    if(!(el = next_element(cl)))
      {
      gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Could not get data");
      return;
      }
    if(!priv->f->put_buffer(priv->filter_priv, el))
      {
      gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Sending data failed");
      buffer_done_read(priv->buf);
      return;
      }
    buffer_done_read(priv->buf);
    }
  gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Remote end closed connection");
  }

static int http_handshake_1(server_t * s,
                            gavl_dictionary_t * res)
  {
  bg_http_response_init(res, "HTTP/1.1", 200, "OK");
  /* Set some specific fields */
  gavl_dictionary_set_string(res, "Connection", "close");
  gavl_dictionary_set_string(res, "Cache-control", "no-cache");
  gavl_dictionary_set_string(res, "Pragma", "no-cache");
  gavl_dictionary_set_string(res, "Accept-Ranges", "none");

  if(s->server_string)
    gavl_dictionary_set_string(res, "Server", s->server_string);
  
  return 1;
  }

int sink_client_init(client_t * cl,
                     const gavf_program_header_t * ph,
                     const gavl_dictionary_t * inline_metadata,
                     buffer_t * buf)
  {
  sink_t * priv;
  gavl_dictionary_t res;
  const char * protocol;
  const char * method;
  //  gavf_io_t * io = NULL;
  int write_response = 0;
  int ret = 0;
  
  int proto;
  
  priv = cl->data;

  gavl_dictionary_init(&res);

  priv->buf = buf;
  priv->seq = -1;

  protocol = bg_http_request_get_protocol(&cl->req);
  method = bg_http_request_get_method(&cl->req);

  proto = server_get_protocol(&cl->req);

  if(!(priv->f = filter_find(proto, ph, &cl->req)))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "No filter found");
    bg_http_response_init(&res, protocol, 406, "Not Acceptable");
    write_response = 1;
    goto end;
    }

  switch(proto)
    {
    case PROTOCOL_HTTP:
      if(!http_handshake_1(cl->s, &res))
        goto end;
      break;
    case PROTOCOL_RTSP:
      if(!rtsp_handshake_1(cl->s, cl->fd, &cl->req, ph))
        goto end;
      break;
    default:
      goto end;
    }
  
  if(!(priv->filter_priv =
       priv->f->create(cl->s, ph, &cl->req, inline_metadata,
                       &res)))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Setting up filter failed");
    gavl_dictionary_free(&res);
    bg_http_response_init(&res, protocol, 406, "Not Acceptable");
    write_response = 1;
    goto end;
    }

  switch(proto)
    {
    case PROTOCOL_HTTP:
      if(!bg_http_response_write(cl->fd, &res) ||
         !strcmp(method, "HEAD"))
        goto end;
      break;
    case PROTOCOL_RTSP:
      if(!rtsp_handshake_2(cl->s, priv->filter_priv, cl->fd, ph))
        goto end;
      break;
    default:
      goto end;
    }
  
  /* Start filter */
  
  if(!priv->f->start(priv->filter_priv, ph, &cl->req,
                     inline_metadata, cl->fd))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Starting filter failed");
    goto end;
    }

  ret = 1;
  
  end:
  if(write_response)
    bg_http_response_write(cl->fd, &res);

  gavl_dictionary_free(&res);
  return ret;
  }

client_t * sink_client_create(server_t * s,
                              int * fd, const gavl_dictionary_t * req,
                              const gavf_program_header_t * ph,
                              const gavl_dictionary_t * inline_metadata,
                              buffer_t * buf, const char * method,
                              int proto, client_t * source)
  {
  client_t * ret = NULL;
  sink_t * priv = calloc(1, sizeof(*ret));
  
  ret = client_create_full(s, CLIENT_TYPE_SINK, *fd, req, priv,
                           sink_destroy, sink_func, source);

  *fd = -1;

  return ret;
  }
