/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998  Andreas Mueller <mueller@daneb.ping.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 * $Log: PWSubChannel96.cc,v $
 * Revision 1.2  2000/12/17 10:51:22  andreasm
 * Default verbose level is now 2. Adaopted message levels to have finer
 * grained control about the amount of messages printed by cdrdao.
 * Added CD-TEXT writing support to the GenericMMCraw driver.
 * Fixed CD-TEXT cue sheet creating for the GenericMMC driver.
 *
 * Revision 1.1.1.1  2000/02/05 01:36:39  llanero
 * Uploaded cdrdao 1.1.3 with pre10 patch applied.
 *
 * Revision 1.6  1999/04/05 11:04:48  mueller
 * Added decoding of media catalog number and ISRC code.
 *
 * Revision 1.5  1999/03/27 20:58:55  mueller
 * Added various access functions.
 *
 * Revision 1.4  1998/09/27 19:19:39  mueller
 * Added retrieval of control nibbles for track with 'analyzeTrack()'.
 *
 * Revision 1.3  1998/09/06 13:34:22  mueller
 * Use 'message()' for printing messages.
 *
 * Revision 1.2  1998/08/30 19:10:32  mueller
 * Added handling of Catalog Number and ISRC codes.
 *
 * Revision 1.1  1998/08/29 21:32:27  mueller
 * Initial revision
 *
 *
 */

#include "PWSubChannel96.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "util.h"

PWSubChannel96::PWSubChannel96()
{
  memset(data_, 0, 96);
  type_ = QMODE1DATA;
}

PWSubChannel96::PWSubChannel96(unsigned char *buf)
{
  init(buf);
}

void PWSubChannel96::init(unsigned char *buf)
{
  memcpy(data_, buf, 96);

  switch (getChannelByte(Q_CHAN, 0) & 0x0f) {
  case 1:
    type_ = QMODE1DATA;
    break;

  case 2:
    type_ = QMODE2;
    break;

  case 3:
    type_ = QMODE3;
    break;

  case 5:
    type_ = QMODE5TOC;
    break;

  default:
    type_ = QMODE_ILLEGAL;
    break;
  }
}

PWSubChannel96::~PWSubChannel96()
{
}

SubChannel *PWSubChannel96::makeSubChannel(Type t)
{
  PWSubChannel96 *chan = new PWSubChannel96;

  chan->type_ = t;

  switch (t) {
  case QMODE1TOC:
  case QMODE1DATA:
    chan->setChannelByte(Q_CHAN, 0, 0x01);
    break;
    
  case QMODE2:
    chan->setChannelByte(Q_CHAN, 0, 0x02);
    break;

  case QMODE3:
    chan->setChannelByte(Q_CHAN, 0, 0x03);
    break;

  case QMODE5TOC:
    chan->setChannelByte(Q_CHAN, 0, 0x05);
    break;

  case QMODE_ILLEGAL:
    chan->setChannelByte(Q_CHAN, 0, 0x00);
    break;
  }

  return chan;
}

SubChannel *PWSubChannel96::makeSubChannel(unsigned char *buf)
{
  PWSubChannel96 *chan = new PWSubChannel96(buf);

  return chan;
}

void PWSubChannel96::setChannelByte(Channel chan, int byteNr,
				    unsigned char value)
{
  assert(byteNr >= 0 && byteNr < 12);

  register unsigned char setMask = 1 << chan;
  register unsigned char clearMask = ~setMask;

  register unsigned char *p = data_ + (byteNr * 8);
  register int i;

  for (i = 0; i < 8; i++) {
    if (value & 0x80)
      *p |= setMask;
    else
      *p &= clearMask;

    p++;
    value <<= 1;
  }
}

unsigned char PWSubChannel96::getChannelByte(Channel chan, int byteNr) const
{
  assert(byteNr >= 0 && byteNr < 12);

  register unsigned char testMask = 1 << chan;
  register const unsigned char *p = data_ + (byteNr * 8);
  register unsigned char val = 0;
  register int i;
  
  for (i = 0; i < 8; i++) {
    val <<= 1;
    if ((*p) & testMask)
      val |= 0x01;

    p++;
  }

  return val;
}

const unsigned char *PWSubChannel96::data() const
{
  return data_;
}

long PWSubChannel96::dataLength() const
{
  return 96;
}

// calculate the crc over Q sub channel bytes 0-9 and stores it in byte 10,11
void PWSubChannel96::calcCrc()
{
  register unsigned short crc = 0;
  register int i;

  for (i = 0; i < 10; i++) {
    register unsigned char data = getChannelByte(Q_CHAN, i);
    crc = crctab[(crc >> 8) ^ data] ^ (crc << 8);
  }

  crc = ~crc;

  setChannelByte(Q_CHAN, 10, crc >> 8);
  setChannelByte(Q_CHAN, 11, crc);
}

int PWSubChannel96::checkCrc() const
{
  register unsigned short crc = 0;
  register int i;

  if (!crcValid_) {
    return 1;
  }

  for (i = 0; i < 10; i++) {
    register unsigned char data = getChannelByte(Q_CHAN, i);
    crc = crctab[(crc >> 8) ^ data] ^ (crc << 8);
  }

  crc = ~crc;

  if (getChannelByte(Q_CHAN, 10) == (crc >> 8) &&
      getChannelByte(Q_CHAN, 11) == (crc & 0xff))
    return 1;
  else
    return 0;
}


// sets P channel bit
void PWSubChannel96::pChannel(int f)
{
  register int i;

  if (f != 0) {
    for (i = 0; i < 96; i++)
      data_[i] |= 0x80;
  }
  else {
    for (i = 0; i < 96; i++)
      data_[i] &= 0x7f;
  }    
}

// returns Q type
SubChannel::Type PWSubChannel96::type() const
{
  return type_;
}


// set Q type
void PWSubChannel96::type(unsigned char type)
{
  switch (type & 0x0f) {
  case 1:
    type_ = QMODE1DATA;
    break;

  case 2:
    type_ = QMODE2;
    break;

  case 3:
    type_ = QMODE3;
    break;

  case 5:
    type_ = QMODE5TOC;
    break;

  default:
    type_ = QMODE_ILLEGAL;
    break;
  }

  register unsigned char val = getChannelByte(Q_CHAN, 0);

  val &= 0xf0;
  val |= type & 0x0f;

  setChannelByte(Q_CHAN, 0, val);
}

// function for setting various Q sub channel fields

void PWSubChannel96::ctl(int c)
{
  assert((c & 0x0f) == 0);

  register unsigned char val = getChannelByte(Q_CHAN, 0);

  val &= 0x0f;
  val |= c & 0xf0;

  setChannelByte(Q_CHAN, 0, val);
}

unsigned char PWSubChannel96::ctl() const
{
  register unsigned char val = getChannelByte(Q_CHAN, 0);

  return val >> 4;
}

void PWSubChannel96::trackNr(int t)
{
  assert(type_ == QMODE1DATA);
  setChannelByte(Q_CHAN, 1, bcd(t));
}

int PWSubChannel96::trackNr() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(getChannelByte(Q_CHAN, 1));
}

void PWSubChannel96::indexNr(int i)
{
  assert(type_ == QMODE1DATA);
  setChannelByte(Q_CHAN, 2, bcd(i));
}

int PWSubChannel96::indexNr() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(getChannelByte(Q_CHAN, 2));
}

void PWSubChannel96::point(int p)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 2, bcd(p));
}

void PWSubChannel96::min(int m)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 3, bcd(m));
}

int PWSubChannel96::min() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(getChannelByte(Q_CHAN, 3));
}

void PWSubChannel96::sec(int s)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 4, bcd(s));
}

int PWSubChannel96::sec() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(getChannelByte(Q_CHAN, 4));
}

void PWSubChannel96::frame(int f)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 5, bcd(f));
}

int PWSubChannel96::frame() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(getChannelByte(Q_CHAN, 5));
}

void PWSubChannel96::zero(int z)
{
  assert(type_ == QMODE5TOC);

  setChannelByte(Q_CHAN, 6, bcd(z));
}

void PWSubChannel96::amin(int am)
{
  assert(type_ == QMODE1DATA);
  setChannelByte(Q_CHAN, 7, bcd(am));
}

int PWSubChannel96::amin() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(getChannelByte(Q_CHAN, 7));
}

void PWSubChannel96::asec(int as)
{
  assert(type_ == QMODE1DATA);
  setChannelByte(Q_CHAN, 8, bcd(as));
}

int PWSubChannel96::asec() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(getChannelByte(Q_CHAN, 8));
}

void PWSubChannel96::aframe(int af)
{
  assert(type_ == QMODE1DATA || type_ == QMODE2 || type_ == QMODE3);
  setChannelByte(Q_CHAN, 9, bcd(af));
}

int PWSubChannel96::aframe() const
{
  assert(type_ == QMODE1DATA || type_ == QMODE2 || type_ == QMODE3);
  return bcd2int(getChannelByte(Q_CHAN, 9));
}

void PWSubChannel96::pmin(int pm)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 7, bcd(pm));
}

void PWSubChannel96::psec(int ps)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 8, bcd(ps));
}

void PWSubChannel96::pframe(int pf)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  setChannelByte(Q_CHAN, 9, bcd(pf));
}

void PWSubChannel96::catalog(char n1, char n2, char n3, char n4, char n5,
			     char n6, char n7, char n8, char n9, char n10,
			     char n11, char n12, char n13)
{
  assert(type_ == QMODE2);

  unsigned char buf[8];
  int i;
  
  encodeCatalogNumber(buf, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12,
		      n13);
  buf[7] = 0;

  for (i = 0; i < 8; i++)
    setChannelByte(Q_CHAN, i + 1, buf[i]);
}

const char *PWSubChannel96::catalog() const
{
  static char buf[14];
  unsigned char in[7];
  int i;

  for (i = 0; i < 7; i++)
    in[i] = getChannelByte(Q_CHAN, i + 1);

  decodeCatalogNumber(in, &buf[0], &buf[1], &buf[2], &buf[3], &buf[4],
		      &buf[5], &buf[6], &buf[7], &buf[8], &buf[9], &buf[10],
		      &buf[11], &buf[12]);

  buf[13] = 0;

  return buf;
}

void PWSubChannel96::isrc(char c1, char c2, char o1, char o2, char o3,
			  char y1, char y2, char s1, char s2, char s3,
			  char s4, char s5)
{
  assert(type_ == QMODE3);

  unsigned char buf[8];
  int i;

  encodeIsrcCode(buf, c1, c2, o1, o2, o3, y1, y2, s1, s2, s3, s4, s5);

  for (i = 0; i < 8; i++)
    setChannelByte(Q_CHAN, i + 1, buf[i]);
}

const char *PWSubChannel96::isrc() const
{
  static char buf[13];
  unsigned char in[8];
  int i;

  assert(type_ == QMODE3);

  for (i = 0; i < 8; i++)
    in[i] = getChannelByte(Q_CHAN, i + 1);

  decodeIsrcCode(in, &buf[0], &buf[1], &buf[2], &buf[3], &buf[4],
		 &buf[5], &buf[6], &buf[7], &buf[8], &buf[9], &buf[10], 
		 &buf[11]);
  buf[12] = 0;

  return buf;
}


void PWSubChannel96::print() const
{
  if (type_ != QMODE_ILLEGAL) 
    message(0, "P:%02x ", getChannelByte(P_CHAN, 0));

  switch (type_) {
  case QMODE1TOC:
  case QMODE1DATA:
  case QMODE5TOC:
    message(0, "Q: (%02x) %02x,%02x %02x:%02x:%02x %02x %02x:%02x:%02x ", 
	   getChannelByte(Q_CHAN, 0), getChannelByte(Q_CHAN, 1),
	   getChannelByte(Q_CHAN, 2), getChannelByte(Q_CHAN, 3),
	   getChannelByte(Q_CHAN, 4), getChannelByte(Q_CHAN, 5),
	   getChannelByte(Q_CHAN, 6), getChannelByte(Q_CHAN, 7),
	   getChannelByte(Q_CHAN, 8), getChannelByte(Q_CHAN, 9));
    break;
  case QMODE2:
    message(0, "Q: (%02x) MCN: %s      %02x ", getChannelByte(Q_CHAN, 0),
	    catalog(), getChannelByte(Q_CHAN, 9));
    break;
  case QMODE3:
    message(0, "Q: (%02x) ISRC: %s      %02x ", getChannelByte(Q_CHAN, 0),
	    isrc(), getChannelByte(Q_CHAN, 9));
    break;
  case QMODE_ILLEGAL:
    message(0, "INVALID QMODE: %02x", getChannelByte(Q_CHAN, 0));
    break;
  }

  if (type_ != QMODE_ILLEGAL) 
    message(0, "%04x %d", 
	    (getChannelByte(Q_CHAN, 10) << 8) | getChannelByte(Q_CHAN, 11),
	    checkCrc());
}

// sets R-W channel bits from 72 byte data buffer, used for CD-TEXT
void PWSubChannel96::setRawRWdata(const unsigned char *data)
{
  int i;

  for (i = 0; i < 96; i += 4) {
    data_[i]     |= (data[0] >> 2) & 0x3f;
    data_[i + 1] |= ((data[0] << 4) & 0x30) | ((data[1] >> 4) & 0x0f);
    data_[i + 2] |= ((data[1] << 2) & 0x3c) | ((data[2] >> 6) & 0x03);
    data_[i + 3] |= data[2] & 0x3f;
    
    data += 3;
  }
}

// concatenates the R-W channel bits and writes them to provided 72 byte buffer
void PWSubChannel96::getRawRWdata(unsigned char *data) const
{
  int i;

  for (i = 0; i < 96; i += 4) {
    data[0] = ((data_[i] << 2) & 0xfc)     | ((data_[i + 1] >> 4) & 0x03);
    data[1] = ((data_[i + 1] << 4) & 0xf0) | ((data_[i + 2] >> 2) & 0x0f);
    data[2] = ((data_[i + 2] << 6) & 0xc0) | (data_[i + 3] & 0x3f);
    
    data += 3;
  }
}
