

#ifndef __LP64__

#include <iostream>

#include "texception.h"
#include "tsound.h"
#include "tconvert.h"
#include "tpropertytype.h"
#include "timageinfo.h"
#include "trasterimage.h"
#include "tmachine.h"
#include "tsystem.h"

// movesettings deps, must be included before QuickTime includes
#include "texception.h"
#include "tpropertytype.h"
#include "tproperty.h"

// following includes may include QuickTime
#include "tiio_movM.h"
#include "movsettings.h"

/* QuickDraw は 10.7 以降なくなった */
//#define HAS_QUICKDRAW

namespace {

const string firstFrameKey  = "frst";
const int firstFrameKeySize = 4 * sizeof(char);

//------------------------------------------------------------------------------

class QuickTimeCleanUp;
class QuickTimeStuff {
public:
  static QuickTimeStuff *instance() {
    if (!m_singleton) m_singleton = new QuickTimeStuff();
    return m_singleton;
  }
  OSErr getStatus() { return m_status; }
  ~QuickTimeStuff() {}

private:
  QuickTimeStuff() : m_status(noErr) {
    m_status = noErr;
    EnterMovies();
  }

  static QuickTimeStuff *m_singleton;

  OSErr m_status;
  friend class QuickTimeCleanUp;  // questa DEVE essere friend, cosi' posso
                                  // controllare direttamente
  // lo stato del singleton.
};

class QuickTimeCleanUp {
public:
  QuickTimeCleanUp() {}
  ~QuickTimeCleanUp() { /*
                                      Nel caso si arrivasse qui senza il
                           singleton instanziato, e si facesse direttamente
                                      'delete QuickTimeStuff::instance();'
                           Quicktime non farebbe in tempo a terminare le
                                      sue routine di inizializzazione (che sono
                           ANCHE su altri thread) e la chiamata a
                                      TerminateQTML() causerebbe un crash
                                      */
    // std::cout << "destroying variable... Thread = " << GetCurrentThreadId() <<
    // std::endl;

    //              std::cout <<"~QTCleanup"<< std::endl;
    if (QuickTimeStuff::m_singleton) delete QuickTimeStuff::m_singleton;
  }
};

QuickTimeCleanUp cleanUp;
QuickTimeStuff *QuickTimeStuff::m_singleton = 0;

//------------------------------------------------------------------------------

enum QTLibError {
  QTNoError = 0x0000,
  QTNotInstalled,
  QTUnknownError,
  QTUnableToOpenFile,
  QTCantCreateFile,
  QTUnableToGetCompressorNames,
  QTUnableToCreateResource,
  QTUnableToUpdateMovie,
  QTBadTimeValue,
  QTUnableToDoMovieTask,
  QTUnableToSetTimeValue,
  QTUnableToSetMovieGWorld,
  QTUnableToSetMovieBox,
};

}  // namespace

////-----------------------------------------------------------
//
// CodecType Tiio::MovWriterProperties::getCurrentCodec()const
//{
// std::map<wstring, CodecType>::const_iterator it;
//
// it = m_codecMap.find(m_codec.getValue());
// assert(it!=m_codecMap.end());
// return it->second;
//}
//
////-----------------------------------------------------------
//
// wstring Tiio::MovWriterProperties::getCurrentNameFromCodec(CodecType
// &cCodec)const
//{
// std::map<wstring, CodecType>::const_iterator it;
//
// for(it=m_codecMap.begin();it!=m_codecMap.end();++it)
//{
////  CodecType tmp=it->second;
//  if(it->second==cCodec) break;
//}
// assert(it!=m_codecMap.end());
// return it->first;
//}
//
////-----------------------------------------------------------
//
// wstring Tiio::MovWriterProperties::getCurrentQualityFromCodeQ(CodecQ
// &cCodecQ)const
//{
// std::map<wstring, CodecQ>::const_iterator it;
//
// for(it=m_qualityMap.begin();it!=m_qualityMap.end();++it)
//{
////  CodecQ tmp=it->second;
//  if(it->second==cCodecQ) break;
//}
// assert(it!=m_qualityMap.end());
// return it->first;
//}
//
////-----------------------------------------------------------
//
// CodecQ Tiio::MovWriterProperties::getCurrentQuality() const
//{
// std::map<wstring, CodecQ>::const_iterator it;
//
// it = m_qualityMap.find(m_quality.getValue());
// assert(it!=m_qualityMap.end());
// return it->second;
//}
//
////-----------------------------------------------------------
//
//
// TPropertyGroup* Tiio::MovWriterProperties::clone() const
//{
//  MovWriterProperties*g = new MovWriterProperties();
//
//	g->m_codec.setValue(m_codec.getValue());
//	g->m_quality.setValue(m_quality.getValue());
//
// return (TPropertyGroup*)g;
//}

Tiio::MovWriterProperties::MovWriterProperties()
//: m_codec("Codec")
//, m_quality("Quality")
{
  if (QuickTimeStuff::instance()->getStatus() != noErr) {
    return;
  }

  // if (InitializeQTML(0)!=noErr)
  //    return;

  ComponentInstance ci =
      OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
  QTAtomContainer settings;

  if (SCGetSettingsAsAtomContainer(ci, &settings) != noErr) assert(false);

  fromAtomsToProperties(settings, *this);

  /*
CodecNameSpecListPtr codecList;
if (GetCodecNameList(&codecList, 0) !=noErr) return;

TIntEnumeration codecNames;
for (short i = 0; i < codecList->count; ++i)
{
CodecNameSpec *codecNameSpec = &(codecList->list[i]);
// codecNameSpec->typeName contains the name of the codec in Pascal String
Format
// byte 0 = Len
// byte [1..Len] data
UCHAR len = codecNameSpec->typeName[0];
string name((char*)&codecNameSpec->typeName[1], len);
  m_codec.addValue(toWideString(name));
  if (i==0) m_codec.setValue(toWideString(name));
  m_codecMap[toWideString(name)] = codecNameSpec->cType;

}

DisposeCodecNameList(codecList);

TIntEnumeration  codecQuality;

m_quality.addValue(L"min quality");
m_qualityMap[L"min quality"] = codecMinQuality;
m_quality.addValue(L"low quality");
m_qualityMap[L"low quality"] = codecLowQuality;
m_quality.addValue(L"normal quality");
m_qualityMap[L"normal quality"] = codecNormalQuality;
m_quality.addValue(L"high quality");
m_qualityMap[L"high quality"] = codecHighQuality;
m_quality.addValue(L"max quality");
m_qualityMap[L"max quality"] = codecMaxQuality;
m_quality.addValue(L"lossless quality");
m_qualityMap[L"lossless quality"] = codecLosslessQuality;

m_quality.setValue(L"normal quality");

bind(m_codec);
bind(m_quality);
*/
  /*
m_qualityTable.insert(QualityTable::value_type(q++, codecMinQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecLowQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecMaxQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecNormalQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecHighQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecLosslessQuality));

addProperty(CodecQualityId, codecQuality,0);*/
}

namespace {

string buildQTErrorString(int ec) {
  switch (ec) {
  case QTNotInstalled:
    return "Can't create; ensure that quicktime is correctly installed on your "
           "machine";
  case QTUnknownError:
    return "Unknown error";
  case QTUnableToOpenFile:
    return "can't open file";
  case QTCantCreateFile:
    return "can't create movie";
  case QTUnableToGetCompressorNames:
    return "unable to get compressor name";
  case QTUnableToCreateResource:
    return "can't create resource";
  case QTUnableToUpdateMovie:
    return "unable to update movie";
  case QTBadTimeValue:
    return "bad frame number";
  case QTUnableToDoMovieTask:
    return "unable to do movie task";
  case QTUnableToSetTimeValue:
    return "unable to set time value";
  case QTUnableToSetMovieGWorld:
    return "unable to set movie graphic world";
  case QTUnableToSetMovieBox:
    return "unable to set movie box";

  default: { return "unknown error ('" + std::to_string(ec) + "')"; }
  }
}

//-----------------------------------------------------------
#ifdef WIN2
inline LPSTR AtlW2AHelper(LPSTR lpa, LPCWSTR lpw, int nChars, UINT acp) {
  assert(lpw != NULL);
  assert(lpa != NULL);
  // verify that no illegal character present
  // since lpa was allocated based on the size of lpw
  // don't worry about the number of chars
  lpa[0] = '\0';
  WideCharToMultiByte(acp, 0, lpw, -1, lpa, nChars, NULL, NULL);
  return lpa;
}
#endif
//-----------------------------------------------------------

string long2fourchar(TINT32 fcc) {
  string s;
  s += (char((fcc & 0xff000000) >> 24));
  s += (char((fcc & 0x00ff0000) >> 16));
  s += (char((fcc & 0x0000ff00) >> 8));
  s += (char((fcc & 0x000000ff) >> 0));

  return s;
}

//-------------------------------------------------------------------------------

TimeScale frameRateToTimeScale(double frameRate) {
  return tround(frameRate * 100.0);
}

//-------------------------------------------------------------------------------

const string CodecNamesId   = "PU_CodecName";
const string CodecQualityId = "PU_CodecQuality";

}  // namespace

//------------------------------------------------------------------------------

bool IsQuickTimeInstalled() {
  return QuickTimeStuff::instance()->getStatus() == noErr;
}

//------------------------------------------------------------------------------
//  TImageWriterMov
//------------------------------------------------------------------------------

class TImageWriterMov : public TImageWriter {
public:
  TImageWriterMov(const TFilePath &, int frameIndex, TLevelWriterMov *);
  ~TImageWriterMov() { m_lwm->release(); }
  bool is64bitOutputSupported() { return false; }

private:
  // not implemented
  TImageWriterMov(const TImageWriterMov &);
  TImageWriterMov &operator=(const TImageWriterMov &src);

public:
  void save(const TImageP &);
  int m_frameIndex;

private:
  TLevelWriterMov *m_lwm;
};

//-----------------------------------------------------------
//  TImageReaderMov
//-----------------------------------------------------------
class TImageReaderMov : public TImageReader {
public:
  TImageReaderMov(const TFilePath &, int frameIndex, TLevelReaderMov *);
  ~TImageReaderMov() { m_lrm->release(); }

private:
  // not implemented
  TImageReaderMov(const TImageReaderMov &);
  TImageReaderMov &operator=(const TImageReaderMov &src);

public:
  TImageP load();
  void load(const TRasterP &rasP, const TPoint &pos = TPoint(0, 0),
            int shrinkX = 1, int shrinkY = 1);
  int m_frameIndex;

  TDimension getSize() const { return m_lrm->getSize(); }
  TRect getBBox() const { return m_lrm->getBBox(); }

private:
  TLevelReaderMov *m_lrm;
};

//-----------------------------------------------------------
//-----------------------------------------------------------
//        TImageWriterMov
//-----------------------------------------------------------

TImageWriterMov::TImageWriterMov(const TFilePath &path, int frameIndex,
                                 TLevelWriterMov *lwm)
    : TImageWriter(path), m_lwm(lwm), m_frameIndex(frameIndex) {
  m_lwm->addRef();
}

//----------
namespace {

void copy(TRasterP rin, PixelXRGB *bufout, int lx, int ly,
          unsigned short rowbytes = 0) {
  rin->lock();
  TRaster32P rin32 = rin;
  assert(rin32);
  int lineOffset =
      (rowbytes == 0)
          ? lx
          : rowbytes / sizeof(PixelXRGB);  // in pixelsize, cioe 4 bytes

  PixelXRGB *rowout = &(bufout[(ly - 1) * lineOffset]) /*-startOffset*/;
  for (int y = 0; y < rin32->getLy(); y++) {
    PixelXRGB *pixout      = rowout;
    TPixelRGBM32 *pixin    = rin32->pixels(y);
    TPixelRGBM32 *pixinEnd = pixin + rin32->getLx();
    while (pixin < pixinEnd) {
      pixout->x = pixin->m;
      pixout->r = pixin->r;
      pixout->g = pixin->g;
      pixout->b = pixin->b;
      pixout++;
      pixin++;
    }
    rowout -= lineOffset;
  }
  rin->unlock();
}  // end function
};
//-----------------------------------------------------------
/*
TWriterInfo *TWriterInfoMov::create(const string &)
{
return new TWriterInfoMov();
}

//-----------------------------------------------------------

TWriterInfoMov::TWriterInfoMov(const TWriterInfoMov&src)
               :TWriterInfo(src)
               ,m_codecTable(src.m_codecTable)
               ,m_qualityTable(src.m_qualityTable)
{
}

//-----------------------------------------------------------

TWriterInfo *TWriterInfoMov::clone() const
{
return new TWriterInfoMov(*this);
}

//-----------------------------------------------------------

TWriterInfoMov::TWriterInfoMov()
               :TWriterInfo()
{
if (QuickTimeStuff::instance()->getStatus()!=noErr)
  {
  return;
  }

CodecNameSpecListPtr codecList;

if (GetCodecNameList(&codecList, 0) !=noErr)
  {
  return;
  }

TIntEnumeration codecNames;
for (short i = 0; i < codecList->count; ++i)
  {
  CodecNameSpec *codecNameSpec = &(codecList->List[i]);
  // codecNameSpec->typeName contains the name of the codec in Pascal String
Format
  // byte 0 = Len
  // byte [1..Len] data
  UCHAR len = codecNameSpec->typeName[0];
  string name((char*)&codecNameSpec->typeName[1], len);

  codecNames.addItem(name, i);

  m_codecTable.insert(CodecTable::value_type(i, codecNameSpec->cType));
  }

DisposeCodecNameList(codecList);

addProperty(CodecNamesId, codecNames, 0);
int q = 0;

TIntEnumeration  codecQuality;

codecQuality.addItem("min quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecMinQuality));

codecQuality.addItem("low quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecLowQuality));

codecQuality.addItem("max quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecMaxQuality));

codecQuality.addItem("normal quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecNormalQuality));

codecQuality.addItem("high quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecHighQuality));

codecQuality.addItem("lossless quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecLosslessQuality));

addProperty(CodecQualityId, codecQuality,0);

}

TWriterInfoMov::~TWriterInfoMov()
{}

*/
//-----------------------------------------------------------

void TImageWriterMov::save(const TImageP &img) {
  m_lwm->save(img, m_frameIndex);
}

//-----------------------------------------------------------

namespace {
bool getFSRefFromPosixPath(const char *filepath, FSRef *fileRef, bool writing) {
  const UInt8 *filename = (const UInt8 *)filepath;
  if (writing) {
    FILE *fp = fopen(filepath, "wb");
    if (!fp) return false;
    fclose(fp);
  }
  OSErr err = FSPathMakeRef(filename, fileRef, 0);
  return err == noErr;
}

bool getFSSpecFromPosixPath(const char *filepath, FSSpec *fileSpec,
                            bool writing) {
  FSRef fileRef;

  if (!getFSRefFromPosixPath(filepath, &fileRef, writing)) return false;
  OSErr err = FSGetCatalogInfo(&fileRef, kFSCatInfoNone, 0, 0, fileSpec, 0);
  return err == noErr;
}
}

void TLevelWriterMov::save(const TImageP &img, int frameIndex) {
  m_firstFrame = tmin(frameIndex, m_firstFrame);

  TRasterImageP image(img);
  if (!image) throw TImageException(getFilePath(), "Unsupported image type");

  int lx = image->getRaster()->getLx();
  int ly = image->getRaster()->getLy();
  // void *buffer = image->getRaster()->getRawData();

  int pixSize = image->getRaster()->getPixelSize();
  if (pixSize != 4)
    throw TImageException(getFilePath(), "Unsupported pixel type");

  QMutexLocker sl(&m_mutex);
  if (!m_properties) m_properties = new Tiio::MovWriterProperties();

  Tiio::MovWriterProperties *prop = (Tiio::MovWriterProperties *)(m_properties);

  // CodecType codecType;
  // try {
  // codecType = prop->getCurrentCodec();
  //  } catch (...)//boost::bad_any_cast&)
  //    {
  //    throw TImageException(m_path, "bad codec name");
  //    }
  //
  // CodecQ quality = prop->getCurrentQuality();
  QDErr err;

  if (m_videoTrack == 0) {
    QTAtomContainer atoms;
    QTNewAtomContainer(&atoms);
    fromPropertiesToAtoms(*prop, atoms);
    m_ci = OpenDefaultComponent(StandardCompressionType,
                                StandardCompressionSubType);
    if (SCSetSettingsFromAtomContainer(m_ci, atoms) != noErr) {
      CloseComponent(m_ci);
      assert(!"cannot use that quickTime codec, use default");
      m_ci = OpenDefaultComponent(StandardCompressionType,
                                  StandardCompressionSubType);
    }

    QTDisposeAtomContainer(atoms);

    // FSSpec fspec;
    Rect frame;
    long max_compressed_size;

    if (QuickTimeStuff::instance()->getStatus() != noErr) {
      m_IOError = QTNotInstalled;
      throw TImageException(m_path, buildQTErrorString(m_IOError));
    }

    // First, set the movie's time scale to match a suitable multiple of
    // m_frameRate.
    // The timeScale will be used for both the movie and media.
    TimeScale timeScale = ::frameRateToTimeScale(m_frameRate);
    SetMovieTimeScale(m_movie, timeScale);

    m_videoTrack = NewMovieTrack(m_movie, FixRatio((short)lx, 1),
                                 FixRatio((short)ly, 1), kNoVolume);

    if ((err = GetMoviesError() != noErr))
      throw TImageException(getFilePath(), "can't create video track");

    m_videoMedia = NewTrackMedia(m_videoTrack, VideoMediaType, timeScale, 0, 0);

    if ((err = GetMoviesError() != noErr))
      throw TImageException(getFilePath(), "can't create video media");

    frame.left   = 0;
    frame.top    = 0;
    frame.right  = lx;
    frame.bottom = ly;

    if ((err = QTNewGWorld(&(m_gworld), pixSize * 8, &frame, 0, 0, 0)) != noErr)
      throw TImageException(getFilePath(), "can't create movie buffer");

    //#ifdef WIN32
    //  LockPixels(m_gworld->portPixMap);
    //  if ((err = GetMaxCompressionSize(m_gworld->portPixMap, &frame, 0,
    //                                quality,  compression,anyCodec,
    //                         	 &max_compressed_size))!=noErr)
    //    throw TImageException(getFilePath(), "can't get max compression
    //    size");
    //
    //#else
    //  PixMapHandle pixmapH = GetPortPixMap (m_gworld);
    //
    //  LockPixels(pixmapH);
    //  if ((err = GetMaxCompressionSize(pixmapH, &frame, 0,
    //                                quality,  codecType,anyCodec,
    //				 &max_compressed_size))!=noErr)
    //    throw TImageException(getFilePath(), "can't get max compression
    //    size");
    //
    //#endif

    // m_compressedData = NewHandle(max_compressed_size);

    if ((err = MemError()) != noErr)
      throw TImageException(getFilePath(),
                            "can't allocate compressed data for movie");

    // MoveHHi(m_compressedData);
    // HLock(m_compressedData);

    if ((err = MemError()) != noErr)
      throw TImageException(getFilePath(), "can't allocate img handle");

/* FIXME: QuickDraw とともに無くなったようだ */
#if defined(HAS_QUICKDRAW)
    m_pixmap = GetGWorldPixMap(m_gworld);

    if (!LockPixels(m_pixmap))
      throw TImageException(getFilePath(), "can't lock pixels");

    buf = (PixelXRGB *)GetPixBaseAddr(m_pixmap);
#endif
    buf_lx = lx;
    buf_ly = ly;
  }

  if ((err = BeginMediaEdits(m_videoMedia)) != noErr)
    throw TImageException(getFilePath(), "can't begin edit video media");

  unsigned short rowBytes =
      (unsigned short)(((short)(*(m_pixmap))->rowBytes & ~(3 << 14)));

  Rect frame;
  ImageDescriptionHandle img_descr = 0;
  Handle compressedData            = 0;

  frame.left   = 0;
  frame.top    = 0;
  frame.right  = lx;
  frame.bottom = ly;

  TRasterP ras = image->getRaster();

#ifdef WIN32
  compressed_data_ptr = StripAddress(*(m_compressedData));
  copy(ras, buf, buf_lx, buf_ly);
#else
  // compressed_data_ptr  = *m_compressedData;
  copy(ras, buf, buf_lx, buf_ly, rowBytes);
#endif

// img_descr = (ImageDescriptionHandle)NewHandle(4);

#ifdef WIN32
  if ((err = CompressImage(m_gworld->portPixMap, &frame, quality, compression,
                           img_descr, compressed_data_ptr)) != noErr)
    throw TImageException(getFilePath(), "can't compress image");
#else

#if defined(HAS_QUICKDRAW)
  PixMapHandle pixmapH = GetPortPixMap(m_gworld);

  if ((err = SCCompressImage(m_ci, pixmapH, &frame, &img_descr,
                             &compressedData)) != noErr)
    throw TImageException(getFilePath(), "can't compress image");
#endif

// if ((err = CompressImage(pixmapH,
//	                 &frame,
//  			 quality, codecType,
//			 img_descr, compressed_data_ptr))!=noErr)
//  throw TImageException(getFilePath(), "can't compress image");

#endif

  TimeValue sampleTime;
  if ((err = AddMediaSample(
           m_videoMedia, compressedData, 0, (*img_descr)->dataSize, 100,
           (SampleDescriptionHandle)img_descr, 1, 0, &sampleTime)) != noErr)
    throw TImageException(getFilePath(), "can't add image to movie media");

  if ((err = EndMediaEdits(m_videoMedia)) != noErr)
    throw TImageException(getFilePath(), "can't end edit media");

  DisposeHandle(compressedData);

  DisposeHandle((Handle)img_descr);

  m_savedFrames.push_back(std::pair<int, TimeValue>(frameIndex, sampleTime));
}

//-----------------------------------------------------------
//        TLevelWriterMov
//-----------------------------------------------------------

TLevelWriterMov::TLevelWriterMov(const TFilePath &path, TPropertyGroup *winfo)
    : TLevelWriter(path, winfo)
    , m_IOError(QTNoError)
    , m_pixmap(0)
    //,m_compressedData(0)
    , m_gworld(0)
    , m_videoMedia(0)
    , m_videoTrack(0)
    , m_soundMedia(0)
    , m_soundTrack(0)
    , m_movie(0)
    , m_firstFrame((std::numeric_limits<int>::max)()) {
  m_frameRate = 12.;
  QDErr err;
  FSSpec fspec;
  if (QuickTimeStuff::instance()->getStatus() != noErr) {
    m_IOError = QTNotInstalled;
    throw TImageException(m_path, buildQTErrorString(m_IOError));
  }

  QString qStr     = QString::fromStdWString(m_path.getWideString());
  const char *pStr = qStr.toUtf8().data();

  if (!getFSSpecFromPosixPath(pStr, &fspec, true)) {
    m_IOError = QTUnableToOpenFile;
    throw TImageException(m_path, buildQTErrorString(m_IOError));
  }

  // Fix: in alcuni rari casi non veniva il mov preesistente
  if (TSystem::doesExistFileOrLevel(m_path)) TSystem::deleteFile(m_path);

  if ((err = CreateMovieFile(&fspec, 'TVOD', smCurrentScript,
                             createMovieFileDeleteCurFile |
                                 createMovieFileDontCreateResFile,
                             &(m_refNum), &(m_movie)) != noErr)) {
    m_IOError = QTCantCreateFile;
    throw TImageException(m_path, buildQTErrorString(m_IOError));
  }
}

//-----------------------------------------------------------

#ifdef _DEBUG
#define FailIf(cond, handler)                                                  \
  if (cond) {                                                                  \
    /*		DebugStr((ConstStr255Param)#cond " goto " #handler);*/                 \
    goto handler;                                                              \
  }
#else
#define FailIf(cond, handler)                                                  \
  if (cond) {                                                                  \
    goto handler;                                                              \
  }
#endif

#ifdef _DEBUG
#define C(cond, action, handler)                                               \
  if (cond) {                                                                  \
    /*		DebugStr((ConstStr255Param)#cond " goto " #handler);*/                 \
    { action; }                                                                \
    goto handler;                                                              \
  }
#else
#define FailWithAction(cond, action, handler)                                  \
  if (cond) {                                                                  \
    { action; }                                                                \
    goto handler;                                                              \
  }
#endif

void TLevelWriterMov::saveSoundTrack(TSoundTrack *st) {
  OSErr myErr = noErr;

  // int err;

  if (!st) throw TException("null reference to soundtrack");

  if (st->getBitPerSample() != 16) {
    throw TImageException(m_path, "Only 16 bits per sample is supported");
  }

  if (m_soundTrack == 0) {
    m_soundTrack = NewMovieTrack(m_movie, 0, 0, kFullVolume);
    myErr        = GetMoviesError();

    FailIf(myErr != noErr, CompressErr);

    m_soundMedia =
        NewTrackMedia(m_soundTrack, SoundMediaType, st->getSampleRate(), NULL,
                      0);  // track->rate >> 16
    myErr = GetMoviesError();
    FailIf(myErr != noErr, Exit);
  }

  SoundDescriptionV1Handle mySampleDesc;
  Handle myDestHandle;
  SoundComponentData sourceInfo;
  SoundComponentData destInfo;
  SoundConverter converter;
  CompressionInfo compressionInfo;

  myDestHandle = NewHandle(0);
  FailWithAction(myDestHandle == NULL, myErr = MemError(), NoDest);

  *myDestHandle = (char *)st->getRawData();

  // start a media editing session
  myErr = BeginMediaEdits(m_soundMedia);
  FailIf(myErr != noErr, Exit);

  sourceInfo.flags       = 0x0;
  sourceInfo.format      = kSoundNotCompressed;
  sourceInfo.numChannels = st->getChannelCount();
  sourceInfo.sampleSize  = st->getBitPerSample();
  sourceInfo.sampleRate  = st->getSampleRate();
  sourceInfo.sampleCount = st->getSampleCount();
  sourceInfo.buffer      = (unsigned char *)st->getRawData();
  sourceInfo.reserved    = 0x0;

  destInfo.flags = kNoSampleRateConversion | kNoSampleSizeConversion |
                   kNoSampleFormatConversion | kNoChannelConversion |
                   kNoDecompression | kNoVolumeConversion |
                   kNoRealtimeProcessing;

  destInfo.format = k16BitNativeEndianFormat;

  destInfo.numChannels = st->getChannelCount();
  destInfo.sampleSize  = st->getBitPerSample();
  destInfo.sampleRate  = st->getSampleRate();
  destInfo.sampleCount = st->getSampleCount();
  destInfo.buffer      = (unsigned char *)st->getRawData();
  destInfo.reserved    = 0x0;

  SoundConverterOpen(&sourceInfo, &destInfo, &converter);

  myErr =
      SoundConverterGetInfo(converter, siCompressionFactor, &compressionInfo);

  myErr =
      SoundConverterGetInfo(converter, siCompressionFactor, &compressionInfo);
  myErr = GetCompressionInfo(fixedCompression, sourceInfo.format,
                             sourceInfo.numChannels, sourceInfo.sampleSize,
                             &compressionInfo);
  FailIf(myErr != noErr, ConverterErr);

  compressionInfo.bytesPerFrame =
      compressionInfo.bytesPerPacket * destInfo.numChannels;

  //////////
  //
  // create a sound sample description
  //
  //////////

  // use the SoundDescription format 1 because it adds fields for data size
  // information
  // and is required by AddSoundDescriptionExtension if an extension is required
  // for the compression format

  mySampleDesc =
      (SoundDescriptionV1Handle)NewHandleClear(sizeof(SoundDescriptionV1));
  FailWithAction(myErr != noErr, myErr = MemError(), Exit);

  (**mySampleDesc).desc.descSize      = sizeof(SoundDescriptionV1);
  (**mySampleDesc).desc.dataFormat    = destInfo.format;
  (**mySampleDesc).desc.resvd1        = 0;
  (**mySampleDesc).desc.resvd2        = 0;
  (**mySampleDesc).desc.dataRefIndex  = 1;
  (**mySampleDesc).desc.version       = 1;
  (**mySampleDesc).desc.revlevel      = 0;
  (**mySampleDesc).desc.vendor        = 0;
  (**mySampleDesc).desc.numChannels   = destInfo.numChannels;
  (**mySampleDesc).desc.sampleSize    = destInfo.sampleSize;
  (**mySampleDesc).desc.compressionID = 0;
  (**mySampleDesc).desc.packetSize    = 0;
  (**mySampleDesc).desc.sampleRate    = st->getSampleRate() << 16;
  (**mySampleDesc).samplesPerPacket   = compressionInfo.samplesPerPacket;
  (**mySampleDesc).bytesPerPacket     = compressionInfo.bytesPerPacket;
  (**mySampleDesc).bytesPerFrame      = compressionInfo.bytesPerFrame;
  (**mySampleDesc).bytesPerSample     = compressionInfo.bytesPerSample;

  //////////
  //
  // add samples to the media
  //
  //////////

  /*if ((err = AddMediaSample(m_videoMedia, compressedData,  0,
                          (*img_descr)->dataSize, 1,
                                                                                            (SampleDescriptionHandle)img_descr,
                                                                                                  1, 0, 0))!=noErr)
throw TImageException(getFilePath(), "can't add image to movie media");*/

  myErr = AddMediaSample(
      m_soundMedia, myDestHandle, 0,
      destInfo.sampleCount * compressionInfo.bytesPerFrame, 1,
      (SampleDescriptionHandle)mySampleDesc,
      destInfo.sampleCount * compressionInfo.samplesPerPacket, 0, NULL);
  FailIf(myErr != noErr, MediaErr);

  myErr = EndMediaEdits(m_soundMedia);
  FailIf(myErr != noErr, MediaErr);

  //////////
  //
  // insert the media into the track
  //
  //////////

  goto Done;

ConverterErr:
NoDest:
CompressErr:
Exit:

Done:

MediaErr:
  if (mySampleDesc != NULL) DisposeHandle((Handle)mySampleDesc);

  if (converter) SoundConverterClose(converter);

  if (myErr != noErr) throw TImageException(m_path, "error saving audio track");
}

//-----------------------------------------------------------

TLevelWriterMov::~TLevelWriterMov() {
/* FIXME: */
#if defined(HAS_QUICKDRAW)
  if (m_pixmap) UnlockPixels(m_pixmap);
  if (m_gworld) DisposeGWorld(m_gworld);
#endif
  if (m_ci) CloseComponent(m_ci);

  QDErr err;
  OSStatus mderr;

  if (m_videoTrack) {
    // Insert the saved Frames into the track. Holes in the frames sequence will
    // be intended as still
    // frames.
    TimeValue movieTimeScale = GetMovieTimeScale(m_movie);
    int savedFramesSize      = (int)m_savedFrames.size() - 1;

    int i;
    for (i = 0; i < savedFramesSize; ++i) {
      TimeValue mediaPosition = m_savedFrames[i].second;
      TimeValue trackPosition =
          ((m_savedFrames[i].first - m_firstFrame) * movieTimeScale) /
          m_frameRate;

      TimeValue duration = m_savedFrames[i + 1].first - m_savedFrames[i].first;
      Fixed ratio        = tround(fixed1 / (double)duration);

      if ((err = InsertMediaIntoTrack(m_videoTrack, trackPosition,
                                      mediaPosition, 100, ratio)) != noErr)
        throw TImageException(getFilePath(), "can't insert media into track");
    }

    if (savedFramesSize >= 0) {
      TimeValue mediaPosition = m_savedFrames[i].second;
      TimeValue trackPosition =
          ((m_savedFrames[i].first - m_firstFrame) * movieTimeScale) /
          m_frameRate;

      // The last frame has duration 1 (frame):
      //   duration = 1  =>  ratio = fixed1

      if ((err = InsertMediaIntoTrack(m_videoTrack, trackPosition,
                                      mediaPosition, 100, fixed1)) != noErr)
        throw TImageException(getFilePath(), "can't insert media into track");
    }

    QTMetaDataRef metaDataRef;
    if ((mderr = QTCopyMovieMetaData(m_movie, &metaDataRef)) != noErr)
      throw TImageException(getFilePath(),
                            "can't access metadata information");

    if ((mderr = QTMetaDataAddItem(
             metaDataRef, kQTMetaDataStorageFormatUserData,
             kQTMetaDataKeyFormatUserData, (const UInt8 *)firstFrameKey.c_str(),
             firstFrameKeySize, (const UInt8 *)(&m_firstFrame), sizeof(int),
             kQTMetaDataTypeUnsignedIntegerBE, 0)) != noErr)
      throw TImageException(getFilePath(),
                            "can't insert metadata information");

    QTMetaDataRelease(metaDataRef);
  }

  if (m_soundTrack) {
    if (0 != (err = InsertMediaIntoTrack(
                  m_soundTrack, 0, 0, GetMediaDuration(m_soundMedia), fixed1)))
      throw TImageException(getFilePath(), "can't insert sound into track");
    // FailIf(err != noErr, MediaErr);
  }

  short resId = movieInDataForkResID;
  if (m_movie)
    if ((err = AddMovieResource(m_movie, m_refNum, &resId, 0)) != noErr) {
    }

/**************************************************************************************/
#ifdef PROVA3GP
  QString qStr        = QString::fromStdWString(m_path.getWideString());
  char *inputFileName = qStr.toUtf8().data();

  OSType movieType = FOUR_CHAR_CODE('3gpp');
  OSType creator   = 'TVOD';
  Movie movie;

  // get the file
  FSSpec inFsSpec;
  // NativePathNameToFSSpec(inputFileName, &inFsSpec, 0);
  FSMakeFSSpec(0, 0, inputFileName, &inFsSpec);
  getFSSpecFromPosixPath(pStr, &inFsSpec, false);

  // Initialize Quicktime Movie Toolbox
  // EnterMovies ();

  // open the movie
  short sResRefNum;
  OpenMovieFile(&inFsSpec, &sResRefNum, fsRdPerm);
  NewMovieFromFile(&movie, sResRefNum, nil, nil, 0, nil);
  CloseMovieFile(sResRefNum);

  UCHAR myCancelled = FALSE;
  MovieExportComponent m_myExporter;
  OpenADefaultComponent(MovieExportType, '3gpp', &m_myExporter);
  err = (short)MovieExportDoUserDialog(m_myExporter, movie, 0, 0, 0,
                                       &myCancelled);

  if ((err != noErr)) throw TImageException(getFilePath(), "can't export 3gp");

  FSSpec fspec;
  FSMakeFSSpec(0, 0, "C:\\temp\\out.3gp", &fspec);
  getFSSpecFromPosixPath(pStr, &fspec, false);

  // NativePathNameToFSSpec	("C:\\temp\\out.3gp", &fspec, kFullNativePath);

  long myFlags = 0L;
  myFlags      = createMovieFileDeleteCurFile;  // |
  // movieFileSpecValid | movieToFileOnlyExport;

  err = ConvertMovieToFile(movie,                   // the movie to convert
                           0,                       // all tracks in the movie
                           &fspec,                  // the output file
                           FOUR_CHAR_CODE('3gpp'),  // the output file type
                           FOUR_CHAR_CODE('TVOD'),  // the output file creator
                           smSystemScript,          // the script
                           0,              // no resource ID to be returned
                           myFlags,        // export flags
                           m_myExporter);  // no specific exp
}
if (sResRefNum) CloseMovieFile(sResRefNum);
DisposeMovie(movie);
}
#endif

if (m_refNum) CloseMovieFile(m_refNum);
DisposeMovie(m_movie);
}

//-----------------------------------------------------------

TImageWriterP TLevelWriterMov::getFrameWriter(TFrameId fid) {
  if (m_IOError) throw TImageException(m_path, buildQTErrorString(m_IOError));
  if (fid.getLetter() != 0) return TImageWriterP(0);
  int index = fid.getNumber() - 1;

  TImageWriterMov *iwm = new TImageWriterMov(m_path, index, this);
  return TImageWriterP(iwm);
}

//-----------------------------------------------------------

TLevelReaderMov::TLevelReaderMov(const TFilePath &path)
    : TLevelReader(path)
    , m_IOError(QTNoError)
    , m_track(0)
    , m_movie(0)
    , m_depth(0)
    , m_readAsToonzOutput(false) {
  FSSpec fspec;
  QDErr err;
  Boolean dataRefWasChanged;
  if (QuickTimeStuff::instance()->getStatus() != noErr) {
    m_IOError = QTNotInstalled;
    return;
  }

  QString qStr = QString::fromStdWString(m_path.getWideString());
  char *pStr = qStr.toUtf8().data();

  getFSSpecFromPosixPath(pStr, &fspec, false);
  pStr = 0;

  if ((err = OpenMovieFile(&fspec, &m_refNum, fsRdPerm))) {
    m_IOError = QTUnableToOpenFile;
    return;
  }

  m_resId = 0;
  Str255 name;
  err = NewMovieFromFile(&m_movie, m_refNum, &m_resId, name, fsRdPerm,
                         &dataRefWasChanged);

  int numTracks = GetMovieTrackCount(m_movie);
  // assert(numTracks==1 || numTracks ==2);
  m_track =
      GetMovieIndTrackType(m_movie, 1, VideoMediaType, movieTrackMediaType);

  // m_track=GetMovieTrack(m_movie,numTracks);

  ImageDescriptionHandle imageH;
  imageH = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
  TINT32 index   = 1;
  Media theMedia = GetTrackMedia(m_track);

  GetMediaSampleDescription(theMedia, index, (SampleDescriptionHandle)imageH);
  ImageDescriptionPtr imagePtr    = *imageH;
  m_lx                            = imagePtr->width;
  m_ly                            = imagePtr->height;
  m_depth                         = imagePtr->depth;
  m_info                          = new TImageInfo();
  m_info->m_lx                    = m_lx;
  m_info->m_ly                    = m_ly;
  Tiio::MovWriterProperties *prop = new Tiio::MovWriterProperties();
  m_info->m_properties            = prop;

  DisposeHandle((Handle)imageH);

  m_info->m_frameRate = GetMediaTimeScale(theMedia) / 100.0;
}

//------------------------------------------------

//------------------------------------------------

//------------------------------------------------
// TImageReaderMov
//------------------------------------------------

TImageReaderMov::TImageReaderMov(const TFilePath &path, int frameIndex,
                                 TLevelReaderMov *lrm)
    : TImageReader(path), m_lrm(lrm), m_frameIndex(frameIndex) {
  m_lrm->addRef();
}

//------------------------------------------------

TLevelReaderMov::~TLevelReaderMov() {
  StopMovie(m_movie);

  if (m_refNum) CloseMovieFile(m_refNum);
  if (m_movie) DisposeMovie(m_movie);
}

//------------------------------------------------

TLevelP TLevelReaderMov::loadInfo() {
  if (m_readAsToonzOutput) return loadToonzOutputFormatInfo();

  TLevelP level;
  if (m_IOError != QTNoError)
    throw TImageException(m_path, buildQTErrorString(m_IOError));

  OSType mediaType = VisualMediaCharacteristic;
  TimeValue nextTime, currentTime;
  currentTime = 0;
  nextTime    = 0;
  // per il primo
  int f = 0;
  // io vorrei fare '|', ma sul manuale c'e' scritto '+'
  GetMovieNextInterestingTime(m_movie, nextTimeMediaSample + nextTimeEdgeOK, 1,
                              &mediaType, currentTime, 0, &nextTime, 0);
  if (nextTime != -1) {
    TFrameId frame(f + 1);
    level->setFrame(frame, TImageP());
    currentTimes[f] = nextTime;
    f++;
  }
  currentTime = nextTime;
  while (nextTime != -1) {
    GetMovieNextInterestingTime(m_movie, nextTimeMediaSample, 1, &mediaType,
                                currentTime, 0, &nextTime, 0);
    if (nextTime != -1) {
      TFrameId frame(f + 1);
      level->setFrame(frame, TImageP());
      currentTimes[f] = nextTime;
      f++;
    }
    currentTime = nextTime;
  }
  return level;
}

//------------------------------------------------

TLevelP TLevelReaderMov::loadToonzOutputFormatInfo() {
  TLevelP level;
  if (m_IOError != QTNoError)
    throw TImageException(m_path, buildQTErrorString(m_IOError));

  OSStatus mderr;

  // Retrieve the first frame of movie. This info has been put in a metadata
  // atom.
  QTMetaDataRef metaDataRef;
  if ((mderr = QTCopyMovieMetaData(m_movie, &metaDataRef)) != noErr)
    throw TImageException(m_path, "can't access metadata information");

  QTMetaDataItem firstFrameItem;

  // Find the metadata atom
  mderr = QTMetaDataGetNextItem(
      metaDataRef, kQTMetaDataStorageFormatUserData,
      kQTMetaDataItemUninitialized, kQTMetaDataKeyFormatUserData,
      (const UInt8 *)firstFrameKey.c_str(), firstFrameKeySize, &firstFrameItem);

  // Try to read the value. If the atom was not found, just assume firstFrame =
  // 0.
  int firstFrame = 0;
  if (mderr == noErr) {
    // The atom was found. Then, retrieve the value
    if ((mderr = QTMetaDataGetItemValue(metaDataRef, firstFrameItem,
                                        (UInt8 *)(&firstFrame), sizeof(int),
                                        0) != noErr))
      throw TImageException(m_path, "can't read metadata information");
  }

  mderr = 0;

  QTMetaDataRelease(metaDataRef);

  OSType mediaType = VisualMediaCharacteristic;
  TimeValue nextTime, currentTime;
  currentTime = 0;
  nextTime    = 0;

  TimeValue movieDuration = GetMovieDuration(m_movie);

  std::vector<TimeValue> interestingTimeValues;

  // io vorrei fare '|', ma sul manuale c'e' scritto '+'
  GetMovieNextInterestingTime(m_movie, nextTimeMediaSample + nextTimeEdgeOK, 1,
                              &mediaType, currentTime, 0, &nextTime, 0);
  if (nextTime != -1) {
    interestingTimeValues.push_back(nextTime);
    currentTime = nextTime;
  }
  while (nextTime != -1) {
    GetMovieNextInterestingTime(m_movie, nextTimeMediaSample, 1, &mediaType,
                                currentTime, 0, &nextTime, 0);
    if (nextTime != -1) {
      interestingTimeValues.push_back(nextTime);
      currentTime = nextTime;
    }
  }

  if (currentTime != -1) {
    double frameRate         = m_info->m_frameRate;
    TimeScale movieTimeScale = GetMovieTimeScale(m_movie);
    int firstFrameTimeValue  = movieTimeScale * firstFrame;

    std::vector<TimeValue>::iterator it;
    for (it = interestingTimeValues.begin(); it != interestingTimeValues.end();
         ++it) {
      int frame =
          firstFrame + tround((*it * frameRate) / (double)movieTimeScale);
      TFrameId frameId(frame + 1);
      level->setFrame(frameId, TImageP());
      currentTimes[frame] = *it;
    }
  }

  return level;
}

//------------------------------------------------

TImageP TImageReaderMov::load() {
  TRasterPT<TPixelRGBM32> ret(m_lrm->getSize());

  m_lrm->load(ret, m_frameIndex, TPointI(), 1, 1);

  return TRasterImageP(ret);
}

//------------------------------------------------

void TImageReaderMov::load(const TRasterP &rasP, const TPoint &pos, int shrinkX,
                           int shrinkY) {
  if ((shrinkX != 1) || (shrinkY != 1) || (pos != TPoint(0, 0))) {
    TImageReader::load(rasP, pos, shrinkX, shrinkY);
  } else
    m_lrm->load(rasP, m_frameIndex, pos, shrinkX, shrinkY);
}
//------------------------------------------------
inline void setMatteAndYMirror(const TRaster32P &ras) {
  ras->lock();
  TPixel32 *upRow = ras->pixels();
  TPixel32 *dwRow = ras->pixels(ras->getLy() - 1);
  int hLy = (int)(ras->getLy() / 2. + 0.5);  // piccola pessimizzazione...
  int wrap          = ras->getWrap();
  int lx            = ras->getLx();
  TPixel32 *upPix   = 0;
  TPixel32 *lastPix = ras->pixels(hLy);
  while (upPix < lastPix) {
    upPix            = upRow;
    TPixel32 *dwPix  = dwRow;
    TPixel32 *endPix = upPix + lx;
    while (upPix < endPix) {
      TPixel32 tmpPix(upPix->r, upPix->g, upPix->b, 0xff);
      *upPix   = *dwPix;
      upPix->m = 0xff;
      *dwPix   = tmpPix;
      ++upPix;
      ++dwPix;
    }
    upRow += wrap;
    dwRow -= wrap;
  }
  ras->lock();
}

//------------------------------------------------

void TLevelReaderMov::load(const TRasterP &rasP, int frameIndex,
                           const TPoint &pos, int shrinkX, int shrinkY) {
  GWorldPtr offscreenGWorld = 0;
  {
    QMutexLocker sl(&m_mutex);
    if (m_IOError != QTNoError)
      throw TImageException(m_path, buildQTErrorString(m_IOError));

    TRaster32P ras = rasP;

    Rect rect;
    rect.right  = pos.x + ras->getLx();
    rect.left   = pos.x;
    rect.bottom = pos.y + ras->getLy();
    rect.top    = pos.y;

    OSErr err;

#if defined TNZ_MACHINE_CHANNEL_ORDER_BGRM
    OSType pixelFormat = k32BGRAPixelFormat;
#elif defined TNZ_MACHINE_CHANNEL_ORDER_MRGB
    OSType pixelFormat = k32ARGBPixelFormat;
#endif
    // ras->lock();
    err = QTNewGWorldFromPtr(&offscreenGWorld, pixelFormat, &rect, 0, 0, 0,
                             ras->getRawData(), ras->getWrap() * 4);

    if (err != noErr) {
      m_IOError = QTUnableToCreateResource;
      goto error;
    }

    SetMovieBox(m_movie, &rect);
    err = GetMoviesError();
    if (err != noErr) {
      m_IOError = QTUnableToSetMovieBox;

      goto error;
    }

#if defined(HAS_QUICKDRAW)
    SetMovieGWorld(m_movie, offscreenGWorld, GetGWorldDevice(offscreenGWorld));
#endif
    err = GetMoviesError();
    if (err != noErr) {
      m_IOError = QTUnableToSetMovieGWorld;
      goto error;
    }

    if (currentTimes.empty()) loadInfo();

    std::map<int, TimeValue>::iterator it = currentTimes.find(frameIndex);
    if (it == currentTimes.end()) goto error;

    TimeValue currentTime = it->second;
    SetMovieTimeValue(m_movie, currentTime);

    err = GetMoviesError();
    if (err != noErr) {
      m_IOError = QTUnableToSetTimeValue;
      goto error;
    }

    err = UpdateMovie(m_movie);
    if (err != noErr) {
      m_IOError = QTUnableToUpdateMovie;
      goto error;
    }

    MoviesTask(m_movie, 0);
    err = GetMoviesError();
    if (err != noErr) {
      m_IOError = QTUnableToDoMovieTask;
      goto error;
    }

    SetMovieGWorld(m_movie, 0, 0);
#if defined(HAS_QUICKDRAW)
    DisposeGWorld(offscreenGWorld);
#endif
    // ras->unlock();
  }

  if (m_depth != 32) {
    setMatteAndYMirror(rasP);
  } else {
    rasP->yMirror();
  }

  return;

error:
// rasP->unlock();
#if defined(HAS_QUICKDRAW)
  if (offscreenGWorld) DisposeGWorld(offscreenGWorld);
#endif
  throw TImageException(m_path, buildQTErrorString(m_IOError));
}

//------------------------------------------------

TImageReaderP TLevelReaderMov::getFrameReader(TFrameId fid) {
  if (m_IOError != QTNoError)
    throw TImageException(m_path, buildQTErrorString(m_IOError));

  if (fid.getLetter() != 0) return TImageReaderP(0);
  int index = fid.getNumber() - 1;

  TImageReaderMov *irm = new TImageReaderMov(m_path, index, this);
  return TImageReaderP(irm);
}

#endif  //!__LP64__
