/***************************************************************************
 *                                                                         *
 * Modified from cd-discid.c, found at http://lly.org/~rcw/cd-discid/      *
 *                                                                         *
 * Copyright (c) 1999-2003 Robert Woodcock <rcw@debian.org>                *
 *                                                                         *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "freedbimporter.h"
#include "../tellico_debug.h"

using Tellico::Import::FreeDBImporter;

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/ioctl.h>

/* Porting credits:
 * Solaris: David Champion <dgc@uchicago.edu>
 * FreeBSD: Niels Bakker <niels@bakker.net>
 * OpenBSD: Marcus Daniel <danielm@uni-muenster.de>
 * NetBSD: Chris Gilbert <chris@NetBSD.org>
 * MacOSX: Evan Jones <ejones@uwaterloo.ca> http://www.eng.uwaterloo.ca/~ejones/
 */

#if defined(__linux__)

// see http://bugs.kde.org/show_bug.cgi?id=86188
#ifdef __STRICT_ANSI__
#undef __STRICT_ANSI__
#define _ANSI_WAS_HERE_
#endif
#include <linux/types.h>
#include <linux/cdrom.h>
#ifdef _ANSI_WAS_HERE_
#define __STRICT_ANSI__
#undef _ANSI_WAS_HERE_
#endif
#define         cdte_track_address      cdte_addr.lba

#elif defined(sun) && defined(unix) && defined(__SVR4)

#include <sys/cdio.h>
#define CD_MSF_OFFSET   150
#define CD_FRAMES       75
/* According to David Schweikert <dws@ee.ethz.ch>, cd-discid needs this
 * to compile on Solaris */
#define cdte_track_address cdte_addr.lba

#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)

#include <netinet/in.h>
#include <sys/cdio.h>
#define        CDROM_LBA       CD_LBA_FORMAT   /* first frame is 0 */
#define        CD_MSF_OFFSET   150     /* MSF offset of first frame */
#define        CD_FRAMES       75      /* per second */
#define        CDROM_LEADOUT   0xAA    /* leadout track */
#define        CDROMREADTOCHDR         CDIOREADTOCHEADER
#define        CDROMREADTOCENTRY       CDIOREADTOCENTRY
#define        cdrom_tochdr    ioc_toc_header
#define        cdth_trk0       starting_track
#define        cdth_trk1       ending_track
#define        cdrom_tocentry  ioc_read_toc_single_entry
#define        cdte_track      track
#define        cdte_format     address_format
#define        cdte_track_address       entry.addr.lba

#elif defined(__OpenBSD__) || defined(__NetBSD__)

#include <netinet/in.h>
#include <sys/cdio.h>
#define        CDROM_LBA       CD_LBA_FORMAT   /* first frame is 0 */
#define        CD_MSF_OFFSET   150     /* MSF offset of first frame */
#define        CD_FRAMES       75      /* per second */
#define        CDROM_LEADOUT   0xAA    /* leadout track */
#define        CDROMREADTOCHDR         CDIOREADTOCHEADER
#define        cdrom_tochdr    ioc_toc_header
#define        cdth_trk0       starting_track
#define        cdth_trk1       ending_track
#define        cdrom_tocentry  cd_toc_entry
#define        cdte_track      track
#define        cdte_track_address       addr.lba

#elif defined(__APPLE__)

#include <sys/types.h>
#include <IOKit/storage/IOCDTypes.h>
#include <IOKit/storage/IOCDMediaBSDClient.h>
#define        CD_FRAMES       75      /* per second */
#define        CD_MSF_OFFSET   150     /* MSF offset of first frame */
#define        cdrom_tochdr    CDDiscInfo
#define        cdth_trk0       numberOfFirstTrack
/* NOTE: Judging by the name here, we might have to do this:
 * hdr.lastTrackNumberInLastSessionMSB << 8 *
 * sizeof(hdr.lastTrackNumberInLastSessionLSB)
 * | hdr.lastTrackNumberInLastSessionLSB; */
#define        cdth_trk1       lastTrackNumberInLastSessionLSB
#define        cdrom_tocentry  CDTrackInfo
#define        cdte_track_address trackStartAddress

#else
# warning "Your OS isn't supported yet for CDDB lookup."
#endif  /* os selection */

}

#include <config.h>

namespace {
  class CloseDrive {
  public:
    CloseDrive(int d) : drive(d) {}
    ~CloseDrive() { ::close(drive); }
  private:
    int drive;
  };
}

TQValueList<uint> FreeDBImporter::offsetList(const TQCString& drive_, TQValueList<uint>& trackLengths_) {
  TQValueList<uint> list;

  int drive = ::open(drive_.data(), O_RDONLY | O_NONBLOCK);
  CloseDrive closer(drive);
  if(drive < 0) {
    return list;
  }

  cdrom_tochdr hdr;
#if defined(__APPLE__)
  dk_cd_read_disc_info_t discInfoParams;
  ::memset(&discInfoParams, 0, sizeof(discInfoParams));
  discInfoParams.buffer = &hdr;
  discInfoParams.bufferLength = sizeof(hdr);
  if(ioctl(drive, DKIOCCDREADDISCINFO, &discInfoParams) < 0
     || discInfoParams.bufferLength != sizeof(hdr)) {
    return list;
  }
#else
  if(ioctl(drive, CDROMREADTOCHDR, &hdr) < 0) {
    return list;
  }
#endif

//  uchar first = hdr.cdth_trk0;
  uchar last = hdr.cdth_trk1;

  cdrom_tocentry* TocEntry = new cdrom_tocentry[last+1];
#if defined(__OpenBSD__)
  ioc_read_toc_entry t;
  t.starting_track = 0;
#elif defined(__NetBSD__)
  ioc_read_toc_entry t;
  t.starting_track = 1;
#endif
#if defined(__OpenBSD__) || defined(__NetBSD__)
  t.address_format = CDROM_LBA;
  t.data_len = (last + 1) * sizeof(cdrom_tocentry);
  t.data = TocEntry;

  if (::ioctl(drive, CDIOREADTOCENTRYS, (char *) &t) < 0)
       return list;

#elif defined(__APPLE__)
  dk_cd_read_track_info_t trackInfoParams;
  ::memset(&trackInfoParams, 0, sizeof(trackInfoParams));
  trackInfoParams.addressType = kCDTrackInfoAddressTypeTrackNumber;
  trackInfoParams.bufferLength = sizeof(*TocEntry);

  for(int i = 0; i < last; ++i) {
    trackInfoParams.address = i + 1;
    trackInfoParams.buffer = &TocEntry[i];
    ::ioctl(drive, DKIOCCDREADTRACKINFO, &trackInfoParams);
  }

  /* MacOS X on G5-based systems does not report valid info for
   * TocEntry[last-1].lastRecordedAddress + 1, so we compute the start
   * of leadout from the start+length of the last track instead
   */
  TocEntry[last].cdte_track_address = TocEntry[last-1].trackSize + TocEntry[last-1].trackStartAddress;
#else /* FreeBSD, Linux, Solaris */
  for(uint i = 0; i < last; ++i) {
    /* tracks start with 1, but I must start with 0 on OpenBSD */
    TocEntry[i].cdte_track = i + 1;
    TocEntry[i].cdte_format = CDROM_LBA;
    ::ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]);
  }

  TocEntry[last].cdte_track = CDROM_LEADOUT;
  TocEntry[last].cdte_format = CDROM_LBA;
  ::ioctl(drive, CDROMREADTOCENTRY, &TocEntry[last]);
#endif

#if defined(__FreeBSD__)
  TocEntry[last].cdte_track_address = ntohl(TocEntry[last].cdte_track_address);
#endif

  for(uint i = 0; i < last; ++i) {
#if defined(__FreeBSD__)
    TocEntry[i].cdte_track_address = ntohl(TocEntry[i].cdte_track_address);
#endif
    list.append(TocEntry[i].cdte_track_address + CD_MSF_OFFSET);
  }

  list.append(TocEntry[0].cdte_track_address + CD_MSF_OFFSET);
  list.append(TocEntry[last].cdte_track_address + CD_MSF_OFFSET);

  // hey, these are track lengths! :P
  trackLengths_.clear();
  for(uint i = 0; i < last; ++i) {
    trackLengths_.append((TocEntry[i+1].cdte_track_address - TocEntry[i].cdte_track_address) / CD_FRAMES);
  }

  delete[] TocEntry;
  return list;
}

inline
ushort from2Byte(uchar* d) {
  return (d[0] << 8 & 0xFF00) | (d[1] & 0xFF);
}

#define SIZE 61
// mostly taken from kover and k3b
// licensed under GPL
FreeDBImporter::CDText FreeDBImporter::getCDText(const TQCString& drive_) {
  CDText cdtext;
#ifdef USE_CDTEXT
// only works for linux ATM
#if defined(__linux__)
  int drive = ::open(drive_.data(), O_RDONLY | O_NONBLOCK);
  CloseDrive closer(drive);
  if(drive < 0) {
    return cdtext;
  }

  cdrom_generic_command m_cmd;
  ::memset(&m_cmd, 0, sizeof(cdrom_generic_command));

  int dataLen;

  int format = 5;
  uint track = 0;
  uchar buffer[2048];

  m_cmd.cmd[0] = 0x43;
  m_cmd.cmd[1] = 0x0;
  m_cmd.cmd[2] = format & 0x0F;
  m_cmd.cmd[6] = track;
  m_cmd.cmd[8] = 2; // we only read the length first

  m_cmd.buffer = buffer;
  m_cmd.buflen = 2;
  m_cmd.data_direction = CGC_DATA_READ;

  if(ioctl(drive, CDROM_SEND_PACKET, &m_cmd) != 0) {
    myDebug() << "FreeDBImporter::getCDText() - access error" << endl;
    return cdtext;
  }

  dataLen = from2Byte(buffer) + 2;
  m_cmd.cmd[7] = 2048 >> 8;
  m_cmd.cmd[8] = 2048 & 0xFF;
  m_cmd.buflen = 2048;
  ::ioctl(drive, CDROM_SEND_PACKET, &m_cmd);
  dataLen = from2Byte(buffer) + 2;

  ::memset(buffer, 0, dataLen);

  m_cmd.cmd[7] = dataLen >> 8;
  m_cmd.cmd[8] = dataLen;
  m_cmd.buffer = buffer;
  m_cmd.buflen = dataLen;
  ::ioctl(drive, CDROM_SEND_PACKET, &m_cmd);

  bool rc = false;
  int buffer_size = (buffer[0] << 8) | buffer[1];
  buffer_size -= 2;

  char data[SIZE];
  short pos_data = 0;
  char old_block_no = 0xff;
  for(uchar* bufptr = buffer + 4; buffer_size >= 18; bufptr += 18, buffer_size -= 18) {
    char code = *bufptr;

    if((code & 0x80) != 0x80) {
      continue;
    }

    char block_no = *(bufptr + 3);
    if(block_no & 0x80) {
      myDebug() << "FreeDBImporter::readCDText() - double byte code not supported" << endl;
      continue;
    }
    block_no &= 0x70;

    if(block_no != old_block_no) {
      if(rc) {
        break;
      }
      pos_data = 0;
      old_block_no = block_no;
    }

    track = *(bufptr + 1);
    if(track & 0x80) {
      continue;
    }

    uchar* txtstr = bufptr + 4;

    int length = 11;
    while(length >= 0 && *(txtstr + length) == '\0') {
      --length;
    }

    ++length;
    if(length < 12) {
      ++length;
    }

    for(int j = 0; j < length; ++j) {
      char c = *(txtstr + j);
      if(c == '\0') {
        data[pos_data] = c;
        if(track == 0) {
          if(code == (char)0xFFFFFF80) {
            cdtext.title = TQString::fromUtf8(data);
          } else if(code == (char)0xFFFFFF81) {
            cdtext.artist = TQString::fromUtf8(data);
          } else if (code == (char)0xFFFFFF85) {
            cdtext.message = TQString::fromUtf8(data);
          }
        } else {
          if(code == (char)0xFFFFFF80) {
            if(cdtext.trackTitles.size() < track) {
              cdtext.trackTitles.resize(track);
            }
            cdtext.trackTitles[track-1] = TQString::fromUtf8(data);
          } else if(code == (char)0xFFFFFF81) {
            if(cdtext.trackArtists.size() < track) {
              cdtext.trackArtists.resize(track);
            }
            cdtext.trackArtists[track-1] = TQString::fromUtf8(data);
          }
        }
        rc = true;
        pos_data = 0;
        ++track;
      } else if(pos_data < (SIZE - 1)) {
        data[pos_data++] = c;
      }
    }
  }
  if(cdtext.trackTitles.size() != cdtext.trackArtists.size()) {
    size_t size = TQMAX(cdtext.trackTitles.size(), cdtext.trackArtists.size());
    cdtext.trackTitles.resize(size);
    cdtext.trackArtists.resize(size);
  }
#endif
#endif
  return cdtext;
}
#undef SIZE
