/* 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 "tremor/ivorbisfile.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)
   return(ov_pcm_total(track->ovfile, -1) / 588);
  else if(track->MPCReaderFile)
  {
   return(((track->MPCStreamInfo->frames - 1) * MPC_FRAME_LENGTH + track->MPCStreamInfo->last_frame_samples) / 588);
  }
  else
  {
   struct stat stat_buf;
   fstat(fileno(track->fp), &stat_buf);
   return(stat_buf.st_size / 2352);
  }
 }
 
 return(0);
}

void cdrfile_destroy(CDRFile *p_cdrfile)
{
 if(p_cdrfile->p_cdio)
  cdio_destroy(p_cdrfile->p_cdio);
 else
 {
  track_t track;
  for(track = p_cdrfile->FirstTrack; track < (p_cdrfile->FirstTrack + p_cdrfile->NumTracks); track++)
  {
   CDRFILE_TRACK_INFO *this_track = &p_cdrfile->Tracks[track];
   
   if(this_track->MPCReaderFile)
    free(this_track->MPCReaderFile);
   if(this_track->MPCStreamInfo)
    free(this_track->MPCStreamInfo);
   if(this_track->MPCDecoder)
    free(this_track->MPCDecoder);
   if(this_track->MPCBuffer)
    free(this_track->MPCBuffer);
 
   if(p_cdrfile->Tracks[track].sf)
   {
    sf_close(p_cdrfile->Tracks[track].sf);
   }

   if(p_cdrfile->Tracks[track].ovfile)
   {
    ov_clear(p_cdrfile->Tracks[track].ovfile);
    free(p_cdrfile->Tracks[track].ovfile);
   }
   else
   {
    if(this_track->FirstFileInstance)
     fclose(this_track->fp);
   }
  }  
 }
 free(p_cdrfile);
}

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");

  #if !defined(__APPLE__) && !defined(__MACOS__)
  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);
  }

  if(devices)
   cdio_free_device_list(devices);
  #endif

  p_cdio = cdio_open_cd(path); //, DRIVER_UNKNOWN); //NULL, DRIVER_UNKNOWN);

  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")
	|| !strcasecmp(arg2, "MPC") || !strcasecmp(arg2, "MP+"))
    {
     TmpTrack.ovfile = (OggVorbis_File *) calloc(1, sizeof(OggVorbis_File));

     if((TmpTrack.sf = sf_open_fd(fileno(TmpTrack.fp), SFM_READ, &TmpTrack.sfinfo, 0)))
     {
      free(TmpTrack.ovfile);
      TmpTrack.ovfile = NULL;
     }
     else if(!lseek(fileno(TmpTrack.fp), 0, SEEK_SET) && !ov_open(TmpTrack.fp, TmpTrack.ovfile, NULL, 0))
     {
      //TmpTrack.Format = TRACK_FORMAT_AUDIO;
      //TmpTrack.sectors = ov_pcm_total(&TmpTrack.ovfile, -1) / 588;
     }
     else
     {      
      free(TmpTrack.ovfile);
      TmpTrack.ovfile = NULL;

      fseek(TmpTrack.fp, 0, SEEK_SET);

      TmpTrack.MPCReaderFile = (mpc_reader_file *)calloc(1, sizeof(mpc_reader_file));
      TmpTrack.MPCStreamInfo = (mpc_streaminfo *)calloc(1, sizeof(mpc_streaminfo));
      TmpTrack.MPCDecoder = (mpc_decoder *)calloc(1, sizeof(mpc_decoder));
      TmpTrack.MPCBuffer = (MPC_SAMPLE_FORMAT *)calloc(MPC_DECODER_BUFFER_LENGTH, sizeof(MPC_SAMPLE_FORMAT));

      mpc_reader_setup_file_reader(TmpTrack.MPCReaderFile, TmpTrack.fp);

      if(mpc_streaminfo_read(TmpTrack.MPCStreamInfo, &TmpTrack.MPCReaderFile->reader) != ERROR_CODE_OK)
      {
       MDFN_printf("Unsupported audio track file format: %s\n", arg1);
       free(TmpTrack.MPCReaderFile);
       free(TmpTrack.MPCStreamInfo);
       free(TmpTrack.MPCDecoder);
       free(TmpTrack.MPCBuffer);
       free(ret);
       return(0);
      }

      mpc_decoder_setup(TmpTrack.MPCDecoder, &TmpTrack.MPCReaderFile->reader);
      if(!mpc_decoder_initialize(TmpTrack.MPCDecoder, TmpTrack.MPCStreamInfo))
      {
       MDFN_printf(_("Error initializing MusePack decoder: %s!\n"), arg1);
       free(TmpTrack.MPCReaderFile);
       free(TmpTrack.MPCStreamInfo);
       free(TmpTrack.MPCDecoder);
       free(TmpTrack.MPCBuffer);
       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(CDRFile *p_cdrfile, void *buf, lsn_t lsn)
{
 if(p_cdrfile->p_cdio)
  return(cdio_read_audio_sector(p_cdrfile->p_cdio, buf, lsn) >= 0);
 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);
     }
     else if(p_cdrfile->Tracks[track].ovfile)// vorbis
     {
      int cursection = 0;

      if(p_cdrfile->Tracks[track].LastSamplePos != 588 * (lsn - p_cdrfile->Tracks[track].LSN))
      {
       ov_pcm_seek((OggVorbis_File *)p_cdrfile->Tracks[track].ovfile, (lsn - p_cdrfile->Tracks[track].LSN) * 588);
       p_cdrfile->Tracks[track].LastSamplePos = 588 * (lsn - p_cdrfile->Tracks[track].LSN);
      }
      long toread = 2352;
      while(toread > 0)
      {
       long didread = ov_read((OggVorbis_File *)p_cdrfile->Tracks[track].ovfile, (char*)buf, toread, &cursection);

       if(didread == 0)
       {
	memset(buf, 0, toread);
	toread = 0;
        break;
       }
       buf = (uint8 *)buf + didread;
       toread -= didread;
       p_cdrfile->Tracks[track].LastSamplePos += didread / 4;
      }
     } // end if vorbis
     else if(p_cdrfile->Tracks[track].MPCReaderFile)	// MPC
     {
      //printf("%d %d\n", (lsn - p_cdrfile->Tracks[track].LSN), p_cdrfile->Tracks[track].LastSamplePos);
      if(p_cdrfile->Tracks[track].LastSamplePos != 1176 * (lsn - p_cdrfile->Tracks[track].LSN))
      {
       mpc_decoder_seek_sample(p_cdrfile->Tracks[track].MPCDecoder, 588 * (lsn - p_cdrfile->Tracks[track].LSN));
       p_cdrfile->Tracks[track].LastSamplePos = 1176 * (lsn - p_cdrfile->Tracks[track].LSN);
      }
      //MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
      //  MPC_SAMPLE_FORMAT MPCBuffer[MPC_DECODER_BUFFER_LENGTH];
      //  uint32 MPCBufferIn;
      int16 *cowbuf = (int16 *)buf;
      int32 toread = 1176;

      while(toread)
      {
       int32 tmplen;

       if(!p_cdrfile->Tracks[track].MPCBufferIn)
       {
        int status = mpc_decoder_decode(p_cdrfile->Tracks[track].MPCDecoder, p_cdrfile->Tracks[track].MPCBuffer, 0, 0);
	if(status < 0)
	{
	 printf("Bah\n");
	 break;
	}
        p_cdrfile->Tracks[track].MPCBufferIn = status * 2;
	p_cdrfile->Tracks[track].MPCBufferOffs = 0;
       }

       tmplen = p_cdrfile->Tracks[track].MPCBufferIn;

       if(tmplen >= toread)
        tmplen = toread;

       for(int x = 0; x < tmplen; x++)
       {
	*cowbuf = (int16)(p_cdrfile->Tracks[track].MPCBuffer[p_cdrfile->Tracks[track].MPCBufferOffs + x] >> 14);
	cowbuf++;
       }
      
       p_cdrfile->Tracks[track].MPCBufferOffs += tmplen;
       toread -= tmplen;
       p_cdrfile->Tracks[track].LastSamplePos += tmplen;
       p_cdrfile->Tracks[track].MPCBufferIn -= tmplen;        
     }

     }
     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);
       }
       Endian_A16_LE_to_NE(buf, 588 * 2);
      }
      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(cdio_read_mode1_sectors(p_cdrfile->p_cdio, buf, lsn, b_form2, i_sectors) < 0)
  {
   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);
}
