/*
 * cdda_lib.c: The disc drive access functions
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: cdda_device.c,v 1.20 2005/12/23 09:10:30 cm Exp $
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <vdr/tools.h>
#include <cdio/bytesex.h>
#include "cdda_conf.h"
#include "cdda_device.h"

// --- cCddaTag ----------------------------------------------------------------

cCddaTag::cCddaTag() :
  m_dirty(false), mp_title(NULL), mp_performer(NULL), mp_genre(NULL), mp_message(NULL), mp_arranger(NULL),
  mp_composer(NULL), mp_songwriter(NULL), mp_discid(NULL), mp_isrc(NULL),
  mp_tocinfo(NULL), mp_tocinfo2(NULL) {
}

cCddaTag::~cCddaTag() {
  delete [] mp_title;
  delete [] mp_performer;
  delete [] mp_genre;
  delete [] mp_message;
  delete [] mp_arranger;
  delete [] mp_composer;
  delete [] mp_songwriter;
  delete [] mp_discid;
  delete [] mp_isrc;
  delete [] mp_tocinfo;
  delete [] mp_tocinfo2;
}

bool cCddaTag::SetDirty(bool dirty) {
  return m_dirty = dirty;
}

const char *cCddaTag::SetTitle(const char *title) {
  if (NULL != mp_title)
    delete []mp_title;

  if (NULL == title)
    return mp_title = NULL;
  else
    return mp_title = NewStrDup(title);
}

const char *cCddaTag::SetPerformer(const char *performer) {
  if (NULL != mp_performer)
    delete [] mp_performer;

  if (NULL == performer)
    return mp_performer = NULL;
  else
    return mp_performer = NewStrDup(performer);
}

const char *cCddaTag::SetGenre(const char *genre) {
  if (NULL != mp_genre)
    delete [] mp_genre;

  if (NULL == genre)
    return mp_genre = NULL;
  else
    return mp_genre = NewStrDup(genre);
}

const char *cCddaTag::SetSongwriter(const char *songwriter) {
  if (NULL != mp_songwriter)
    delete [] mp_songwriter;

  if (NULL == songwriter)
    return mp_songwriter = NULL;
  else
    return mp_songwriter = NewStrDup(songwriter);
}

const char *cCddaTag::SetArranger(const char *arranger) {
  if (NULL != mp_arranger)
    delete[] mp_arranger;

  if (NULL == arranger)
    return mp_arranger = NULL;
  else
    return mp_arranger = NewStrDup(arranger);
}

const char *cCddaTag::SetComposer(const char *composer) {
  if (NULL != mp_composer)
    delete [] mp_composer;

  if (NULL == composer)
    return mp_composer = NULL;
  else
    return mp_composer = NewStrDup(composer);
}

const char *cCddaTag::SetMessage(const char *message) {
  if (NULL != mp_message)
    delete [] mp_message;

  if (NULL == message)
    return mp_message = NULL;
  else
    return mp_message = NewStrDup(message);
}

const char *cCddaTag::SetDiscId(const char *discid) {
  if (NULL != mp_discid)
    delete [] mp_discid;

  if (NULL == discid)
    return mp_discid = NULL;
  else
    return mp_discid = NewStrDup(discid);
}

const char *cCddaTag::SetTocInfo(const char *tocinfo) {
  if (NULL != mp_tocinfo)
    delete [] mp_tocinfo;

  if (NULL == tocinfo)
    return mp_tocinfo = NULL;
  else
    return mp_tocinfo = NewStrDup(tocinfo);
}

const char *cCddaTag::SetTocInfo2(const char *tocinfo2) {
  if (NULL != mp_tocinfo2)
    delete [] mp_tocinfo2;

  if (NULL == tocinfo2)
    return mp_tocinfo2 = NULL;
  else
    return mp_tocinfo2 = NewStrDup(tocinfo2);
}

const char *cCddaTag::SetIsrc(const char *isrc) {
  if (NULL != mp_isrc)
    delete [] mp_isrc;

  if (NULL == isrc)
    return mp_isrc = NULL;
  else
    return mp_isrc = NewStrDup(isrc);
}

// --- cCddaTags ---------------------------------------------------------------

cCddaTags::cCddaTags() {
  Initialize();
}

cCddaTags::~cCddaTags() {
}

int cCddaTags::g_current = -1;

void cCddaTags::Initialize(void) {
  cCddaTag *po_tag = new cCddaTag();

  if (NULL == mo_tags.Add(po_tag))
    delete po_tag;
  else
    g_current = 0;
}

cCddaTag *cCddaTags::Current(void) const {
  if (g_current >= mo_tags.Count())
    g_current = 0;

  return Item(g_current);
}

cCddaTag *cCddaTags::Next(void) {
  if (++g_current == mo_tags.Count())
    g_current = 0;

  return Item(g_current);
}

int cCddaTags::Delete(int index) {
  if (0 != index)
    return mo_tags.Delete(index);

  return -1;
}

// --- cddaDevice --------------------------------------------------------------

cCddaDevice::cCddaDevice() :
  m_lock(0), mp_device(NULL), mp_cdIo(NULL), mpo_disc(NULL) {
  cdio_init();
}

cCddaDevice::~cCddaDevice() {
  free(mp_device);
}

cCddaDevice *cCddaDevice::gpo_device = NULL;

cCddaDevice& cCddaDevice::GetInstance(void) {
  if (NULL == gpo_device)
    gpo_device = new cCddaDevice();

  return *gpo_device;
}

void cCddaDevice::Lock(void) {
  m_lock++;
}

void cCddaDevice::Unlock(void) {
  if (0 < m_lock)
    m_lock--;
}

eCddaStatusCode cCddaDevice::Open(const char* name) {
  if (NULL == mp_cdIo) {
    mp_device = strdup(name);
    if (NULL != mp_device) {
      mp_cdIo = cdio_open(mp_device, DRIVER_LINUX);
      if (NULL == mp_cdIo)
        return scCannotOpenDevice;
        cdio_set_speed(mp_cdIo,2);
    } else
      return scOutOfMemory;
  } else
    return scCannotOpenDevice;

  return scSuccess;
}

eCddaStatusCode cCddaDevice::Close(void) {
  if (0 == m_lock) {
    if (mpo_disc)
      DELETENULL(mpo_disc);

    // cdio_destroy accepts NULL and should always be called
    cdio_destroy(mp_cdIo);
    mp_cdIo = NULL;

    free(mp_device);
    mp_device = NULL;
  } else
    return scLocked;

  return scSuccess;
}

cCddaDisc *cCddaDevice::Disc(void) {
  if (NULL == mpo_disc && NULL != mp_cdIo)
    mpo_disc = new cCddaDisc(mp_cdIo);

  return mpo_disc;
}

eCddaStatusCode cCddaDevice::Eject(void) {
  DELETENULL(mpo_disc);
  if (DRIVER_OP_SUCCESS != cdio_eject_media(&mp_cdIo))
    return scCannotEjectMedia;

  return scSuccess;
}

// --- cddaDisc ---------------------------------------------------------------
cCddaDisc::cCddaDisc(CdIo_t *cdIo) :
  cddbId(0), po_tracks(NULL), p_cdIo(cdIo),
  discMode(CDIO_DISC_MODE_NO_INFO) {
  ReadCDText();
}

cCddaDisc::~cCddaDisc() {
  delete po_tracks;

  p_cdIo = NULL;
}

bool cCddaDisc::isAudio(void) {
  if (CDIO_DISC_MODE_NO_INFO == discMode)
    discMode = cdio_get_discmode(p_cdIo);

  return CDIO_DISC_MODE_CD_DA == discMode;
}

cCddaTracks *cCddaDisc::Tracks(void) {
  if (NULL == po_tracks)
    po_tracks = new cCddaTracks(p_cdIo);

  return po_tracks;
}

void cCddaDisc::ReadCDText(void) {
  cdtext_t *p_cdText = cdio_get_cdtext(p_cdIo, 0);

  if (NULL != p_cdText) {
    for (int key = CDTEXT_ARRANGER; key < MAX_CDTEXT_FIELDS; key++) {
      const char *p_string = NULL;
      if (NULL != p_cdText->field[key]) // FIX: workaround for bug in libcdio 0.72
        p_string = cdtext_get((cdtext_field_t)key, p_cdText);
      else
        continue;

      switch (key) {
        case CDTEXT_ARRANGER:
          o_tags.Default()->SetArranger(p_string);
          break;
        case CDTEXT_COMPOSER:
          o_tags.Default()->SetComposer(p_string);
          break;
        case CDTEXT_DISCID:
          o_tags.Default()->SetDiscId(p_string);
          break;
        case CDTEXT_GENRE:
          o_tags.Default()->SetGenre(p_string);
          break;
        case CDTEXT_MESSAGE:
          o_tags.Default()->SetMessage(p_string);
          break;
        case CDTEXT_ISRC:
          o_tags.Default()->SetIsrc(p_string);
          break;
        case CDTEXT_PERFORMER:
          o_tags.Default()->SetPerformer(p_string);
          break;
        case CDTEXT_SONGWRITER:
          o_tags.Default()->SetSongwriter(p_string);
          break;
        case CDTEXT_TITLE:
          o_tags.Default()->SetTitle(p_string);
          break;
        case CDTEXT_TOC_INFO:
          o_tags.Default()->SetTocInfo(p_string);
          break;
        case CDTEXT_TOC_INFO2:
          o_tags.Default()->SetTocInfo2(p_string);
          break;
        default:
          break;
      } // switch (key)
    } // for (...
    cdtext_destroy(p_cdText);
  }
}

unsigned int cCddaDisc::GetSum(unsigned int number) const {
  unsigned int value = 0, n = number;

  while (0 < n) {
    value += n % 10;
    n /= 10;
  }

  return value;
}

unsigned long cCddaDisc::GetCddbId(void) {
  if (0 == cddbId) {
    track_t firstTrack = cdio_get_first_track_num(p_cdIo);
    lba_t firstLba = cdio_get_track_lba(p_cdIo, firstTrack);

    if (CDIO_INVALID_TRACK == firstTrack || CDIO_INVALID_LBA == firstLba)
      return 0;

    unsigned int sum = 0, last = Tracks()->GetFirst() + Tracks()->Count() - 1;
    for (unsigned int i = Tracks()->GetFirst(); i <= last; i++)
      sum += GetSum(DataSizeToSeconds(Tracks()->Item(i)->GetOffset() + (firstLba / CDIO_CD_FRAMES_PER_SEC * 176400)));

    cddbId = ((sum % 0xff) << 24 | DataSizeToSeconds(Tracks()->GetSize()) << 8 | Tracks()->Count());
  }

  return cddbId;
}

// --- cddaTracks -------------------------------------------------------------

cCddaTracks::cCddaTracks(CdIo_t *cdIo) :
  p_cdIo(cdIo), firstTrack(0), numTracks(0) {
  memset(po_track, 0x00, sizeof(cCddaTrack *) * CDIO_CD_MAX_TRACKS);

  GetFirst();
  Count();
}

cCddaTracks::~cCddaTracks() {
  for (int i = 0; i < CDIO_CD_MAX_TRACKS; i++)
    delete po_track[i];
  p_cdIo = NULL;
}

unsigned int cCddaTracks::Count(void) {
  if (0 == numTracks)
    numTracks = cdio_get_last_track_num(p_cdIo) - cdio_get_first_track_num(p_cdIo) + 1;

  return numTracks;
}

unsigned int cCddaTracks::GetFirst(void) {
  if (0 == firstTrack)
    firstTrack = cdio_get_first_track_num(p_cdIo);

  return firstTrack;
}

cCddaTrack *cCddaTracks::Item(track_t index) {
  if (firstTrack <= index && index <= (firstTrack+numTracks)) {
    if (NULL == po_track[index])
      po_track[index] = new cCddaTrack(p_cdIo, index);

    return po_track[index];
  } else
    return NULL;
}

// returns the number of bytes
unsigned long cCddaTracks::GetSize(void) {
  lsn_t startLsn = cdio_get_track_lsn(p_cdIo, GetFirst());
  lsn_t endLsn = cdio_get_track_last_lsn(p_cdIo, Count());

  return (endLsn - startLsn) * CDIO_CD_FRAMESIZE_RAW;
}

// --- cddaTrack --------------------------------------------------------------
cCddaTrack::cCddaTrack(CdIo_t *cdIo, track_t index) :
  m_numChannels(0), m_trackNum(index), mp_cdIo(cdIo), m_startLsn(0), m_endLsn(0),
  m_trackFormat(TRACK_FORMAT_ERROR) {
  Initialize();
}

cCddaTrack::~cCddaTrack() {
  mp_cdIo = NULL;
}

uint8_t cCddaTrack::g_buffer[];

void cCddaTrack::Initialize(void) {
  m_startLsn = cdio_get_track_lsn(mp_cdIo, m_trackNum);
  m_endLsn = cdio_get_track_last_lsn(mp_cdIo, m_trackNum);

  ReadCDText();
}

bool cCddaTrack::isAudio(void) {
  if (TRACK_FORMAT_ERROR == m_trackFormat)
    m_trackFormat = cdio_get_track_format(mp_cdIo, m_trackNum);

  return TRACK_FORMAT_AUDIO == m_trackFormat;
}

void cCddaTrack::ReadCDText(void) {
  cdtext_t *p_cdText = cdio_get_cdtext(mp_cdIo, m_trackNum);

  if (NULL != p_cdText) {
    for (int key = CDTEXT_ARRANGER; key < MAX_CDTEXT_FIELDS; key++) {
      const char *p_string = NULL;
      if (NULL != p_cdText->field[key]) // FIX: workaround for bug in libcdio 0.72
        p_string = cdtext_get((cdtext_field_t)key, p_cdText);
      else
        continue;

      switch (key) {
        case CDTEXT_ARRANGER:
          o_tags.Default()->SetArranger(p_string);
          break;
        case CDTEXT_COMPOSER:
          o_tags.Default()->SetComposer(p_string);
          break;
        case CDTEXT_DISCID:
          o_tags.Default()->SetDiscId(p_string);
          break;
        case CDTEXT_GENRE:
          o_tags.Default()->SetGenre(p_string);
          break;
        case CDTEXT_MESSAGE:
          o_tags.Default()->SetMessage(p_string);
          break;
        case CDTEXT_ISRC:
          o_tags.Default()->SetIsrc(p_string);
          break;
        case CDTEXT_PERFORMER:
          o_tags.Default()->SetPerformer(p_string);
          break;
        case CDTEXT_SONGWRITER:
          o_tags.Default()->SetSongwriter(p_string);
          break;
        case CDTEXT_TITLE:
          o_tags.Default()->SetTitle(p_string);
          break;
        case CDTEXT_TOC_INFO:
          o_tags.Default()->SetTocInfo(p_string);
          break;
        case CDTEXT_TOC_INFO2:
          o_tags.Default()->SetTocInfo2(p_string);
          break;
        default:
          break;
      } // switch (key)
    } // for (...
    cdtext_destroy(p_cdText);
  }
}

int cCddaTrack::GetLba(void) const {
  lba_t lba = cdio_get_track_lba(mp_cdIo, m_trackNum);

  if (CDIO_INVALID_LBA == lba)
    return -1;

  return lba;
}

// returns the number of bytes from the start up to the track
unsigned long cCddaTrack::GetOffset(void) const {
  if (CDIO_INVALID_LSN == m_startLsn || CDIO_INVALID_LSN == m_endLsn)
    return 0;

  lsn_t lsn = 0;
  track_t track = cdio_get_first_track_num(mp_cdIo);
  if (CDIO_INVALID_TRACK == track || CDIO_INVALID_LSN == (lsn = cdio_get_track_lsn(mp_cdIo, track)))
    return 0;
  else 
    return (m_startLsn - lsn) * CDIO_CD_FRAMESIZE_RAW;
}

int cCddaTrack::GetChannels(void) {
  if (!m_numChannels) {
    if (0 > (m_numChannels = cdio_get_track_channels(mp_cdIo, m_trackNum)))
      return scFail;
  }
  return m_numChannels;
}

uint8_t *cCddaTrack::ReadSector(long index, int& count) {
  if (CDIO_INVALID_LSN != m_startLsn && CDIO_INVALID_LSN != m_endLsn &&
      m_startLsn + index <= m_endLsn) {
    if (CDDA_READ_MAX_SECTORS < count)
      count = CDDA_READ_MAX_SECTORS;

    if (m_startLsn + index + count > m_endLsn)
      count = m_endLsn - (m_startLsn + index) + 1;

    if (DRIVER_OP_SUCCESS == cdio_read_audio_sectors(mp_cdIo, g_buffer, m_startLsn + index, count))
      return g_buffer;
  }

  return NULL;
}
