/*  toc.c
 *  Copyright (C) 2002-2005  PCSX2 Team
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  PCSX2 members can be contacted through their website at www.pcsx2.net.
 */


#include <stddef.h> // NULL
#include <sys/types.h> // off64_t

#ifndef __LINUX__
#ifdef __linux__
#define __LINUX__
#endif /* __linux__ */
#endif /* No __LINUX__ */

#define CDVDdefs
#include "PS2Edefs.h"

#include "logfile.h"
#include "convert.h"
#include "isofile.h"
#include "actualfile.h"

#include "toc.h"


// PCSX2's .toc file format:
// 1 unsigned char - CDVD_TYPE_????
// 1 tocTN
// As many tocTDs as it takes.


extern void IsoInitTOC(struct IsoFile *isofile) {
  int i;
  off64_t sectorsize;

  if(isofile == NULL)  return;

#ifdef VERBOSE_FUNCTION_TOC
  PrintLog("CDVDiso TOC: IsoInitTOC()");
#endif /* VERBOSE_FUNCTION_TOC */

  if(isofile->multi > 0) {
    sectorsize = isofile->multisectorend[isofile->multiend];
  } else {
    sectorsize = isofile->filesectorsize;
  } // ENDIF- Establish largest sector from multifile? (or single file?)

  for(i = 0; i < 2048; i++)  isofile->toc[i] = 0;
  switch(isofile->cdvdtype) {
    case CDVD_TYPE_DVDV:
    case CDVD_TYPE_PS2DVD:
      if((isofile->filesectorsize > (2048*1024)) ||
         (isofile->multi > 0)) {
        isofile->toc[0] = 0x24; // Dual-Sided DVD (?)
        isofile->toc[4] = 0x41;
        isofile->toc[5] = 0x95;
      } else {
        isofile->toc[0] = 0x04; // Single-Sided DVD (?)
        isofile->toc[4] = 0x86;
        isofile->toc[5] = 0x72;
      } // ENDIF- Too many sectors for a single-layered disc?

      isofile->toc[1] = 0x02;
      isofile->toc[2] = 0xF2;
      isofile->toc[3] = 0x00;

      isofile->toc[16] = 0x00;
      isofile->toc[17] = 0x03;
      isofile->toc[18] = 0x00;
      isofile->toc[19] = 0x00;
      return; // DVD's don't have tracks. Might track multisession later...
      break;

    case CDVD_TYPE_PS2CD:
    case CDVD_TYPE_PSCD:
      isofile->toc[0] = 0x41;
      break;
    case CDVD_TYPE_CDDA:
      isofile->toc[0] = 0x01;
      break;
    default:
      break;
  } // ENDSWITCH isofile->cdvdtype - Which TOC for which type?

  // CD Details here... (tracks and stuff)
  isofile->toc[2] = 0xA0;
  isofile->toc[7] = 0x01; // Starting Track No.
  isofile->toc[12] = 0xA1;
  isofile->toc[17] = 0x01; // Ending Track No.

  isofile->toc[22] = 0xA2;
  LBAtoMSF(sectorsize, &isofile->toc[27]);
  isofile->toc[27] = HEXTOBCD(isofile->toc[27]);
  isofile->toc[28] = HEXTOBCD(isofile->toc[28]);
  isofile->toc[29] = HEXTOBCD(isofile->toc[29]);

  isofile->toc[40] = 0x02; // YellowBook? Data Mode?
  isofile->toc[42] = 0x01; // Track No.
  LBAtoMSF(0, &isofile->toc[47]);
  isofile->toc[47] = HEXTOBCD(isofile->toc[47]);
  isofile->toc[48] = HEXTOBCD(isofile->toc[48]);
  isofile->toc[49] = HEXTOBCD(isofile->toc[49]);
} // END IsoInitTOC()


extern void IsoAddTNToTOC(struct IsoFile *isofile, struct tocTN toctn) {
  if(isofile == NULL) return;

#ifdef VERBOSE_FUNCTION_TOC
  PrintLog("CDVDiso TOC: IsoAddTNToTOC()");
#endif /* VERBOSE_FUNCTION_TOC */

  isofile->toc[7] = HEXTOBCD(toctn.strack);
  isofile->toc[17] = HEXTOBCD(toctn.etrack);
  return;
} // END IsoAddTNToTOC()


extern void IsoAddTDToTOC(struct IsoFile *isofile,
                         unsigned char track,
                         struct tocTD toctd) {
  int temptrack;
  int position;

#ifdef VERBOSE_FUNCTION_TOC
  PrintLog("CDVDiso TOC: IsoAddTNToTOC(%u)", track);
#endif /* VERBOSE_FUNCTION_TOC */

  if(isofile == NULL)  return;

  temptrack = track;
  if(temptrack == 0xAA)  temptrack = 0;
  if(temptrack > 99)  return; // Only up to 99 tracks allowed.

  if(temptrack == 0) {
    LBAtoMSF(toctd.lsn, &isofile->toc[27]);
    isofile->toc[27] = HEXTOBCD(isofile->toc[27]);
    isofile->toc[28] = HEXTOBCD(isofile->toc[28]);
    isofile->toc[29] = HEXTOBCD(isofile->toc[29]);

  } else {
    position = temptrack * 10;
    position += 30;
    isofile->toc[position] = toctd.type;
    isofile->toc[position + 2] = HEXTOBCD(temptrack);
    LBAtoMSF(toctd.lsn, &isofile->toc[position + 7]);
    isofile->toc[position + 7] = HEXTOBCD(isofile->toc[position + 7]);
    isofile->toc[position + 8] = HEXTOBCD(isofile->toc[position + 8]);
    isofile->toc[position + 9] = HEXTOBCD(isofile->toc[position + 9]);
  } // ENDIF- Is this a lead-out? (or an actual track?)
} // END IsoAddTDToTOC()


extern int IsoLoadTOC(struct IsoFile *isofile) {
  char tocext[] = ".toc\0";
  char tocheader[5];
  ACTUALHANDLE tochandle;
  char tocname[256];
  int i;
  int j;
  int retval;
  unsigned char cdvdtype;
  struct tocTN toctn;
  struct tocTD toctd;

  if(isofile == NULL)  return(-1);

  i = 0;
  while((i < 256) && (isofile->name[i] != 0)) {
    tocname[i] = isofile->name[i];
    i++;
  } // ENDWHILE- Copying the data name to the toc name
  j = 0;
  while((i < 256) && (tocext[j] != 0)) {
    tocname[i] = tocext[j];
    i++;
    j++;
  } // ENDWHILE- Append ".toc" to end of name
  tocname[i] = 0; // And 0-terminate

  tochandle = ActualFileOpenForRead(tocname);
  if(tochandle == ACTUALHANDLENULL)  return(-1);

  retval = ActualFileRead(tochandle, 4, tocheader);
  if(retval < 4) {
    ActualFileClose(tochandle);
    tochandle = ACTUALHANDLENULL;
    return(-1);
  } // ENDIF- Trouble reading the 'toc' file?

  if((tocheader[0] != 'T') ||
     (tocheader[1] != 'O') ||
     (tocheader[2] != 'C') ||
     (tocheader[3] != '1')) {
    ActualFileClose(tochandle);
    tochandle = ACTUALHANDLENULL;
    return(-1);
  } // ENDIF- Not a 'toc' file after all?

#ifdef VERBOSE_FUNCTION_TOC
  PrintLog("CDVDiso TOC: IsoLoadTOC(%s)", tocname);
#endif /* VERBOSE_FUNCTION_TOC */

  retval = ActualFileRead(tochandle, 1, (char *) &cdvdtype);
  if(retval < 1) {
    ActualFileClose(tochandle);
    tochandle = ACTUALHANDLENULL;
    return(-1);
  } // ENDIF- Trouble reading the 'toc' file?

  isofile->cdvdtype = cdvdtype;
  IsoInitTOC(isofile);

  if((cdvdtype != CDVD_TYPE_PS2DVD) && (cdvdtype != CDVD_TYPE_DVDV)) {
    retval = ActualFileRead(tochandle, sizeof(struct tocTN), (char *) &toctn);
    if(retval < sizeof(struct tocTN)) {
      ActualFileClose(tochandle);
      tochandle = ACTUALHANDLENULL;
      return(-1);
    } // ENDIF- Trouble reading the 'toc' file?

    if((toctn.strack > 99) || (toctn.etrack > 99)) {
      ActualFileClose(tochandle);
      tochandle = ACTUALHANDLENULL;
      return(-1);
    } // ENDIF- Track numbers out of range?
#ifdef VERBOSE_FUNCTION_TOC
    PrintLog("CDVDiso TOC:   Start Track %u   End Track %u",
             toctn.strack, toctn.etrack);
#endif /* VERBOSE_FUNCTION_TOC */
    IsoAddTNToTOC(isofile, toctn);

    retval = ActualFileRead(tochandle, sizeof(struct tocTD), (char *) &toctd);
    if(retval < sizeof(struct tocTD)) {
      ActualFileClose(tochandle);
      tochandle = ACTUALHANDLENULL;
      return(-1);
    } // ENDIF- Trouble reading the 'toc' file?

    if(toctd.type != 0) {
      ActualFileClose(tochandle);
      tochandle = ACTUALHANDLENULL;
      return(-1);
    } // ENDIF- Track numbers out of range?
#ifdef VERBOSE_FUNCTION_TOC
    PrintLog("CDVDiso TOC:   Total Sectors: %lu", toctd.lsn);
#endif /* VERBOSE_FUNCTION_TOC */
    IsoAddTDToTOC(isofile, 0xAA, toctd);

    for(i = toctn.strack; i <= toctn.etrack; i++) {

      retval = ActualFileRead(tochandle, sizeof(struct tocTD), (char *) &toctd);
      if(retval < sizeof(struct tocTD)) {
        ActualFileClose(tochandle);
        tochandle = ACTUALHANDLENULL;
        return(-1);
      } // ENDIF- Trouble reading the 'toc' file?
#ifdef VERBOSE_FUNCTION_TOC
      PrintLog("CDVDiso TOC:   Track %u  Type %u  Sector Start: %lu",
               i, toctd.type, toctd.lsn);
#endif /* VERBOSE_FUNCTION_TOC */
      IsoAddTDToTOC(isofile, i, toctd);
    } // NEXT i- read in each track
  } // ENDIF- Not a DVD? (Then read in CD track data)
  ActualFileClose(tochandle);
  tochandle = ACTUALHANDLENULL;

  return(0);
} // END IsoLoadTOC()


extern int IsoSaveTOC(struct IsoFile *isofile) {
  char tocext[] = ".toc\0";
  char tocheader[] = "TOC1\0";
  ACTUALHANDLE tochandle;
  char tocname[256];
  int i;
  int j;
  int retval;
  unsigned char cdvdtype;
  struct tocTN toctn;
  struct tocTD toctd;
  char temptime[3];

  if(isofile == NULL)  return(-1);

  i = 0;
  while((i < 256) && (isofile->name[i] != 0)) {
    tocname[i] = isofile->name[i];
    i++;
  } // ENDWHILE- Copying the data name to the toc name
  j = 0;
  while((i < 256) && (tocext[j] != 0)) {
    tocname[i] = tocext[j];
    i++;
    j++;
  } // ENDWHILE- Append ".toc" to end of name
  tocname[i] = 0; // And 0-terminate

  ActualFileDelete(tocname);
  tochandle = ActualFileOpenForWrite(tocname);
  if(tochandle == ACTUALHANDLENULL)  return(-1);

  retval = ActualFileWrite(tochandle, 4, tocheader);
  if(retval < 4) {
    ActualFileClose(tochandle);
    tochandle = ACTUALHANDLENULL;
    ActualFileDelete(tocname);
    return(-1);
  } // ENDIF- Trouble writing to the 'toc' file?

  cdvdtype = isofile->cdvdtype;
  ActualFileWrite(tochandle, 1, (char *) &cdvdtype);

  if((cdvdtype != CDVD_TYPE_PS2DVD) && (cdvdtype != CDVD_TYPE_DVDV)) {
    toctn.strack = BCDTOHEX(isofile->toc[7]);
    toctn.etrack = BCDTOHEX(isofile->toc[17]);
    ActualFileWrite(tochandle, sizeof(struct tocTN), (char *) &toctn);

    // Leadout Data
    toctd.type = 0;
    temptime[0] = BCDTOHEX(isofile->toc[27]);
    temptime[1] = BCDTOHEX(isofile->toc[28]);
    temptime[2] = BCDTOHEX(isofile->toc[29]);
    toctd.lsn = MSFtoLBA(temptime);
    ActualFileWrite(tochandle, sizeof(struct tocTD), (char *) &toctd);

    for(i = toctn.strack; i <= toctn.etrack; i++) {
      j = i * 10 + 30;
      toctd.type = isofile->toc[j];
      temptime[0] = BCDTOHEX(isofile->toc[j + 7]);
      temptime[1] = BCDTOHEX(isofile->toc[j + 8]);
      temptime[2] = BCDTOHEX(isofile->toc[j + 9]);
      toctd.lsn = MSFtoLBA(temptime);
      ActualFileWrite(tochandle, sizeof(struct tocTD), (char *) &toctd);
    } // NEXT i- write out each track
  } // ENDIF- Not a DVD? (Then output CD track data)
  ActualFileClose(tochandle);
  tochandle = ACTUALHANDLENULL;

  return(0);
} // END IsoSaveTOC()
