/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2002  Andreas Mueller <andreas@daneb.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.
 */

#include "PQSubChannel16.h"

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

#include "util.h"

PQSubChannel16::PQSubChannel16()
{
  memset(data_, 0, 16);
  type_ = QMODE1DATA;
}


PQSubChannel16::~PQSubChannel16()
{
}

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

  chan->type_ = t;

  switch (t) {
  case QMODE1TOC:
  case QMODE1DATA:
    chan->data_[0] = 0x01;
    break;
    
  case QMODE2:
    chan->data_[0] = 0x02;
    break;

  case QMODE3:
    chan->data_[0] = 0x03;
    break;

  case QMODE5TOC:
    chan->data_[0] = 0x05;
    break;

  case QMODE_ILLEGAL:
    chan->data_[0] = 0x00;
    break;
  }

  return chan;
}

void PQSubChannel16::init(unsigned char *buf)
{
  memcpy(data_, buf, 16);

  switch (data_[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;
  }
}

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

  chan->init(buf);

  return chan;
}


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

long PQSubChannel16::dataLength() const
{
  return 16;
}

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

  for (i = 0; i < 10; i++) {
    crc = crctab[(crc >> 8) ^ data_[i]] ^ (crc << 8);
  }

  data_[10] = crc >> 8;
  data_[11] = crc;
}

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

  if (!crcValid_) {
    return 1;
  }

  for (i = 0; i < 10; i++) {
    crc = crctab[(crc >> 8) ^ data_[i]] ^ (crc << 8);
  }

  if (data_[10] == (crc >> 8) && data_[11] == (crc & 0xff))
    return 1;
  else
    return 0;
}

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

// set Q type
void PQSubChannel16::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;
  }
  
  data_[0] &= 0xf0;
  data_[0] |= type & 0x0f;
}

// sets P channel bit
void PQSubChannel16::pChannel(int f)
{
  data_[15] = f != 0 ? 0x80 : 0;
}


// function for setting various Q sub channel fields

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

  data_[0] &= 0x0f;
  data_[0] |= c & 0xf0;
}

unsigned char PQSubChannel16::ctl() const
{
  return data_[0] >> 4;
}

void PQSubChannel16::trackNr(int t)
{
  assert(type_ == QMODE1DATA);
  data_[1] = bcd(t);
}

int PQSubChannel16::trackNr() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(data_[1]);
}

void PQSubChannel16::indexNr(int i)
{
  assert(type_ == QMODE1DATA);
  data_[2] = bcd(i);
}

int PQSubChannel16::indexNr() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(data_[2]);
}

void PQSubChannel16::point(int p)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  data_[2] = bcd(p);
}

void PQSubChannel16::min(int m)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  data_[3] = bcd(m);
}

int PQSubChannel16::min() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(data_[3]);
}

void PQSubChannel16::sec(int s)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  data_[4] = bcd(s);
}

int PQSubChannel16::sec() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(data_[4]);
}

void PQSubChannel16::frame(int f)
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  data_[5] = bcd(f);
}

int PQSubChannel16::frame() const
{
  assert(type_ == QMODE1TOC || type_ == QMODE1DATA || type_ == QMODE5TOC);
  return bcd2int(data_[5]);
}

void PQSubChannel16::zero(int z)
{
  assert(type_ == QMODE5TOC);
  data_[6] = bcd(z);
}

void PQSubChannel16::amin(int am)
{
  assert(type_ == QMODE1DATA);
  data_[7] = bcd(am);
}

int PQSubChannel16::amin() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(data_[7]);
}

void PQSubChannel16::asec(int as)
{
  assert(type_ == QMODE1DATA);
  data_[8] = bcd(as);
}

int PQSubChannel16::asec() const
{
  assert(type_ == QMODE1DATA);
  return bcd2int(data_[8]);
}

void PQSubChannel16::aframe(int af)
{
  assert(type_ == QMODE1DATA || type_ == QMODE2 || type_ == QMODE3);
  data_[9] = bcd(af);
}

int PQSubChannel16::aframe() const
{
  assert(type_ == QMODE1DATA || type_ == QMODE2 || type_ == QMODE3);
  return bcd2int(data_[9]);
}

void PQSubChannel16::pmin(int pm)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  data_[7] = bcd(pm);
}

void PQSubChannel16::psec(int ps)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  data_[8] = bcd(ps);
}

void PQSubChannel16::pframe(int pf)
{
  assert(type_ == QMODE1TOC || type_ == QMODE5TOC);
  data_[9] = bcd(pf);
}

void PQSubChannel16::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);

  encodeCatalogNumber(data_ + 1, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10,
		      n11, n12, n13);
  data_[8] = 0;
}

const char *PQSubChannel16::catalog() const
{
  static char buf[14];

  assert(type_ == QMODE2);

  decodeCatalogNumber(data_ + 1, &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 PQSubChannel16::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);

  encodeIsrcCode(data_ + 1, c1, c2, o1, o2, o3, y1, y2, s1, s2, s3, s4, s5);
}


const char *PQSubChannel16::isrc() const
{
  static char buf[13];

  assert(type_ == QMODE3);

  decodeIsrcCode(data_ + 1, &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 PQSubChannel16::print() const
{
  if (type_ != QMODE_ILLEGAL) 
    message(0, "P:%02x ", data_[15]);

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

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

int PQSubChannel16::checkConsistency()
{
  switch (type_) {
  case QMODE1DATA:
    if (!isBcd(data_[3]) || !isBcd(data_[4]) || !isBcd(data_[5]) ||
	!isBcd(data_[7]) || !isBcd(data_[8]) || !isBcd(data_[9]))
      return 0;
    break;

  case QMODE1TOC:
  case QMODE2:
  case QMODE3:
  case QMODE5TOC:
  case QMODE_ILLEGAL:
    // no checks, yet
    break;
  }

  return SubChannel::checkConsistency();
}
