/* Mednafen - Multi-system Emulator
 *
 * 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
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <cdio/cdio.h>
#include <vorbis/vorbisfile.h>
#include <sndfile.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "mednafen.h"
#include "endian.h"
#include "general.h"
#include "cdromif.h"
#include "cdromfile.h"

static char *UnQuotify(char *src, char *dest)
{
 bool in_quote = 0;
 bool already_normal = 0;

 while(*src)
 {
  if(*src == ' ' || *src == '\t')
  {
   if(!in_quote)
   {
    if(already_normal)
     break;
    else
    {
     src++;
     continue;
    }
   }
  }

  if(*src == '"')
  {
   if(in_quote)
   {
    src++;
    break;
   }
   else
    in_quote = 1;
  }
  else
  {
   *dest = *src;
   already_normal = 1;
   dest++;
  }
  src++;
 }

 *dest = 0;
 return(src);
}

static uint32 GetSectorCount(CDRFILE_TRACK_INFO *track)
{
 if(track->Format == TRACK_FORMAT_DATA)
 {
  struct stat stat_buf;

  fstat(fileno(track->fp), &stat_buf);

  if(track->IsData2352)
   return(stat_buf.st_size / 2352);
  else
   return(stat_buf.st_size / 2048);
 }
 else if(track->Format == TRACK_FORMAT_AUDIO)
 {
  if(track->sf)
   return(track->sfinfo.frames / 588);
  else if(track->ovfile.datasource)
   return(ov_pcm_total( &track->ovfile, -1) / 588);
  else
  {
   struct stat stat_buf;
   fstat(fileno(track->fp), &stat_buf);
   return(stat_buf.st_size / 2352);
  }
 }
 
 return(0);
}

CDRFile *cdrfile_open(const char *path)
{
 CDRFile *ret = (CDRFile *)calloc(1, sizeof(CDRFile));
 struct stat stat_buf;

 if(path == NULL || stat(path, &stat_buf) || !S_ISREG(stat_buf.st_mode))
 {
  CdIo *p_cdio;
  char **devices;
  char **parseit;
  cdio_init();

  GetFileBase("cdrom");

  devices = cdio_get_devices(DRIVER_DEVICE);
  parseit = devices;
  if(parseit)
  {
   MDFN_printf(_("Connected physical devices:\n"));
   MDFN_indent(1);
   while(*parseit)
   {
    MDFN_printf("%s\n", *parseit);
    parseit++;
   }
   MDFN_indent(-1);
  }
  if(!parseit || parseit == devices)
  {
   MDFN_PrintError(_("No CDROM drives detected(or no disc present)."));
   if(devices)
    cdio_free_device_list(devices);
   free(ret);
   return(NULL);
  }
  p_cdio = cdio_open_cd(path); //, DRIVER_UNKNOWN); //NULL, DRIVER_UNKNOWN);

  if(devices)
   cdio_free_device_list(devices);

  if(!p_cdio) 
  {
   free(ret);
   return(NULL);
  }
  ret->p_cdio = p_cdio;
  return(ret);
 }
 else
 {
  FILE *fp = fopen(path, "rb");
  if(!fp)
  {
   free(ret);
   MDFN_PrintError("Error opening CUE sheet \"%s\": %s\n", path, strerror(errno));
   return(NULL);
  }
  GetFileBase(path);

  char linebuf[512];
  int32 active_track = -1;
  CDRFILE_TRACK_INFO TmpTrack;
  memset(&TmpTrack, 0, sizeof(TmpTrack));

  while(fgets(linebuf, 512, fp) > 0)
  {
   char cmdbuf[512], args[512], arg1[512], arg2[512];
   sscanf(linebuf, "%s %[^\r\n]", cmdbuf, args);
   
   UnQuotify(UnQuotify(args, arg1), arg2);
   if(!strcasecmp(cmdbuf, "FILE"))
   {
    if(active_track >= 0)
    {
     memcpy(&ret->Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
     memset(&TmpTrack, 0, sizeof(TmpTrack));
     active_track = -1;
    }
    std::string efn = MDFN_MakeFName(MDFNMKF_AUX, 0, arg1);
    if(NULL == (TmpTrack.fp = fopen(efn.c_str(), "rb")))
    {
     MDFN_printf(_("Could not open referenced file \"%s\": %s\n"), efn.c_str(), strerror(errno));
     free(ret);
     return(0);
    }
    TmpTrack.FirstFileInstance = 1;
    if(!strcasecmp(arg2, "BINARY"))
    {
     //TmpTrack.Format = TRACK_FORMAT_DATA;
     //struct stat stat_buf;
     //fstat(fileno(TmpTrack.fp), &stat_buf);
     //TmpTrack.sectors = stat_buf.st_size; // / 2048;
    }
    else if(!strcasecmp(arg2, "OGG") || !strcasecmp(arg2, "VORBIS") || !strcasecmp(arg2, "WAVE") || !strcasecmp(arg2, "WAV") || !strcasecmp(arg2, "PCM"))
    {
     if(ov_open(TmpTrack.fp, &TmpTrack.ovfile, NULL, 0) == 0)
     {
      //TmpTrack.Format = TRACK_FORMAT_AUDIO;
      //TmpTrack.sectors = ov_pcm_total(&TmpTrack.ovfile, -1) / 588;
     }
     else if(!fseek(TmpTrack.fp, 0, SEEK_SET) && (TmpTrack.sf = sf_open_fd(fileno(TmpTrack.fp), SFM_READ, &TmpTrack.sfinfo, 0)))
     {
      //TmpTrack.Format = TRACK_FORMAT_AUDIO;
      //TmpTrack.sectors = sfinfo.frames / 588;
     }
     else 
     {
      MDFN_printf("Unsupported audio track file format.\n");
      free(ret);
      return(0);
     }
    }
    else
    {
     MDFN_printf(_("Unsupported track format: %s\n"), arg2);
     free(ret);
     return(0);
    }
   }
   else if(!strcasecmp(cmdbuf, "TRACK"))
   {
    if(active_track >= 0)
    {
     memcpy(&ret->Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
     TmpTrack.FirstFileInstance = 0;
    }
    active_track = atoi(arg1);

    if(!strcasecmp(arg2, "AUDIO"))
     TmpTrack.Format = TRACK_FORMAT_AUDIO;
    else if(!strcasecmp(arg2, "MODE1/2048"))
    {
     TmpTrack.Format = TRACK_FORMAT_DATA;
     TmpTrack.IsData2352 = 0;
    }
    else if(!strcasecmp(arg2, "MODE1/2352"))
    {
     TmpTrack.Format = TRACK_FORMAT_DATA;
     TmpTrack.IsData2352 = 1;
    }
    TmpTrack.sectors = GetSectorCount(&TmpTrack);
    if(active_track < 0 || active_track > 99)
    {
     MDFN_printf(_("Invalid track number: %d\n"), active_track);
     return(0);
    }
   }
   else if(!strcasecmp(cmdbuf, "INDEX"))
   {
    if(active_track >= 0 && (!strcasecmp(arg1, "01") || !strcasecmp(arg1, "1")))
    {
     int m,s,f;
     sscanf(arg2, "%d:%d:%d", &m, &s, &f);
     TmpTrack.index = (m * 60 + s) * 75 + f;
    }
   }
   else if(!strcasecmp(cmdbuf, "PREGAP"))
   {
    if(active_track >= 0)
    {
     int m,s,f;
     sscanf(arg1, "%d:%d:%d", &m, &s, &f);
     TmpTrack.pregap = (m * 60 + s) * 75 + f;
    }
   }
  } // end of fgets() loop
  if(ferror(fp))
  {
   MDFN_printf(_("Error reading CUE sheet: %m\n"), errno);
   return(0);
  }

  if(active_track >= 0)
   memcpy(&ret->Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
 }
 
 for(int x = 0; x < 100; x++)
 {
  if(ret->Tracks[x].sectors + ret->Tracks[x].pregap)
  {
   ret->FirstTrack = x;
   break;
  }
 }
 for(int x = 99; x >= 0; x--)
 {
  if(ret->Tracks[x].sectors + ret->Tracks[x].pregap)
  {
   ret->NumTracks = x - ret->FirstTrack + 1;
   break;
  }
 }

 if(!ret->NumTracks)
 {
  MDFN_printf(_("No tracks found!\n"));
  return(0);
 }

 lsn_t RunningLSN = 0;
 lsn_t LastIndex = 0;

 for(int x = ret->FirstTrack; x < (ret->FirstTrack + ret->NumTracks); x++)
 {
  if(ret->Tracks[x].FirstFileInstance) LastIndex = 0;
  RunningLSN += ret->Tracks[x].pregap;
  ret->Tracks[x].LSN = RunningLSN;

  if((x+1) >= (ret->FirstTrack + ret->NumTracks))
  {
   //RunningLSN += ret->Tracks[x].sectors;
  }
  else if(ret->Tracks[x+1].FirstFileInstance)
  {
   //RunningLSN += ret->Tracks[x].sectors;
  }
  else
  {
   ret->Tracks[x].sectors = ret->Tracks[x + 1].index - ret->Tracks[x].index - ret->Tracks[x].pregap;
  }
  RunningLSN += ret->Tracks[x].sectors;
 }
 ret->total_sectors = RunningLSN; // Running LBA?  Running LSN? arghafsdf...LSNBAN!#!$ -_-
 return(ret);
}

lsn_t cdrfile_get_track_lsn(const CDRFile *p_cdrfile, track_t i_track)
{
 if(p_cdrfile->p_cdio)
 {
  return(cdio_get_track_lsn(p_cdrfile->p_cdio, i_track));
 }
 else
 {
  if(p_cdrfile->Tracks[i_track].Format == TRACK_FORMAT_AUDIO)
  {
   return(p_cdrfile->Tracks[i_track].LSN);
  }
  else
   return(p_cdrfile->Tracks[i_track].LSN);
 }
}

int cdrfile_read_audio_sector(const CDRFile *p_cdrfile, void *buf, lsn_t lsn)
{
 if(p_cdrfile->p_cdio)
  return(0 == cdio_read_audio_sector(p_cdrfile->p_cdio, buf, lsn));
 else
 {
  track_t track;
  for(track = p_cdrfile->FirstTrack; track < (p_cdrfile->FirstTrack + p_cdrfile->NumTracks); track++)
  {
   if(lsn >= (p_cdrfile->Tracks[track].LSN - p_cdrfile->Tracks[track].pregap) && lsn < (p_cdrfile->Tracks[track].LSN + p_cdrfile->Tracks[track].sectors))
   {
    if(lsn < p_cdrfile->Tracks[track].LSN)
    {
     //puts("Pregap read");
     memset(buf, 0, 2352);
    }
    else
    {
     if(p_cdrfile->Tracks[track].sf)
     {
      sf_seek(p_cdrfile->Tracks[track].sf, (lsn - p_cdrfile->Tracks[track].LSN) * 588, SEEK_SET);
      sf_read_short(p_cdrfile->Tracks[track].sf, (short*)buf, 588 * 2);

      Endian_A16_NE_to_LE(buf, 588 * 2);
     }
     else if(p_cdrfile->Tracks[track].ovfile.datasource)// vorbis
     {
      int cursection = 0;
      ov_pcm_seek((OggVorbis_File *)&p_cdrfile->Tracks[track].ovfile, (lsn - p_cdrfile->Tracks[track].LSN) * 588);
      long toread = 2352;
      while(toread > 0)
      {
       long didread = ov_read((OggVorbis_File *)&p_cdrfile->Tracks[track].ovfile, (char*)buf, toread, 0, 2, 1, &cursection);
       if(didread == 0)
       {
	memset(buf, 0, toread);
	toread = 0;
        break;
       }
       buf = (uint8 *)buf + didread;
       toread -= didread;
      }
     } // end if vorbis
     else	// Binary, woo.
     {
      if(!fseek(p_cdrfile->Tracks[track].fp, lsn * 2352, SEEK_SET))
      {
       size_t didread = fread(buf, 1, 2352, p_cdrfile->Tracks[track].fp);
       if(didread != 2352)
       {
        if(didread < 0) didread = 0;
        memset((uint8*)buf + didread, 0, 2352 - didread);
       }
      }
      else
       memset(buf, 0, 2352);
     }
    } // end if audible part of audio track read.
    break;
   } // End if LSN is in range
  } // end track search loop

  if(track == (p_cdrfile->FirstTrack + p_cdrfile->NumTracks))
  {
   memset(buf, 0, 2352);
   return(0);
  }
  return(1);
 }
}

track_t cdrfile_get_num_tracks (const CDRFile *p_cdrfile)
{
 if(p_cdrfile->p_cdio)
  return(cdio_get_num_tracks(p_cdrfile->p_cdio));
 else
  return(p_cdrfile->NumTracks);
}

track_format_t cdrfile_get_track_format(const CDRFile *p_cdrfile, track_t i_track)
{
 if(p_cdrfile->p_cdio)
  return(cdio_get_track_format(p_cdrfile->p_cdio, i_track));
 else
  return(p_cdrfile->Tracks[i_track].Format);
}

track_t cdrfile_get_first_track_num(const CDRFile *p_cdrfile)
{
 if(p_cdrfile->p_cdio)
  return(cdio_get_first_track_num(p_cdrfile->p_cdio));
 else
  return(p_cdrfile->FirstTrack);
}

int cdrfile_read_mode1_sectors (const CDRFile *p_cdrfile, void *buf, lsn_t lsn, bool b_form2, unsigned int i_sectors)
{
 if(p_cdrfile->p_cdio)
 {
  while(0 != cdio_read_mode1_sectors(p_cdrfile->p_cdio, buf, lsn, b_form2, i_sectors))
  {
   if(MDFND_ExitBlockingLoop())
    return(0);
  }
  return(1);
 }
 else
 {
  lsn_t end_lsn = lsn + i_sectors - 1;
  
  for(;lsn <= end_lsn; lsn++)
  {
   track_t track;
   for(track = p_cdrfile->FirstTrack; track < (p_cdrfile->FirstTrack + p_cdrfile->NumTracks); track++)
   {
    if(lsn >= (p_cdrfile->Tracks[track].LSN - p_cdrfile->Tracks[track].pregap) && lsn < (p_cdrfile->Tracks[track].LSN + p_cdrfile->Tracks[track].sectors))
    {
     if(lsn < p_cdrfile->Tracks[track].LSN)
     {
      //MDFN_printf("PREGAPREAD!!! mode1 sector read out of range!\n");
      memset(buf, 0x00, 2048);
     }
     else
     {
      //MDFN_printf("DONUT: %d, %d %d\n", track, lsn, p_cdrfile->Tracks[track].LSN);
      if(p_cdrfile->Tracks[track].IsData2352)
      {
       fseek(p_cdrfile->Tracks[track].fp, (lsn + p_cdrfile->Tracks[track].index - p_cdrfile->Tracks[track].LSN) * 2352 + 12 + 3 + 1, SEEK_SET);
      }
      else
       fseek(p_cdrfile->Tracks[track].fp, (lsn + p_cdrfile->Tracks[track].index - p_cdrfile->Tracks[track].LSN) * 2048, SEEK_SET);
      fread(buf, 1, 2048, p_cdrfile->Tracks[track].fp);
     }
     break;
    }
   }
   if(track == (p_cdrfile->FirstTrack + p_cdrfile->NumTracks))
   {
    //MDFN_printf("mode1 sector read out of range!\n");
    memset(buf, 0x00, 2048);
   }
   buf = (uint8*)buf + 2048;
  }
  return(1);
 }
}

int cdrfile_read_mode1_sector (const CDRFile *p_cdrfile, void *buf, lsn_t lsn, bool b_form2)
{
 return(cdrfile_read_mode1_sectors(p_cdrfile, buf, lsn, b_form2, 1));
}

uint32_t cdrfile_stat_size (const CDRFile *p_cdrfile)
{
 if(p_cdrfile->p_cdio)
  return(cdio_stat_size(p_cdrfile->p_cdio));
 else
  return(p_cdrfile->total_sectors);
}
