/*****************************************************************
 * 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 <string.h>
#include <gmerlin/http.h>
#include <gmerlin/log.h>
#define LOG_DOMAIN "mediahandler"

#include <unistd.h> // access
#include <errno.h> 

#define THREAD_THRESHOLD (1024*1024) // 1 M

typedef struct
  {
  char * path;
  int64_t offset;
  int64_t length;
  } send_file_t;

static void send_file_func(client_t * c)
  {
  send_file_t * s = c->data;
  if(!bg_socket_send_file(c->fd, s->path, s->offset, s->length))
    c->keep_alive = 0;
  }

static void cleanup_func(void * data)
  {
  send_file_t * s = data;
  free(s->path);
  free(s);
  }

int server_handle_media(bg_http_connection_t * conn, void * data)
  {
  const char * var;
  bg_db_object_t * o = NULL;
  bg_db_file_t * f;
  int64_t id = 0;
  char * path = NULL;
  int type;
  int launch_thread = 0;
  char * pos;
  const char * range;
  int64_t start, end;
  int64_t off;
  int64_t file_start, file_end;
  id3v2_t * id3 = NULL;
  //  int id3_offset = 0;
  int64_t file_size;
  int keep_alive = 0;
  const bg_upnp_client_t * cl;
  server_t * s = data;
  
  if(strcmp(conn->method, "GET") && strcmp(conn->method, "HEAD"))
    {
    /* Method not allowed */
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                405, "Method Not Allowed");
    goto go_on;
    }
  
  path = gavl_strdup(conn->path);

  /* Some clients append bullshit to the path */
  if((pos = strchr(path, '#')))
    *pos = '\0';

  id = strtoll(path, &pos, 10);
  
  if((*pos != '\0') && (*pos != '/'))
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                400, "Bad Request");
    goto go_on;
    }

  cl = bg_upnp_detect_client(&conn->req);
  
  /* Get the object from the db */
  if(!s->db)
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                404, "Not Found");
    goto go_on;
    }
  
  o = bg_db_object_query(s->db, id);

  type = bg_db_object_get_type(o);
  
  if(!(bg_db_object_get_type(o) & BG_DB_FLAG_FILE))
    {
    bg_http_connection_init_res(conn, "HTTP/1.1", 
                                404, "Not Found");
    goto go_on;
    }

  f = (bg_db_file_t*)o;
  
  if(access(f->path, R_OK))
    {
    gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN,
           "Could not access file %s (database out of date?)", f->path);

    if(errno == EACCES)
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 
                                  401, "Forbidden");
      goto go_on;
      }
    else
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 
                                  404, "Not Found");
      goto go_on;
      }
    }

  /* Decide whether to send an ID3 tag */
  
  id3 = server_get_id3_for_file(s, conn, f);
  off = 0;
  
  if(id3)
    {
    //    fprintf(stderr, "Size: %"PRId64", id3_offset: %d, id3_len: %d\n",
    //            o->size, id3_offset, id3->len);
    off = id3->len - id3->skip_size;
    }
  file_size = o->size + off;
  
  /* Figure out byte range */
  
  range = gavl_dictionary_get_string_i(&conn->req, "Range");
  if(range)
    {
    if(gavl_string_starts_with(range, "bytes="))
      {
      range += 6;

      if(sscanf(range, "%"PRId64"-%"PRId64, &start, &end) < 2)
        {
        if(sscanf(range, "%"PRId64"-", &start) == 1)
          end = file_size - 1;
        else
          {
          bg_http_connection_init_res(conn, "HTTP/1.1", 
                                      400, "Bad Request");
          goto go_on;
          }
        }
      else
        {
        if(end >= file_size)
          {
          bg_http_connection_init_res(conn, "HTTP/1.1", 
                                      416, "Requested range not satisfiable");
          goto go_on;
          }
        }
      }
    else
      {
      bg_http_connection_init_res(conn, "HTTP/1.1", 
                                  400, "Bad Request");
      goto go_on;
      }
    // fprintf(stderr, "Got range: %"PRId64"-%"PRId64"\n", start, end);
    }
  else
    {
    start = 0;
    end = file_size - 1;
    }
  
  /* Decide whether to launch a thread */

  if((type == BG_DB_OBJECT_AUDIO_FILE) ||
     (type == BG_DB_OBJECT_VIDEO_FILE) ||
     (o->size > THREAD_THRESHOLD))
    {
    launch_thread = 1;
    }
  
  /* Set up header for transmission */

  if(range)
    bg_http_connection_init_res(conn, "HTTP/1.1", 206, "Partial Content");
  else
    bg_http_connection_init_res(conn, "HTTP/1.1", 200, "OK");
  
  if(s->server_string)
    gavl_dictionary_set_string(&conn->res, "Server", s->server_string);
  bg_http_header_set_date(&conn->res, "Date");
  gavl_dictionary_set_string(&conn->res, "Content-Type", bg_upnp_client_translate_mimetype(cl, f->mimetype));
  gavl_dictionary_set_string(&conn->res, "Accept-Ranges", "bytes");

  if(o->duration > 0)
    gavl_dictionary_set_string_nocopy(&conn->res, "X-Content-Duration", bg_sprintf("%f", gavl_time_to_seconds(o->duration)));
    
  
  if(range)
    {
    char * tmp_string;
    gavl_dictionary_set_long(&conn->res, "Content-Length", end - start + 1);
    tmp_string = bg_sprintf("bytes %"PRId64"-%"PRId64"/%"PRId64, start, end,
                            file_size);
    gavl_dictionary_set_string(&conn->res, "Content-Range", tmp_string);
    free(tmp_string);
    }
  else
    gavl_dictionary_set_long(&conn->res, "Content-Length", file_size);

  bg_http_connection_check_keepalive(conn);
  gavl_dictionary_set_string(&conn->res, "Cache-control", "no-cache");

  if((var = gavl_dictionary_get_string(&conn->req, "getcontentFeatures.dlna.org")) &&
     (atoi(var) == 1))
    {
    // todo: Generate correct flags
    gavl_dictionary_set_string(&conn->res, "contentFeatures.dlna.org",
                      "DLNA.ORG_OP=11;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000");
    
    if(launch_thread)
      gavl_dictionary_set_string(&conn->res, "transferMode.dlna.org", "Streaming");
    else
      gavl_dictionary_set_string(&conn->res, "transferMode.dlna.org", "Interactive");
    
    }
  else if(gavl_dictionary_get_string(&conn->req, "transferMode.dlna.org"))
    {
    if(launch_thread)
      gavl_dictionary_set_string(&conn->res, "transferMode.dlna.org", "Streaming");
    else
      gavl_dictionary_set_string(&conn->res, "transferMode.dlna.org", "Interactive");
    }

  
  //  fprintf(stderr, "Sending file:\n");
  //  gavl_dictionary_dump(&conn->res, 0);
  
  go_on:
  
  if(!bg_http_server_write_res(s->s, conn))
    goto cleanup;
  
  /* Send ID3 tag */

  end++; // http -> C

  file_start = start - off;
  file_end = end - off;
  
  if(id3)
    {
    if(file_start - id3->skip_size < 0) // | ID3   |<-delta->||                MP3    |
      {
      int write_bytes = id3->skip_size - file_start;

      if(start + write_bytes > end)
        write_bytes = end - start;
      
      // fprintf(stderr, "Sending data\n");
      // gavl_hexdump(id3->data + start, 176, 16);
      
      if(!bg_socket_write_data(conn->fd, id3->data + start, write_bytes))
        goto cleanup;
      
      file_start += write_bytes;
      }
    }
#if 0
  fprintf(stderr,
          "Sending file: th: %d http_pos: %"PRId64" %"PRId64" %"PRId64" "
          "file_pos: %"PRId64" %"PRId64" %"PRId64"\n", launch_thread,
          start, end, end - start, file_start, file_end, file_end - file_start);
#endif
  if(!launch_thread)
    {
    if(!bg_socket_send_file(conn->fd, f->path, file_start, file_end - file_start))
      bg_http_connection_clear_keepalive(conn);
    }
  else
    {
    client_t * c;
    send_file_t * sf = calloc(1, sizeof(*sf));
    sf->path = gavl_strdup(f->path);
    sf->offset = file_start;
    sf->length = file_end - file_start;
    c = client_create(s, CLIENT_TYPE_MEDIA, conn->fd, &conn->req, sf,
                      cleanup_func, send_file_func);
    c->keep_alive = keep_alive;
    server_attach_client(s, c);
    conn->fd = -1;
    }
  
  cleanup:

  if(path)
    free(path);
  if(o)
    bg_db_object_unref(o);
  
  return 1;
  }
