/*
 *  mpadec - MPEG audio decoder
 *  Copyright (C) 2002-2004 Dmitriy Startsev (dstartsev@rambler.ru)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* $Id: mp3dec.c,v 1.5 2004/08/03 05:22:22 metal_man Exp $ */

#include "mp3dec_internal.h"

mp3dec_t MPADECAPI mp3dec_init(void)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)malloc(sizeof(struct mp3dec_t));

  if (!mp3) return NULL;
  memset(mp3, 0, sizeof(struct mp3dec_t));
  mp3->size = sizeof(struct mp3dec_t);
  mp3->fd = -1;
  mp3->mpadec = mpadec_init();
  if (!mp3->mpadec) {
    free(mp3);
    return NULL;
  }
  return mp3;
}

int MPADECAPI mp3dec_init_file(mp3dec_t mp3dec, int fd, int64_t length, int nogap)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;
  int64_t tmp; int r;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (fd < 0) {
    mp3dec_reset(mp3);
    return MP3DEC_RETCODE_INVALID_PARAMETERS;
  }
  if (mp3->flags & MP3DEC_FLAG_INITIALIZED) close(mp3->fd);
  mp3->fd = fd;
  mp3->flags = MP3DEC_FLAG_SEEKABLE;
  mp3->stream_offset = mp3->stream_size = mp3->stream_position = 0;
  mp3->in_buffer_offset = mp3->in_buffer_used = 0;
  mp3->out_buffer_offset = mp3->out_buffer_used = 0;
  tmp = lseek(fd, 0, SEEK_CUR);
  if (tmp >= 0) mp3->stream_offset = tmp; else mp3->flags &= ~MP3DEC_FLAG_SEEKABLE;
  if (mp3->flags & MP3DEC_FLAG_SEEKABLE) {
    tmp = lseek(fd, 0, SEEK_END);
    if (tmp >= 0) {
      mp3->stream_size = tmp;
      lseek(fd, mp3->stream_offset, SEEK_SET);
    } else mp3->flags &= ~MP3DEC_FLAG_SEEKABLE;
  }
  if (mp3->stream_size > mp3->stream_offset) {
    mp3->stream_size -= mp3->stream_offset;
    if (length && (length < mp3->stream_size)) mp3->stream_size = length;
  } else mp3->stream_size = length;
  r = read(fd, mp3->in_buffer, 4);
  if (r < 4) {
    mp3dec_reset(mp3);
    return ((r < 0) ? MP3DEC_RETCODE_INVALID_PARAMETERS : MP3DEC_RETCODE_NOT_MPEG_STREAM);
  } else mp3->in_buffer_used = r;
  if (mp3->flags & MP3DEC_FLAG_SEEKABLE) tmp = lseek(fd, mp3->stream_offset, SEEK_SET); else tmp = -1;
  if (tmp < 0) {
    int32_t n = sizeof(mp3->in_buffer) - mp3->in_buffer_used;
    mp3->flags &= ~MP3DEC_FLAG_SEEKABLE;
    if (mp3->stream_size && (n > (mp3->stream_size - mp3->in_buffer_used))) n = (int32_t)(mp3->stream_size - mp3->in_buffer_used);
    n = read(fd, mp3->in_buffer + mp3->in_buffer_used, n);
    if (n < 0) n = 0;
    mp3->in_buffer_used += n;
    mp3->stream_position = mp3->in_buffer_used;
  } else {
    int32_t n = sizeof(mp3->in_buffer);
    if (mp3->stream_size && (n > mp3->stream_size)) n = (int32_t)mp3->stream_size;
    n = read(fd, mp3->in_buffer, n);
    if (n < 0) n = 0;
    mp3->stream_position = mp3->in_buffer_used = n;
  }
  if (mp3->in_buffer_used < 4) {
    mp3dec_reset(mp3);
    return MP3DEC_RETCODE_NOT_MPEG_STREAM;
  }
  if (nogap) {
    mpadec_decode(mp3->mpadec, mp3->in_buffer, mp3->in_buffer_used, mp3->out_buffer, sizeof(mp3->out_buffer), &mp3->in_buffer_offset, &mp3->out_buffer_used);
    mp3->in_buffer_used -= mp3->in_buffer_offset;
    if (!mp3->out_buffer_used) {
      mpadec_reset(mp3->mpadec);
      mp3->in_buffer_used += mp3->in_buffer_offset;
      mp3->in_buffer_offset = 0;
    }
  } else mpadec_reset(mp3->mpadec);
  if (!mp3->out_buffer_used) {
    r = mpadec_decode(mp3->mpadec, mp3->in_buffer, mp3->in_buffer_used, NULL, 0, &mp3->in_buffer_offset, NULL);
    mp3->in_buffer_used -= mp3->in_buffer_offset;
    if (r != MPADEC_RETCODE_OK) {
      mp3dec_reset(mp3);
      return MP3DEC_RETCODE_NOT_MPEG_STREAM;
    }
  }
  if ((mpadec_get_info(mp3->mpadec, &mp3->mpainfo, MPADEC_INFO_STREAM) != MPADEC_RETCODE_OK) ||
      (mpadec_get_info(mp3->mpadec, &mp3->taginfo, MPADEC_INFO_TAG) != MPADEC_RETCODE_OK)) {
    mp3dec_reset(mp3);
    return MP3DEC_RETCODE_NOT_MPEG_STREAM;
  }
  if (mp3->taginfo.flags & 2) if (!mp3->stream_size || (mp3->stream_size > mp3->taginfo.bytes)) mp3->stream_size = mp3->taginfo.bytes;
  if (mp3->taginfo.flags & 1) {
    mp3->mpainfo.frames = mp3->taginfo.frames;
    if (mp3->mpainfo.frames && mp3->mpainfo.frame_samples) {
      mp3->mpainfo.bitrate = (int32_t)((FLOAT)(((FLOAT)mp3->stream_size*(FLOAT)mp3->mpainfo.frequency + 0.5)/((FLOAT)125.0*mp3->mpainfo.frame_samples*mp3->mpainfo.frames)));
    }
  } else if (mp3->mpainfo.bitrate && mp3->mpainfo.frame_samples) {
    mp3->mpainfo.frames = (int32_t)((FLOAT)(((FLOAT)mp3->stream_size*(FLOAT)mp3->mpainfo.frequency + 0.5)/((FLOAT)125.0*mp3->mpainfo.frame_samples*mp3->mpainfo.bitrate)));
  }
  mp3->mpainfo.duration = (mp3->mpainfo.frames*mp3->mpainfo.frame_samples + (mp3->mpainfo.frequency >> 1))/mp3->mpainfo.frequency;
  mp3->flags |= MP3DEC_FLAG_INITIALIZED;
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_uninit(mp3dec_t mp3dec)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (mp3->flags & MP3DEC_FLAG_INITIALIZED) close(mp3->fd);
  mp3->fd = -1;
  mp3->flags = 0;
  mpadec_uninit(mp3->mpadec);
  mp3->size = 0;
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_reset(mp3dec_t mp3dec)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (mp3->flags & MP3DEC_FLAG_INITIALIZED) close(mp3->fd);
  mp3->fd = -1;
  mp3->flags = 0;
  mpadec_reset(mp3->mpadec);
  mp3->stream_offset = mp3->stream_size = mp3->stream_position = 0;
  mp3->in_buffer_offset = mp3->in_buffer_used = 0;
  mp3->out_buffer_offset = mp3->out_buffer_used = 0;
  memset(&mp3->mpainfo, 0, sizeof(mp3->mpainfo));
  memset(&mp3->taginfo, 0, sizeof(mp3->taginfo));
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_configure(mp3dec_t mp3dec, mpadec_config_t *cfg)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  mp3->out_buffer_offset = mp3->out_buffer_used = 0;
  if (mpadec_configure(mp3->mpadec, cfg) != MPADEC_RETCODE_OK) return MP3DEC_RETCODE_INVALID_PARAMETERS;
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_get_info(mp3dec_t mp3dec, void *info, int info_type)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (!info) return MP3DEC_RETCODE_INVALID_PARAMETERS;
  if (mp3->flags & MP3DEC_FLAG_INITIALIZED) {
    switch (info_type) {
      case MPADEC_INFO_STREAM: memcpy(info, &mp3->mpainfo, sizeof(mp3->mpainfo)); break;
      case MPADEC_INFO_TAG:    memcpy(info, &mp3->taginfo, sizeof(mp3->taginfo)); break;
      case MPADEC_INFO_CONFIG:
      default:                 if (mpadec_get_info(mp3->mpadec, info, info_type) != MPADEC_RETCODE_OK) return MP3DEC_RETCODE_INVALID_PARAMETERS;
    }
  } else return MP3DEC_RETCODE_BAD_STATE;
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_decode(mp3dec_t mp3dec, uint8_t *buf, uint32_t bufsize, uint32_t *used)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;
  uint32_t n, src_used, dst_used; int r;

  if (used) *used = 0;
  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (!(mp3->flags & MP3DEC_FLAG_INITIALIZED)) return MP3DEC_RETCODE_BAD_STATE;
  if (!buf) return MP3DEC_RETCODE_INVALID_PARAMETERS;
  while (bufsize) {
    if (mp3->out_buffer_used) {
      n = (bufsize < mp3->out_buffer_used) ? bufsize : mp3->out_buffer_used;
      memcpy(buf, mp3->out_buffer + mp3->out_buffer_offset, n);
      mp3->out_buffer_offset += n;
      mp3->out_buffer_used -= n;
      buf += n;
      bufsize -= n;
      if (used) *used += n;
    }
    if (!bufsize) break;
    if (mp3->in_buffer_used > 4) {
      r = mpadec_decode(mp3->mpadec, mp3->in_buffer + mp3->in_buffer_offset, mp3->in_buffer_used, buf, bufsize, &src_used, &dst_used);
      mp3->in_buffer_offset += src_used;
      mp3->in_buffer_used -= src_used;
      buf += dst_used;
      bufsize -= dst_used;
      if (used) *used += dst_used;
      if (!bufsize) break;
      if (r == MPADEC_RETCODE_BUFFER_TOO_SMALL) {
        mp3->out_buffer_offset = mp3->out_buffer_used = 0;
        mpadec_decode(mp3->mpadec, mp3->in_buffer + mp3->in_buffer_offset, mp3->in_buffer_used, mp3->out_buffer, sizeof(mp3->out_buffer), &src_used, &mp3->out_buffer_used);
        mp3->in_buffer_offset += src_used;
        mp3->in_buffer_used -= src_used;
        continue;
      }
    }
    if (mp3->in_buffer_used && mp3->in_buffer_offset) memmove(mp3->in_buffer, mp3->in_buffer + mp3->in_buffer_offset, mp3->in_buffer_used);
    mp3->in_buffer_offset = 0;
    n = sizeof(mp3->in_buffer) - mp3->in_buffer_used;
    if (mp3->stream_size && (n > (mp3->stream_size - mp3->stream_position))) n = (int32_t)(mp3->stream_size - mp3->stream_position);
    if (n) r = read(mp3->fd, mp3->in_buffer + mp3->in_buffer_used, n); else r = 0;
    if (r < 0) r = 0;
    mp3->in_buffer_used += r;
    mp3->stream_position += r;
    if (mp3->stream_position > mp3->stream_size) mp3->stream_position = mp3->stream_size;
    if (!r) break;
  }
  return MP3DEC_RETCODE_OK;
}

int MPADECAPI mp3dec_seek(mp3dec_t mp3dec, int64_t pos, int units)
{
  register struct mp3dec_t *mp3 = (struct mp3dec_t *)mp3dec;
  int64_t newpos;

  if (!mp3 || (mp3->size != sizeof(struct mp3dec_t)) || !mp3->mpadec) return MP3DEC_RETCODE_INVALID_HANDLE;
  if (!(mp3->flags & MP3DEC_FLAG_INITIALIZED)) return MP3DEC_RETCODE_BAD_STATE;
  if (!(mp3->flags & MP3DEC_FLAG_SEEKABLE)) return MP3DEC_RETCODE_SEEK_FAILED;
  if (units == MP3DEC_SEEK_BYTES) {
    newpos = (pos < mp3->stream_size) ? pos : mp3->stream_size;
    newpos = lseek(mp3->fd, mp3->stream_offset + newpos, SEEK_SET);
    if (newpos < 0) return MP3DEC_RETCODE_SEEK_FAILED;
    mp3->stream_position = newpos - mp3->stream_offset;
    mp3->in_buffer_offset = mp3->in_buffer_used = 0;
    mp3->out_buffer_offset = mp3->out_buffer_used = 0;
  } else if (units == MP3DEC_SEEK_SAMPLES) {
    FLOAT fsize = (FLOAT)(125.0*mp3->mpainfo.bitrate*mp3->mpainfo.decoded_frame_samples)/(FLOAT)mp3->mpainfo.decoded_frequency;
    newpos = (int64_t)((FLOAT)pos*fsize/(FLOAT)mp3->mpainfo.decoded_frame_samples);
    if (newpos > mp3->stream_size) newpos = mp3->stream_size;
    pos = (pos%mp3->mpainfo.decoded_frame_samples)*mp3->mpainfo.decoded_sample_size;
    newpos = lseek(mp3->fd, mp3->stream_offset + newpos, SEEK_SET);
    if (newpos < 0) return MP3DEC_RETCODE_SEEK_FAILED;
    mp3->stream_position = newpos - mp3->stream_offset;
    mp3->in_buffer_offset = mp3->in_buffer_used = 0;
    mp3->out_buffer_offset = mp3->out_buffer_used = 0;
    {
      uint8_t temp[8*1152];
      mp3dec_decode(mp3, temp, (uint32_t)pos, NULL);
    }
  } else if (units == MP3DEC_SEEK_SECONDS) {
    if (pos > mp3->mpainfo.duration) pos = mp3->mpainfo.duration;
    if (mp3->taginfo.flags & 4) {
      int32_t n = (int32_t)((100*pos + (mp3->mpainfo.duration >> 1))/mp3->mpainfo.duration);
      if (n > 99) newpos = mp3->stream_size;
      else newpos = (mp3->taginfo.toc[n]*mp3->stream_size)/255;
    } else newpos = (pos*mp3->stream_size + (mp3->mpainfo.duration >> 1))/mp3->mpainfo.duration;
    if (newpos > mp3->stream_size) newpos = mp3->stream_size;
    newpos = lseek(mp3->fd, mp3->stream_offset + newpos, SEEK_SET);
    if (newpos < 0) return MP3DEC_RETCODE_SEEK_FAILED;
    mp3->stream_position = newpos - mp3->stream_offset;
    mp3->in_buffer_offset = mp3->in_buffer_used = 0;
    mp3->out_buffer_offset = mp3->out_buffer_used = 0;
  } else return MP3DEC_RETCODE_INVALID_PARAMETERS;
  return MP3DEC_RETCODE_OK;
}

char * MPADECAPI mp3dec_error(int code)
{
  static char *mp3_errors[] = { "No error",
                                "Invalid handle",
                                "Bad decoder state",
                                "Invalid parameters",
                                "Not an MPEG audio stream",
                                "Seek failed",
                                "Unknown error" };
  if (code > MP3DEC_RETCODE_UNKNOWN) code = MP3DEC_RETCODE_UNKNOWN;
  return mp3_errors[code];
}

