/******************************************************************************/
/* Mednafen - Multi-system Emulator                                           */
/******************************************************************************/
/* qtrecord.cpp:
**  Copyright (C) 2010-2020 Mednafen 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.,
** 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <mednafen/mednafen.h>
#include <mednafen/Time.h>
#include "qtrecord.h"
#include <minilzo/minilzo.h>
#include "video/png.h"

#include <zlib.h>

namespace Mednafen
{

void QTRecord::w16(uint16 val)
{
 uint8 buf[2];

 MDFN_en16msb(buf, val);

 qtfile.write(buf, sizeof(buf));
}

void QTRecord::w32(uint32 val)
{
 uint8 buf[4];

 MDFN_en32msb(buf, val);

 qtfile.write(buf, sizeof(buf));
}

void QTRecord::w32s(const char *str)
{
 uint8 buf[4];

 memset(buf, 0x20, sizeof(buf));

 for(unsigned int i = 0; i < sizeof(buf); i++)
 {
  if(!str[i])
   break;

  buf[i] = str[i];
 }

 qtfile.write(buf, sizeof(buf));
}

void QTRecord::w64s(const char *str)
{
 uint8 buf[8];

 memset(buf, 0x20, sizeof(buf));

 for(unsigned int i = 0; i < sizeof(buf); i++)
 {
  if(!str[i])
   break;

  buf[i] = str[i];
 }

 qtfile.write(buf, sizeof(buf));
}


void QTRecord::w64(uint64 val)
{
 uint8 buf[8];

 MDFN_en64msb(buf, val);

 qtfile.write(buf, sizeof(buf));
}

// fixed_len doesn't include the leading 1-byte count
void QTRecord::wps(const char *str, uint8 fixed_len)
{
 uint8 slen = std::min<size_t>(255, strlen(str));
 uint8 count = (fixed_len ? fixed_len : slen);
 char buf[1 + 255];

 memset(buf, 0, sizeof(buf));
 buf[0] = count;
 memcpy(&buf[1], str, std::min<uint8>(slen, count));

 qtfile.write(buf, 1 + count);
}

void QTRecord::vardata_begin(void)
{
 vardata_foffsets.push_back(qtfile.tell());

 w32(0);	// Overwritten in vardata_end()
}

void QTRecord::vardata_end(void)
{
 uint64 cur_offset = qtfile.tell();
 uint64 start_offset = vardata_foffsets.back();

 vardata_foffsets.pop_back();

 qtfile.seek(start_offset, SEEK_SET);
 w32(cur_offset - start_offset);

 //printf("%d\n", cur_offset - start_offset);

 qtfile.seek(cur_offset, SEEK_SET);
}

void QTRecord::atom_begin(uint32 type, bool small_atom)
{
 //small_atom = true; // DEBUG, REMOVE ME

 atom_foffsets.push_back(qtfile.tell());
 atom_smalls.push_back(small_atom);

 if(small_atom)
 {
  w32(0);
  w32(type);
 }
 else
 {
  w32(0x00000001);
  w32(type);
  w64(0);
 }
}


void QTRecord::atom_begin(const char *type, bool small_atom)
{
 uint32 type_num = 0;

 for(int i = 0; i < 4; i++)
 {
  if(!type[i])
   break;

  type_num |= (uint32)type[i] << ((3 - i) * 8);
 }
 atom_begin(type_num, small_atom);
}

void QTRecord::atom_end(void)
{
 uint64 cur_offset = qtfile.tell();
 uint64 start_offset = atom_foffsets.back();
 bool small_atom = atom_smalls.back();

 atom_foffsets.pop_back();
 atom_smalls.pop_back();

 if(small_atom)
 {
  qtfile.seek(start_offset, SEEK_SET);
  w32(cur_offset - start_offset);
 }
 else
 {
  qtfile.seek(start_offset + 8, SEEK_SET);

  w64(cur_offset - start_offset);
 }

 qtfile.seek(cur_offset, SEEK_SET);
}

QTRecord::QTRecord(const std::string& path, const VideoSpec &spec) : qtfile(path, FileStream::MODE_WRITE_SAFE), resampler(NULL)
{
 Finished = false;

 SoundFramesWritten = 0;

 SoundRate = spec.SoundRate;
 SoundChan = spec.SoundChan;

 // If we change the rate threshold for resampling here, remember to change the std::min in the informational printf in
 // mednafen.cpp
 if(SoundRate > 64000)
 {
  int err = 0;

  ResampInRate = spec.SoundRate;
  SoundRate = 64000;
  if(!(resampler = speex_resampler_init(spec.SoundChan, spec.SoundRate, SoundRate, 5, &err)))
  {
   throw MDFN_Error(0, _("Error initializing audio resampler."));
  }
  //printf("%f ms\n", 1000.0 * speex_resampler_get_input_latency(resampler) / spec.SoundRate);
 }
 else
  resampler = NULL;

 ResampInBufferFramesInCount = 0;
 ResampInBuffer.resize(0);
 ResampOutBuffer.resize(0);

 //ResampInBuffer.resize((uint32)((100 * spec.SoundRate + 999) / 1000) * SoundChan);
 //ResampOutBuffer.resize((uint32)((100 * SoundRate + 999) / 1000) * SoundChan);

 //printf("%u %u\n", ResampInBuffer.size(), ReampOutBuffer.size());

 TimeIndex = 0;
 if(SoundRate && SoundChan)
 {
  TimeScale = SoundRate;
 }
 else
 {
  TimeScale = 10000;
  MC = spec.MasterClock;
  MCAccum = 0;
 }

 QTVideoWidth = spec.VideoWidth;
 QTVideoHeight = spec.VideoHeight;

 A = 65536 * spec.AspectXAdjust;
 D = 65536 * spec.AspectYAdjust;

 VideoCodec = spec.VideoCodec;

 if(VideoCodec == VCODEC_PNG)
  RawVideoBuffer.resize((1 + QTVideoWidth * 3) * QTVideoHeight);
 else if(VideoCodec == VCODEC_CSCD)
  RawVideoBuffer.resize(((QTVideoWidth * 3 + 3) &~ 3) * QTVideoHeight);
 else
  RawVideoBuffer.resize(QTVideoWidth * 3 * QTVideoHeight);

 if(VideoCodec == VCODEC_CSCD)
 {
  lzo1x_1_workmem.reset(new uint8[LZO1X_1_MEM_COMPRESS]);
  CompressedVideoBuffer.resize((RawVideoBuffer.size() * 110 + 99 ) / 100);	// 1.10
 }
 else if(VideoCodec == VCODEC_PNG)
  CompressedVideoBuffer.resize(compressBound(RawVideoBuffer.size()));

 {
  uint32 appley_time = Time::EpochTime() + 2082844800;

  CreationTS = appley_time;
  ModificationTS = appley_time;
 }

 Write_ftyp();

 atom_begin("mdat", false);
}


template<typename T, uint64 rgb16_tag = 0>
static void DecodeLine(const T* src, const MDFN_PixelFormat& src_pf, int src_width, uint8* dest, bool dest_bgr, uint32 dest_width)
{
 const int xscale_factor = dest_width / src_width;
 uint32 dest_x = 0;
 const unsigned rs = src_pf.Rshift;
 const unsigned gs = src_pf.Gshift;
 const unsigned bs = src_pf.Bshift;

 for(int x = 0; x < src_width; x++)
 {
  const uint32 c = src[x];
  int r, g, b;

  if(sizeof(T) == 2)
   MDFN_PixelFormat::TDecodeColor(rgb16_tag, c, &r, &g, &b);
  else
  {
   r = (uint8)(c >> rs);
   g = (uint8)(c >> gs);
   b = (uint8)(c >> bs);
  }

  for(int sub_x = 0; sub_x < xscale_factor; sub_x++)
  {
   if(dest_x < dest_width)
   {
    if(dest_bgr)
    {
     dest[dest_x * 3 + 0] = b;
     dest[dest_x * 3 + 1] = g;
     dest[dest_x * 3 + 2] = r;
    }
    else
    {
     dest[dest_x * 3 + 0] = r;
     dest[dest_x * 3 + 1] = g;
     dest[dest_x * 3 + 2] = b;
    }

    dest_x++;
   }
  }
 }

 while(dest_x < dest_width)
 {
  dest[dest_x * 3 + 0] = 0;
  dest[dest_x * 3 + 1] = 0;
  dest[dest_x * 3 + 2] = 0;

  dest_x++;
 }
}

void QTRecord::WriteFrame(const MDFN_Surface *surface, const MDFN_Rect &DisplayRect, const int32 *LineWidths,
			  const int16 *SoundBuf, const int32 SoundBufSize, const int64 MasterCycles)
{
 QTChunk qts;

 memset(&qts, 0, sizeof(qts));

 if(DisplayRect.h <= 0)
 {
  fprintf(stderr, "[BUG] qtrecord.cpp: DisplayRect.h <= 0\n");
  return;
 }


 qts.video_foffset = qtfile.tell();

 // Write video here
 {
  uint32 dest_y = 0;
  int yscale_factor = QTVideoHeight / DisplayRect.h;

  for(int y = DisplayRect.y; y < DisplayRect.y + DisplayRect.h; y++)
  {
   int width;
   uint8 *dest_line;

   if(dest_y >= QTVideoHeight)
    break;

   if(VideoCodec == VCODEC_CSCD)
    dest_line = &RawVideoBuffer[(QTVideoHeight - 1 - dest_y) * ((QTVideoWidth * 3 + 3) &~ 3)];
   else if(VideoCodec == VCODEC_PNG)
    dest_line = &RawVideoBuffer[dest_y * (QTVideoWidth * 3 + 1)];
   else
    dest_line = &RawVideoBuffer[dest_y * QTVideoWidth * 3];

   if(LineWidths[0] == ~0)
    width = DisplayRect.w;
   else
    width = LineWidths[y];

   if(VideoCodec == VCODEC_PNG)
   {
    *dest_line = 0;
    dest_line++;
   }

   switch(surface->format.opp)
   {
    case 1:
	DecodeLine<uint8>(surface->pix<uint8>() + y * surface->pitchinpix + DisplayRect.x, surface->format, width, dest_line, VideoCodec == VCODEC_CSCD, QTVideoWidth);
	break;

    case 2:
	if(surface->format.Gprec == 5)
	 DecodeLine<uint16, MDFN_PixelFormat::IRGB16_1555>(surface->pix<uint16>() + y * surface->pitchinpix + DisplayRect.x, surface->format, width, dest_line, VideoCodec == VCODEC_CSCD, QTVideoWidth);
	else
	 DecodeLine<uint16, MDFN_PixelFormat::RGB16_565>(surface->pix<uint16>() + y * surface->pitchinpix + DisplayRect.x, surface->format, width, dest_line, VideoCodec == VCODEC_CSCD, QTVideoWidth);
	break;

    case 4:
	DecodeLine<uint32>(surface->pix<uint32>() + y * surface->pitchinpix + DisplayRect.x, surface->format, width, dest_line, VideoCodec == VCODEC_CSCD, QTVideoWidth);
	break;
   }

   for(int sub_y = 1; sub_y < yscale_factor; sub_y++)
   {
    if((dest_y + sub_y) >= QTVideoHeight)
     break;

    if(VideoCodec == VCODEC_CSCD)
     memcpy(&RawVideoBuffer[(QTVideoHeight - 1 - (dest_y + sub_y)) * ((QTVideoWidth * 3 + 3) &~ 3)], dest_line, QTVideoWidth * 3);
    else if(VideoCodec == VCODEC_PNG)
     memcpy(&RawVideoBuffer[(dest_y + sub_y) * (QTVideoWidth * 3 + 1)], dest_line - 1, QTVideoWidth * 3 + 1);
    else
     memcpy(&RawVideoBuffer[(dest_y + sub_y) * QTVideoWidth * 3], dest_line, QTVideoWidth * 3);
   }

   dest_y += yscale_factor;
  } // end for(int y = DisplayRect.y; y < DisplayRect.y + DisplayRect.h; y++)
 }

 if(VideoCodec == VCODEC_CSCD)
 {
  lzo_uint dst_len = CompressedVideoBuffer.size();
  uint8 tmp[2];

  tmp[0] = (0 << 1) | 0x1;
  tmp[1] = 0;

  qtfile.write(tmp, 2);

  lzo1x_1_compress(&RawVideoBuffer[0], RawVideoBuffer.size(), &CompressedVideoBuffer[0], &dst_len, lzo1x_1_workmem.get());

  qtfile.write(&CompressedVideoBuffer[0], dst_len);
 }
 else if(VideoCodec == VCODEC_RAW)
  qtfile.write(&RawVideoBuffer[0], RawVideoBuffer.size());
 else if(VideoCodec == VCODEC_PNG)
 {
  //PNGWrite(qtfile, surface, DisplayRect, LineWidths);
  static const uint8 png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
  uint8 IHDR[13];
  uLongf compress_buffer_size;

  qtfile.write(png_sig, sizeof(png_sig));

  MDFN_en32msb(&IHDR[0], QTVideoWidth);
  MDFN_en32msb(&IHDR[4], QTVideoHeight);

  IHDR[8] = 8;	// 8 bits per color component
  IHDR[9] = 2;	// Color type: RGB triplet(no alpha)
  IHDR[10] = 0;	// Compression: deflate
  IHDR[11] = 0;	// Basic adaptive filter set
  IHDR[12] = 0;	// No interlace


  PNGWrite::WriteChunk(qtfile, 13, "IHDR", IHDR);

  compress_buffer_size = CompressedVideoBuffer.size();

  compress(&CompressedVideoBuffer[0], &compress_buffer_size, &RawVideoBuffer[0], RawVideoBuffer.size());

  PNGWrite::WriteChunk(qtfile, compress_buffer_size, "IDAT", &CompressedVideoBuffer[0]);

  PNGWrite::WriteChunk(qtfile, 0, "IEND", 0);
 }



 qts.video_byte_size = qtfile.tell() - qts.video_foffset;



 qts.audio_foffset = qtfile.tell();

 // Write audio here
 //
 //
 int32 SoundBufROSize;

 if(resampler)
 {
  spx_uint32_t in_len;
  spx_uint32_t out_len;

  if((ResampInBuffer.size() - (ResampInBufferFramesInCount * SoundChan)) < SoundBufSize * SoundChan)
   ResampInBuffer.resize((ResampInBufferFramesInCount + SoundBufSize) * SoundChan);

  for(unsigned i = 0; i < SoundBufSize * SoundChan; i++)
   ResampInBuffer[ResampInBufferFramesInCount * SoundChan + i] = SoundBuf[i];

  ResampInBufferFramesInCount += SoundBufSize;

  {
   // *2 for padding for potential precision issues related to the resampling ratio.
   size_t needed_out_size = (uint64_t)ResampInBuffer.size() / SoundChan * 2 * SoundRate / ResampInRate * SoundChan;

   //printf("Zoom: %u %u\n", (unsigned)ResampInBuffer.size(), (unsigned)needed_out_size);

   if(ResampOutBuffer.size() < needed_out_size)
    ResampOutBuffer.resize(needed_out_size);

   // Very crude, but works because we're only downsampling.  TODO make it more precise?
   //if(ResampOutBuffer.size() < ResampInBuffer.size())
   // ResampOutBuffer.resize(ResampInBuffer.size());
  }

  in_len = ResampInBufferFramesInCount;
  out_len = ResampOutBuffer.size() / SoundChan;

  speex_resampler_process_interleaved_int(resampler, (const spx_int16_t *)&ResampInBuffer[0], &in_len, (spx_int16_t *)&ResampOutBuffer[0], &out_len);

  if((ResampInBufferFramesInCount - in_len) > 0)	// Shouldn't happen, unless there's not enough room in the output buffer.
  {
   printf("%u\n", ResampInBufferFramesInCount - in_len);
   memmove(&ResampInBuffer[0], &ResampInBuffer[in_len * SoundChan], (ResampInBufferFramesInCount - in_len) * sizeof(int16) * SoundChan);
  }

  ResampInBufferFramesInCount -= in_len;
  SoundBufROSize = out_len;

  for(unsigned i = 0; i < SoundBufROSize * SoundChan; i++)
   MDFN_en16msb((uint8 *)&ResampOutBuffer[i], ResampOutBuffer[i]);
 }
 else
 {
  SoundBufROSize = SoundBufSize;

  if(ResampOutBuffer.size() < SoundBufSize * SoundChan)
   ResampOutBuffer.resize(SoundBufSize * SoundChan);

  for(unsigned i = 0; i < SoundBufROSize * SoundChan; i++)
   MDFN_en16msb((uint8 *)&ResampOutBuffer[i], SoundBuf[i]);
 }

 qtfile.write(&ResampOutBuffer[0], sizeof(int16) * SoundBufROSize * SoundChan);
 //
 //
 //
 qts.audio_byte_size = qtfile.tell() - qts.audio_foffset;

 SoundFramesWritten += SoundBufROSize;

 if(SoundRate && SoundChan)
 {
  qts.time_length = qts.audio_byte_size / SoundChan / sizeof(int16);
  TimeIndex += SoundBufROSize;
 }
 else
 {
  uint64 tnt;
 
  MCAccum += (uint64)MasterCycles * TimeScale;
  tnt = MCAccum / (MC >> 32);
  MCAccum %= (MC >> 32);

  //printf("%u\n", tnt);

  qts.time_length = tnt;
  TimeIndex += tnt;
 }

 QTChunks.push_back(qts);
}

void QTRecord::Write_ftyp(void) // Leaf
{
 atom_begin("ftyp");

 w32s("qt  ");		// Major brand

 w32(0x20070900);	// Minor_Version

 w32s("qt  ");		// Compatible brand

 // Placeholders to get the mdat start at 0x20 for prettiness(and libquicktime does it, I don't know if it has another reason).
 w32(0);
 w32(0);
 w32(0);

 atom_end();
}

void QTRecord::Write_mvhd(void)	// Leaf
{
 atom_begin("mvhd");

 w32(0);		// Version/flags
 w32(CreationTS);	// Created Mac date
 w32(ModificationTS);	// Modified Mac date
 w32(TimeScale);	// Time scale

 w32(TimeIndex); 	// Duration
 w32(65536 * 1);	// Preferred rate
 w16(256 * 1);		// Preferred volume

 for(int i = 0; i < 5; i++)
  w16(0);			// Reserved(5 * 2 = 10)

 w32(65536 * 1); // A
 w32(0);	// B
 w32(0);	// U
 w32(0);	// C
 w32(65536 * 1); // D
 w32(0);	// V
 w32(0);	// X
 w32(0);	// Y
 w32(1 << 30); // W

 w32(0);	// Preview time
 w32(0);	// Preview duration

 w32(0);	// Poster time

 w32(0);	// Selection time.
 w32(TimeIndex);	// Selection duration.

 w32(0);	// Current time

 // Next track id
 if(SoundRate && SoundChan)
  w32(3);
 else
  w32(2);

 atom_end();
}

void QTRecord::Write_tkhd(void)	// Leaf
{
 atom_begin("tkhd");

 w32(0xF);	// Version and flags

 w32(CreationTS); // Created mac date
 w32(ModificationTS);	// Modified mac date

 if(OnAudioTrack)
  w32(2);
 else
  w32(1);	// Track id

 w32(0);	// Reserved

 w32(TimeIndex); // Duration 

 w64(0);	// Reserved

 w16(0);	// Video layer.
 w16(0);	// Alternate/other
 w16(256);	// Track audio volume
 w16(0);	// Reserved

 w32(A);		// A
 w32(0);		// B
 w32(0);		// U
 w32(0);		// C
 w32(D);		// D
 w32(0);		// V
 w32(0);		// X
 w32(0);		// Y
 w32(1 << 30);		// W
 
 w32(65536 * QTVideoWidth);
 w32(65536 * QTVideoHeight);

 atom_end();
}

// Sample description
void QTRecord::Write_stsd(void) // Leaf
{
 atom_begin("stsd");

 w32(0);	// Version and flags

 w32(1);	// Number of sample descriptions

 if(OnAudioTrack)	// Audio track
 {
  vardata_begin();	// w32(36)

  w32s("twos");	// Data format

  w32(0);       // Reserved
  w16(0);       // Reserved

  w16(1);       // dref index?

  w16(0);       // Version
  w16(0);       // Revision level

  w32s("MDFN");  // Vendor

  w16(SoundChan); // Number of sound channels
  w16(16);       // Sample size
  w16(0);        // Audio compression ID
  w16(0);        // Audio packet rate
  w32(SoundRate * 65536);        // Audio sample rate

  vardata_end();
 }
 else	// Video track
 {
  vardata_begin();	//  w32(86 + 12);	// Description length (+12 for gama)

  if(VideoCodec == VCODEC_CSCD)
   w32s("CSCD");	// Data format
  else if(VideoCodec == VCODEC_PNG)
   w32s("png ");
  else
   w32s("raw ");	// Data format

  w32(0);	// Reserved
  w16(0);	// Reserved

  w16(1);	// dref index?

  w16(0);	// Version
  w16(0);	// Revision level

  w32s("MDFN");	// Vendor

  w32(1024);	// Video temporal quality
  w32(1024);	// Video spatial quality

  w16(QTVideoWidth);	// Width of source image
  w16(QTVideoHeight);	// Height of source image

  w32(48 * 65536);		// Horizontal PPI(FIXME)

  w32(48 * 65536);		// Vertical PPI(FIXME)

  w32(0);			// Data size must be set to 0

  w16(1);	// Frame count(per sample)

  wps("Mednafen " MEDNAFEN_VERSION, 31);	// Video encoder

  w16(24);	// Depth

  w16(0xFFFF);	// Color table ID

  atom_begin("gama");
   w32(65536 * 2.2);
  atom_end();

  vardata_end();
 }

 atom_end();
}

// Time-to-sample
void QTRecord::Write_stts(void)	// Leaf
{
 atom_begin("stts");

 w32(0);	// Version and flags

 if(OnAudioTrack)
 {
  w32(1);	// Number of entries

  // Entry
  w32(SoundFramesWritten);
  w32(1);
 }
 else
 {
  w32(QTChunks.size()); // number of entries

  for(uint32 i = 0; i < QTChunks.size(); i++)
  {
   w32(1);
   w32(QTChunks[i].time_length);
  }
 }

 atom_end();
}

// Sample-to-chunk
void QTRecord::Write_stsc(void) // Leaf
{
 atom_begin("stsc");

 w32(0);	// Version and flags

 if(OnAudioTrack)
 {
  w32(QTChunks.size());	// Number of entries
  for(uint32 i = 0; i < QTChunks.size(); i++)
  {
   w32(1 + i);		// First chunk number using this entry
   w32(QTChunks[i].audio_byte_size / SoundChan / sizeof(int16));	// Samples per chunk
   w32(1);	// Sample description ID(references stsd)
  }
 }
 else
 {
  w32(1);	// Number of entries
  w32(1);	// First chunk
  w32(1);	// Samples per chunk
  w32(1);	// Sample description ID(references data in stsd)
 }

 atom_end();
}

void QTRecord::Write_stsz(void) // Leaf
{
 atom_begin("stsz");

 w32(0);	// Version and flags

 if(OnAudioTrack)
 {
  w32(1);
  w32(SoundFramesWritten);
 }
 else
 {
  w32(0);		// Sample size

  w32(QTChunks.size());	// Number of entries

  for(uint32 i = 0; i < QTChunks.size(); i++)
  {
   if(OnAudioTrack)
    w32(QTChunks[i].audio_byte_size);
   else
    w32(QTChunks[i].video_byte_size);
  }
 }

 atom_end();
}

// Chunk offset atom(64-bit style)
void QTRecord::Write_co64(void) // Leaf
{
 atom_begin("co64");

 w32(0);	// Version and flags

 w32(QTChunks.size());	// Number of entries

 for(uint32 i = 0; i < QTChunks.size(); i++)
 {
  if(OnAudioTrack)
   w64(QTChunks[i].audio_foffset);
  else
   w64(QTChunks[i].video_foffset);
 }

 atom_end();
}

void QTRecord::Write_stco(void) // Leaf
{
 atom_begin("stco");

 w32(0);	// Version and flags

 w32(QTChunks.size());	// Number of entries

 for(uint32 i = 0; i < QTChunks.size(); i++)
 {
  if(OnAudioTrack)
   w32(QTChunks[i].audio_foffset);
  else
   w32(QTChunks[i].video_foffset);
 }

 atom_end();
}

void QTRecord::Write_stbl(void)
{
 atom_begin("stbl");

 Write_stsd();

 Write_stts();

 Write_stsc();

 Write_stsz();

 bool need64bit_offset = false;

 if(OnAudioTrack && QTChunks.back().audio_foffset >= ((uint64)1 << 32))
  need64bit_offset = true;

 if(!OnAudioTrack && QTChunks.back().video_foffset >= ((uint64)1 << 32))
  need64bit_offset = true;

 if(need64bit_offset)
  Write_co64();
 else
  Write_stco();

 atom_end();
}

// Media header atom
void QTRecord::Write_mdhd(void)	// Leaf
{
 atom_begin("mdhd");

 w32(0);		  // Version/flags
 w32(CreationTS);	  // Creation date
 w32(ModificationTS);	  // Modification date
 w32(TimeScale);	  // Time scale
 w32(TimeIndex);	  // Duration

 w16(0);		// Language
 w16(0);		// Quality

 atom_end();
}

// Sound media information header
void QTRecord::Write_smhd(void) // Leaf
{
 atom_begin("smhd");
 w32(0x1);	// Version/flags
 w16(0);	// Balance
 w16(0);	// Reserved

 atom_end();
}

// Video media information header
void QTRecord::Write_vmhd(void) // Leaf
{
 atom_begin("vmhd");

 w32(0x1);	// Version/flags

 w16(0);	// Quickdraw graphics mode (Simple Copy, no dither)

 // RGB values(unused I guess in simple copy?)
 w16(0x8000);
 w16(0x8000);
 w16(0x8000);

 atom_end();
}

void QTRecord::Write_hdlr(const char *str, const char *comp_name) // Leaf
{
 atom_begin("hdlr");

 w32(0);	// Version/flags

 w64s(str);

 w32(0);	// Reserved

 w32(0);	// reserved

 w32(0);	// Reserved

 wps(comp_name, 0);

 atom_end();
}

void QTRecord::Write_dinf(void)
{
 atom_begin("dinf");

  atom_begin("dref");

   w32(0);	// Version/flags
   w32(1);	// Number of references

   atom_begin("alis");
    w32(0x00000001);	// Version/flags
   atom_end();

  atom_end();

 atom_end();
}

void QTRecord::Write_minf(void)
{
 atom_begin("minf");

 if(OnAudioTrack)
  Write_smhd();
 else
  Write_vmhd();

 Write_hdlr("dhlralis", "Mednafen Alias Data Handler");

 Write_dinf();

 Write_stbl();

 atom_end();
}

void QTRecord::Write_mdia(void)
{
 atom_begin("mdia");

 Write_mdhd();

 if(OnAudioTrack)
  Write_hdlr("mhlrsoun", "Mednafen Sound Media Handler");
 else
  Write_hdlr("mhlrvide", "Mednafen Video Media Handler");

 Write_minf();

 atom_end();
}

void QTRecord::Write_edts(void)
{
 atom_begin("edts");

  atom_begin("elst");
   w32(0); // version/flags
   w32(1); // Number of edits
   w32(TimeIndex);	// Duration
   w32(0);			// start time
   w32(65536 * 1);		// Rate
  atom_end();

 atom_end();
}

void QTRecord::Write_trak(void)
{
 atom_begin("trak");

 Write_tkhd();

 Write_edts();

 Write_mdia();

 atom_end();
}

void QTRecord::Write_udta(void)
{
 atom_begin("udta");

  atom_begin("@fmt");

   qtfile.put_string("Computer-generated via an emulator.");

  atom_end();

  atom_begin("@swr");

   qtfile.put_string("Mednafen " MEDNAFEN_VERSION);

  atom_end();

 atom_end();
}

void QTRecord::Write_moov(void)
{
 atom_begin("moov");

 Write_mvhd();

 OnAudioTrack = false;
 Write_trak();

 if(SoundRate && SoundChan)
 {
  OnAudioTrack = true;
  Write_trak();
 }

 Write_udta();

 atom_end();
}

void QTRecord::Finish(void)
{
 if(Finished)
  return;

 Finished = true;

 atom_end();

 Write_moov();

 qtfile.close();
}

QTRecord::~QTRecord(void)
{
 try
 {
  Finish();
 }
 catch(std::exception &e)
 {
  MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
 }

 if(resampler)
 {
  speex_resampler_destroy(resampler);
  resampler = NULL;
 }
}

}
