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

#include <string.h>
#include <stdio.h>

#if 0
#define CHARSET "UTF-16"
#define BYTES_PER_CHAR 2
#else
#define CHARSET "ISO_8859-1"
#define BYTES_PER_CHAR 1
#endif

static void write_data(id3v2_t * ret, const void * data, int len)
  {
  if(ret->len + len > ret->alloc)
    {
    ret->alloc = ret->len + len + 1024;
    ret->data = realloc(ret->data, ret->alloc);
    }
  memcpy(ret->data + ret->len, data, len);
  ret->len += len;
  }

static void write_32_syncsave(uint8_t * dst, uint32_t num)
  {
  dst[0] = (num >> 21) & 0x7f;
  dst[1] = (num >> 14) & 0x7f;
  dst[2] = (num >> 7) & 0x7f;
  dst[3] = num & 0x7f;
  }

 static void write_32_be(id3v2_t * ret, uint32_t num)
  {
  uint8_t buf[4];
  GAVL_32BE_2_PTR(num, buf);
  write_data(ret, buf, 4);
  }

static void add_text_frame(id3v2_t * ret, char * fourcc, const char * string, bg_charset_converter_t * cnv)
  {
  char buf[4] = { 0, 0, 0, 0 };
  char * converted;
  int converted_len;
  int old_len = ret->len;
  int frame_len;
  if(!string)
    return;

  converted = bg_convert_string(cnv, string, -1, &converted_len);

  write_data(ret, fourcc, 4);
  write_data(ret, buf, 4); // Size (not syncsave)
  write_data(ret, buf, 2); // Flags
  write_data(ret, buf, 1); // Encoding
  write_data(ret, converted, converted_len+1); // text + '\0'
 
  frame_len = ret->len - old_len - 10;
  GAVL_32BE_2_PTR(frame_len, ret->data + old_len + 4);
  free(converted);
  }

static void add_int_frame(id3v2_t * ret, char * fourcc, int64_t val)
  {
  char buf[64];
  int old_len = ret->len;
  int frame_len;
  if(!val || (val == 9999))
    return;

  memset(buf, 0, 4);
  
  write_data(ret, fourcc, 4);
  write_data(ret, buf, 4); // Size (not syncsave)
  write_data(ret, buf, 2); // Flags
  write_data(ret, buf, 1); // Encoding

  sprintf(buf, "%"PRId64, val);
  write_data(ret, buf, strlen(buf)+1); // text + '\0'
  frame_len = ret->len - old_len - 10;
  GAVL_32BE_2_PTR(frame_len, ret->data + old_len + 4);
  }

static void add_cover(id3v2_t * ret, const char * image_file)
  {
  char buf[4] = { 0, 0, 0, 0 };
  int frame_len;
  gavl_buffer_t b;
  int old_len = ret->len;

  gavl_buffer_init(&b);
  
  if(!bg_read_file(image_file, &b))
    return;

  write_data(ret, "APIC", 4);
  write_data(ret, buf, 4); // Size (not syncsave)
  write_data(ret, buf, 2); // Flags
  buf[0] = 0x00;
  write_data(ret, buf, 1); // Encoding
  
  write_data(ret, "image/jpeg", 11);
  buf[0] = 0x03; /* front cover */
  write_data(ret, buf, 1);
  buf[0] = 0;
  write_data(ret, buf, 1); // Description
  write_data(ret, b.buf, b.len); // Image

  frame_len = ret->len - old_len - 10;
  GAVL_32BE_2_PTR(frame_len, ret->data + old_len + 4);
  gavl_buffer_free(&b);
  }

#define SIZE_OFFSET 6

static int id3v2_generate_by_file_mp3(bg_db_t * db, bg_db_audio_file_t * song, id3v2_t * ret,
                                      bg_db_audio_album_t * album, bg_db_file_t * cover)
  {
  // Sig (3) + version (2) + flags (1) + size(4)
  uint8_t header[10] = { 'I', 'D', '3', 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  
  bg_charset_converter_t * cnv;
  FILE * file = NULL;
  uint8_t buf[10];
  
  file = fopen(song->file.path, "rb");

  if(fread(buf, 1, 10, file) < 10)
    {
    fclose(file);
    return 0;
    }
  
  if((buf[0] == 'I') &&
     (buf[1] == 'D') &&
     (buf[2] == '3'))
    {
    ret->skip_size = (uint32_t)buf[6] << 24;
    ret->skip_size >>= 1;
    ret->skip_size |= (uint32_t)buf[7] << 16;
    ret->skip_size >>= 1;
    ret->skip_size |= (uint32_t)buf[8] << 8;
    ret->skip_size >>= 1;
    ret->skip_size |= (uint32_t)buf[9];
    ret->skip_size += 10;
    }
  
  /* Create tag */
  ret->len = 0;
  write_data(ret, header, 10);

  /* Add text Frames */
  cnv = bg_charset_converter_create("UTF-8", CHARSET);

  add_text_frame(ret, "TPE1", song->artist, cnv);
  add_text_frame(ret, "TIT2", song->title, cnv);
  add_text_frame(ret, "TCON", song->genre, cnv);
  add_int_frame(ret, "TYER", song->date.year);
  add_int_frame(ret, "TRCK", song->track);
  add_int_frame(ret, "TLEN", song->file.obj.duration / 1000);

  if(album)
    {
    add_text_frame(ret, "TALB", album->title, cnv);
    add_text_frame(ret, "TPE2", album->artist, cnv);
    }

  if(cover)
    {
    /* add cover */
    add_cover(ret, cover->path);
    }

  bg_charset_converter_destroy(cnv);

  /* Write size */
  write_32_syncsave(ret->data + SIZE_OFFSET, ret->len - 10);
  ret->id = bg_db_object_get_id(song);
  
  /* Unref objects */
  if(file)
    fclose(file);
  
  return 1;
  }

static int id3v2_generate_by_file_flac(bg_db_t * db, bg_db_audio_file_t * song, id3v2_t * ret,
                                       bg_db_audio_album_t * album, bg_db_file_t * cover)
  {
  FILE * file = NULL;
  int len;
  int last_tag = 0;
  uint8_t * tag_buffer;
  uint8_t buf[4];
  int start;
  bg_db_image_file_t * cover_img;

  gavl_buffer_t b;
  gavl_buffer_init(&b);
  
  /* Obtain size */
  file = fopen(song->file.path, "rb");
  
  // "flac"
  // 8 bit type (0x80 = last)
  // 24 bit length

  fseek(file, 4, SEEK_SET); // "flac"
  
  while(1)
    {
    last_tag = ftell(file);
    
    if(fread(buf, 1, 4, file) < 4)
      {
      fclose(file);
      break;
      }
    
    len = GAVL_PTR_2_24BE(&buf[1]);
    
    fseek(file, len, SEEK_CUR);
    
    if(buf[0] & 0x80)
      {
      break;
      }
    }
  ret->skip_size = ftell(file);

  tag_buffer = malloc(ret->skip_size);
  fseek(file, 0, SEEK_SET);
  fread(tag_buffer, 1, ret->skip_size, file);

  if(!cover || !(bg_read_file(cover->path, &b)))
    goto end;

  cover_img = (bg_db_image_file_t*)cover;
  
  tag_buffer[last_tag] &= ~0x80;

  ret->len = 0;
  write_data(ret, tag_buffer, ret->skip_size);
  
  /* Create tag */
  buf[0] = 0x86;
  write_data(ret, buf, 4);

  start = ret->len;

  write_32_be(ret, 0x03); // Cover (front)
  
  len = strlen(cover->mimetype);
  write_32_be(ret, len);
  write_data(ret, (uint8_t*)cover->mimetype, len);

  len = strlen("Cover");
  write_32_be(ret, len);
  write_data(ret, (uint8_t*)"Cover", len);

  /* Width, height, depth */
  write_32_be(ret, cover_img->width);
  write_32_be(ret, cover_img->height);
  write_32_be(ret, 24);
  write_32_be(ret, 0); // For indexed-color pictures (e.g. GIF), the number
                       // of colors used, or 0 for non-indexed pictures. 

  write_32_be(ret, b.len);
  write_data(ret, b.buf, b.len);
  /* Write size */

  len = ret->len - start;

  GAVL_24BE_2_PTR(len, ret->data + ret->skip_size + 1);

  ret->id = bg_db_object_get_id(song);
  
  /* Unref objects */

  end:

  if(file)
    fclose(file);

  gavl_buffer_free(&b);
  
  return 1;
  }


static int id3v2_generate_by_file(bg_db_t * db, bg_db_audio_file_t * song, id3v2_t * ret,
                                  const char * out_mimetype)
  {
  int result = 0;
  bg_db_audio_album_t * album = NULL;
  bg_db_file_t * cover = NULL;
  
  if(bg_db_object_get_type(song) != BG_DB_OBJECT_AUDIO_FILE)
    return 0;
  
  if(song->album_id > 0)
    {
    album = bg_db_object_query(db, song->album_id);
    if(album->cover_id > 0)
      cover = bg_db_get_thumbnail(db, album->cover_id,
                                  600, 600, 60000, "image/jpeg");
    }

  if(!out_mimetype)
    out_mimetype = song->file.mimetype;
  
  if(!strcmp(out_mimetype, "audio/mpeg"))
    result = id3v2_generate_by_file_mp3(db, song, ret, album, cover);
  else if(!strcmp(out_mimetype, "audio/flac"))
    result = id3v2_generate_by_file_flac(db, song, ret, album, cover);
  
  if(album)
    bg_db_object_unref(album);
  if(cover)
    bg_db_object_unref(cover);
  
  return result;
  }

static int id3v2_generate_by_id(bg_db_t * db, int64_t id, id3v2_t * ret, const char * out_mimetype)
  {
  int result;
  // Sig (3) + version (2) + flags (1) + size(4)
  
  bg_db_audio_file_t * song = NULL;
  
  /* Query objects */
  song = bg_db_object_query(db, id);
  if(!song)
    return 0;

  result = id3v2_generate_by_file(db, song, ret, out_mimetype);
  bg_db_object_unref(song);
  return result;
  }

static id3v2_t * lookup_cache(server_t * s, int64_t id, const char * transcoder)
  {
  int i;
  /* Check for cached value */
  for(i = 0; i < s->id3_cache_size; i++)
    {
    if(s->id3_cache[i].id == id)
      {
      if((!s->id3_cache[i].transcoder && !transcoder) ||
         (s->id3_cache[i].transcoder && transcoder && !strcmp(s->id3_cache[i].transcoder, transcoder)))
        {
        return &s->id3_cache[i];
        }
      }
    }
  return NULL;
  }

static id3v2_t * get_cache_slot(server_t * s)
  {
  int i;
  gavl_time_t min_time = GAVL_TIME_UNDEFINED;
  int min_index;
  
  /* Check for empty slot */
  for(i = 0; i < s->id3_cache_size; i++)
    {
    if(s->id3_cache[i].last_used == GAVL_TIME_UNDEFINED)
      {
      min_index = i;
      break;
      }
    else if((min_time == GAVL_TIME_UNDEFINED) ||
            (s->id3_cache[i].last_used < min_time))
      {
      min_index = i;
      min_time = s->id3_cache[i].last_used;
      }
    }
  return &s->id3_cache[min_index];
  }

id3v2_t * server_get_id3(server_t * s, bg_http_connection_t * conn,
                         int64_t id, const char * transcoder, const char * out_mimetype)
  { 
  id3v2_t * ret;

  if(!(ret = lookup_cache(s, id, transcoder)))
    {
    ret = get_cache_slot(s);

    if(!id3v2_generate_by_id(s->db, id, ret, out_mimetype))
      return NULL;
    ret->transcoder = transcoder;
    ret->last_used = conn->current_time;
    }
  return ret;
  }

id3v2_t * server_get_id3_for_file(server_t * s,
                                  bg_http_connection_t * conn, bg_db_file_t * f)
  {
  id3v2_t * ret;
  
  /* Check if the file is an mp3 */
  if(strcmp(f->mimetype, "audio/mpeg") &&
     strcmp(f->mimetype, "audio/flac"))
    return NULL;

  if(!(ret = lookup_cache(s, bg_db_object_get_id(f), NULL)))
    {
    ret = get_cache_slot(s);
    if(!id3v2_generate_by_file(s->db, (bg_db_audio_file_t*)f, ret, NULL))
      return NULL;
    }
  ret->last_used = conn->current_time;
  return ret;
  }
