/*
 * PhotoCD Player plugin for VDR
 * pcd_func.c: Functions for handling PhotsCDs
 *
 * See the README file for copyright information and how to reach the author.
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id$
 */


#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "pcd_func.h"
#include "pcd_setup.h"

__u8 bcd_to_bin(__u8 bcd)
{
  return 10 * (bcd >> 4) + (bcd & 0x0F);
}

int bcdmsf_to_lba(cdrom_msf0 msf0)
{
  return bcd_to_bin(msf0.frame)
     + CD_FRAMES * ( bcd_to_bin(msf0.second)
        + CD_SECS * bcd_to_bin(msf0.minute) )
     - CD_MSF_OFFSET;
}

struct cdrom_msf0 lba_to_msf(int lba)
{
  struct cdrom_msf0 msf0;
  msf0.frame = lba % CD_FRAMES;
  lba /= CD_FRAMES;
  msf0.second = lba % CD_SECS;
  msf0.minute = lba / CD_SECS;
  return msf0;
}

void long_to_byte(__u8 *dest, __u32 *src, size_t n)
{
  while (n--)
     *dest++ = *src++;
}


// --- cPCD ------------------------------------------------------------------

cPCD *cPCD::getPCD(void)
{
  if (!pcdInstance)
     new cPCD;
  return pcdInstance;
}

cPCD::cPCD(void)
{
  cdrom = 0;
  pcdInstance = this;
  deviceName = strdup("/dev/cdrom");
}

cPCD::cPCD(const char *DeviceName)
{
  cdrom = 0;
  pcdInstance = this;
  deviceName = strdup(DeviceName);
}

cPCD::~cPCD()
{
  Close();
}

int cPCD::Command(int Cmd)
{
  int result = -1;
  int f;
  if ((f = open(deviceName, O_RDONLY | O_NONBLOCK)) > 0) {
     result = ioctl(f, Cmd, 0);
     close(f);
     }
  return result;
}

bool cPCD::DriveExists(void)
{
  return access(deviceName, F_OK) == 0;
}

bool cPCD::DiscOk(void)
{
  return Command(CDROM_DRIVE_STATUS) == CDS_DISC_OK;
}

void cPCD::Eject(void)
{
  if (pcdInstance)
     pcdInstance->Close();
  Command(CDROMEJECT);
}

void cPCD::Open(void)
{
  if (!cdrom) {
     cdrom = open(deviceName, O_RDONLY | O_NONBLOCK);
     SetDriveSpeed(PcdSetupData.DriveSpeed);
     }
  for (int i=0; i<INFO_SECTORS; i++) {
     __u8 buf[CD_XA21_DATASIZE];
     if (readSectorXA21(i, buf))
        long_to_byte((__u8*)&infoSector[i], (__u32*)buf, sizeof(struct raw_sector));
     else
        memset(&infoSector[i], 0x00, sizeof(struct raw_sector));
     }
  struct cdrom_msf0 addr[PCD_IMAGES];
  memcpy(addr,                    &(infoSector[ENTRY0_LBA].data[0]), SECTOR_DATA_SIZE);
  memcpy(addr+  SECTOR_DATA_SIZE, &(infoSector[ENTRY1_LBA].data[0]), SECTOR_DATA_SIZE);
  memcpy(addr+2*SECTOR_DATA_SIZE, &(infoSector[ENTRY2_LBA].data[0]), SECTOR_DATA_SIZE);
  memcpy(addr+3*SECTOR_DATA_SIZE, &(infoSector[ENTRY3_LBA].data[0]), SECTOR_DATA_SIZE);
  memcpy(addr+4*SECTOR_DATA_SIZE, &(infoSector[ENTRY4_LBA].data[0]), SECTOR_DATA_SIZE);
  memcpy(addr+5*SECTOR_DATA_SIZE, &(infoSector[ENTRY5_LBA].data[0]), SECTOR_DATA_SIZE);
  for (int i=0; i<PCD_IMAGES; i++)
     Image[i].lba = bcdmsf_to_lba(addr[i]);
  Image[GetImageCount()].lba = bcdmsf_to_lba((cdrom_msf0){
     infoSector[INFO_LBA].data[0x1b],
     infoSector[INFO_LBA].data[0x1c],
     infoSector[INFO_LBA].data[0x1d]
     });
}

void cPCD::Close(void)
{
  if (cdrom) {
     SetDriveSpeed(0);
     close(cdrom);
     }
  cdrom = 0;
  memset(infoSector, 0x00, INFO_SECTORS*sizeof(struct raw_sector));
}

void cPCD::SetDriveSpeed(int DriveSpeed)
{
  ioctl(cdrom, CDROM_SELECT_SPEED, DriveSpeed);
}

bool cPCD::VerifyPCD(void)
{
  return memcmp(PCD_ID, &(infoSector[INFO_LBA].data[0]), 8)==0;
}

int cPCD::GetImageCount(void)
{
  return 256*infoSector[INFO_LBA].data[0x17] + infoSector[INFO_LBA].data[0x18];
}

bool cPCD::readSectorRaw(int lba, void *sect)
{
  struct cdrom_msf0 msf0 = lba_to_msf(lba+CD_MSF_OFFSET);
  memcpy(sect, &msf0, sizeof(struct cdrom_msf0));
  if (ioctl(cdrom, CDROMREADRAW, sect) < 0)
     return false;
  return true;
}

bool cPCD::readSectorXA21(int lba, void *data)
{
  struct cdsector_xa21 sect;
  if (readSectorRaw(lba, &sect) == false)
     return false;
  memcpy(data, sect.data, sizeof(sect.data));
  return true;
}
