#include "euterpe_gen/ffmpeg_tagger.h"

#include "WDL/sha.h"
#include "WDL/wdlcstring.h"

RSE_FFmpegTagger::RSE_FFmpegTagger()
  : m_protocol(NULL)
  , m_glue(NULL)
  , m_track(0)
  , m_year(0)
  , m_bitrate(0)
  , m_samplerate(0.0)
  , m_channels(0)
  , m_length(0)
  , m_file_size(0)
{}

RSE_FFmpegTagger::~RSE_FFmpegTagger()
{
  if (m_protocol)
  {
    delete m_protocol;
    m_protocol = NULL;
  }

  if (m_glue)
  {
    delete m_glue;
    m_glue = NULL;
  }
}

bool RSE_FFmpegTagger::Open(const char *filename)
{
  if (m_protocol)
  {
    delete m_protocol;
    m_protocol = NULL;
  }

  if (m_glue)
  {
    delete m_glue;
    m_glue = NULL;
  }

  WDL_ASSERT(filename != NULL);

  m_fn.Set(filename);

  if (m_fn.GetLength() < 3) // probably greater: f.o
  {
    return false;
  }

  m_fn_part.Set(m_fn.get_filepart());
  m_fn_ext.Set(m_fn.get_fileext());

  m_fn_type.Set(m_fn_ext.Get());
  m_fn_type.DeleteSub(0,1);

  for (int i = 0; i < m_fn_type.GetLength(); i++)
  {
    char *c = m_fn_type.Get();
    c[i] = toupper_safe(c[i]);
  }

  WDL_SHA1 sha;
  char hash[WDL_SHA1SIZE];
  sha.reset();
  sha.add(m_fn.Get(), m_fn.GetLength());
  sha.result(hash);

  m_sha.Set("");
  for (int i = 0; i < WDL_SHA1SIZE; i++)
  {
    m_sha.AppendFormatted(64, "%02x", (unsigned char)(hash[i] & 0xff));
  }

  m_protocol  = new RSE_FFmpegFileProtocol(m_fn.Get());
  m_glue = new RSE_FFmpegGlue(m_protocol);

  if (!m_glue->OpenContext())
  {
    wdl_log("FFmpeg cannot open glue context\n");
    return false;
  }

  AVFormatContext *fmt_ctx = m_glue->FormatContext();
  AVDictionaryEntry *tag = NULL;

  //av_dict_set(&fmt_ctx->metadata, "skip_id3v1_tags", "", 0);
  fmt_ctx->max_analyze_duration = 60 * AV_TIME_BASE;

  if (!FindCodec())
  {
    wdl_log("FFmpeg cannot find codec\n");
    return false;
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "title", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    m_title.Set(tag->value);
  }
  else
  {
    m_title.Set(m_fn.get_filepart());
    m_title.remove_fileext();
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "artist", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    m_artist.Set(tag->value);
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "album", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    m_album.Set(tag->value);
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "genre", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    m_genre.Set(tag->value);
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "track", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    bool digit = true;
    int len = (int)strlen(tag->value);

    for (int i = 0; i < len; i++)
    {
      char *c = tag->value;
      if (isdigit_safe(c[i]) == 0)
      {
        digit = false;
        break;
      }
    }

    if (digit)
    {
      m_track = atoi(tag->value);
    }
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "date", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    bool digit = true;
    int len = (int)strlen(tag->value);

    for (int i = 0; i < len; i++)
    {
      char *c = tag->value;
      if (isdigit_safe(c[i]) == 0)
      {
        digit = false;
        break;
      }
    }

    if (digit)
    {
      m_year = atoi(tag->value);
    }
  }

  if ((tag = av_dict_get(fmt_ctx->metadata, "comment", NULL, AV_DICT_IGNORE_SUFFIX)))
  {
    m_comment.Set(tag->value);
  }

  m_samplerate = (double)m_codec_info.stream->codecpar->sample_rate;
  m_channels = m_codec_info.stream->codecpar->channels;
  m_length = (double)(fmt_ctx->duration / AV_TIME_BASE);

  int64_t tmp64;
  m_protocol->GetSize(&tmp64);
  m_file_size = (WDL_INT64)tmp64;

  m_bitrate = m_codec_info.stream->codecpar->bit_rate > 0 ?
    (int)(m_codec_info.stream->codecpar->bit_rate / 1000) : 0;

  if (m_bitrate == 0)
  {
    m_bitrate = fmt_ctx->bit_rate > 0 ? (int)(fmt_ctx->bit_rate / 1000) : 0;
  }

  if (m_bitrate == 0)
  {
    if (m_file_size > 0 && m_length > 0)
    {
      m_bitrate = (int)((double)m_file_size * 8 / m_length / 1000); // kbit/s
    }
  }

  return true;
}

bool RSE_FFmpegTagger::FindCodec()
{
  AVFormatContext *format_context = m_glue->FormatContext();

  // Retrieve stream information.
  if (avformat_find_stream_info(format_context, NULL) < 0)
  {
    wdl_log("FFmpeg cannot retrieve stream information\n");
    return false;
  }

  for (unsigned int i = 0; i < format_context->nb_streams; i++)
  {
    AVStream *stream = format_context->streams[i];

    if (stream && stream->codecpar && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
    {
      AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);

      if (codec)
      {
        m_codec_info.stream_index = i;
        m_codec_info.stream = stream;
        m_codec_info.codec = codec;

        return true;
      }
    }
  }

  wdl_log("FFmpeg could not find the codec\n");
  return false;
}

int RSE_FFmpegTagger::SizeOfFormat(int fmt)
{
  switch (fmt)
  {
  case AV_SAMPLE_FMT_U8:
  case AV_SAMPLE_FMT_U8P:
    return 1;

  case AV_SAMPLE_FMT_S16:
  case AV_SAMPLE_FMT_S16P:
    return 2;

  case AV_SAMPLE_FMT_S32:
  case AV_SAMPLE_FMT_S32P:
    return 4;

  case AV_SAMPLE_FMT_FLT:
  case AV_SAMPLE_FMT_FLTP:
    return 4;

  case AV_SAMPLE_FMT_DBL:
  case AV_SAMPLE_FMT_DBLP:
    return 8;

  default:
    return 0;
  }
}

const char *RSE_FFmpegTagger::GetType() const
{
  return m_fn_type.Get();
}

const char *RSE_FFmpegTagger::GetTitle() const
{
  return m_title.Get();
}

const char *RSE_FFmpegTagger::GetArtist() const
{
  return m_artist.Get();
}

const char *RSE_FFmpegTagger::GetAlbum() const
{
  return m_album.Get();
}

const char *RSE_FFmpegTagger::GetGenre() const
{
  return m_genre.Get();
}

int RSE_FFmpegTagger::GetTrack() const
{
  return m_track;
}

int RSE_FFmpegTagger::GetYear() const
{
  return m_year;
}

const char *RSE_FFmpegTagger::GetComment() const
{
  return m_comment.Get();
}

int RSE_FFmpegTagger::GetBitRate() const
{
  return m_bitrate;
}

double RSE_FFmpegTagger::GetSampleRate() const
{
  return m_samplerate;
}

int RSE_FFmpegTagger::GetChannels() const
{
  return m_channels;
}

double RSE_FFmpegTagger::GetLength() const
{
  return m_length;
}

WDL_INT64 RSE_FFmpegTagger::GetFileSize() const
{
  return m_file_size;
}

const char *RSE_FFmpegTagger::GetFileName() const
{
  return m_fn_part.Get();
}

const char *RSE_FFmpegTagger::GetFilePath() const
{
  return m_fn.Get();
}

const char *RSE_FFmpegTagger::GetFileExtension() const
{
  return m_fn_ext.Get();
}

const char *RSE_FFmpegTagger::GetSHA() const
{
  return m_sha.Get();
}
