

#if !defined(x64) && !(defined(__GNUC__) && defined(_WIN32))

#include <cstdint>

#include "texception.h"
#include "tsound.h"
#include "tconvert.h"
#include "tpropertytype.h"
#include "trasterimage.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 "../mov/tiio_mov.h"
#include "tiio_3gp.h"

#include "movsettings.h"

namespace {

int CompressionNoneId = 0;

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 = InitializeQTML(0);
    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
                                      */

    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,
};

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) + "')";
  }
  }
}

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

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;
}

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

inline char *filePath2unichar(const TFilePath &path) {
  int _convert    = 0;
  std::wstring ws = path.getWideString();

  LPCWSTR lpw = ws.c_str();
  char *name  = NULL;

  if (lpw) {
    _convert   = (lstrlenW(lpw) + 1) * 2;
    LPSTR pStr = new char[_convert];
    name       = AtlW2AHelper(pStr, lpw, _convert, 0);
  }

  char *outName = new char[1024];

  if (QTMLGetCanonicalPathName(name, outName, 1024) == noErr) {
    delete[] name;
    return outName;
  }
  delete[] outName;
  return name;
}

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

TFilePath getLegalName(const TFilePath &name) {
  if (QuickTimeStuff::instance()->getStatus() != noErr) return name;

  TFilePath legalName;
  char outDir[1024];

  TFilePath dirName(name.getParentDir());

  char dirNameFp[1024] = "";

  OSErr err = QTMLGetCanonicalPathName(dirNameFp, outDir, 1024);

  if (err == noErr)
    legalName = TFilePath(outDir) + name.withoutParentDir();
  else
    legalName = name;

  return legalName;
}

//--------------------------`----------------------------------------------------

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;
}

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

}  // namespace

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

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

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

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

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

private:
  TLevelWriter3gp *m_lwm;
};

//-----------------------------------------------------------
//  TImageReaderv
//-----------------------------------------------------------
class TImageReader3gp final : public TImageReader {
public:
  TImageReader3gp(const TFilePath &, int frameIndex, TLevelReader3gp *);
  ~TImageReader3gp() { m_lrm->release(); }

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

public:
  TImageP load() override;
  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:
  TLevelReader3gp *m_lrm;
};

//-----------------------------------------------------------
//-----------------------------------------------------------
//        TImageWriter3gp
//-----------------------------------------------------------

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

//----------
namespace {
void copy(TRasterP rin, PixelXRGB *bufout, int lx, int ly) {
  rin->lock();
  TRaster32P rin32 = rin;
  assert(rin32);

  PixelXRGB *rowout = &(bufout[(ly - 1) * lx]);
  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 -= lx;
  }
  rin->unlock();
}
};  // namespace

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

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

void TLevelWriter3gp::save(const TImageP &img, int frameIndex) {
  if (m_cancelled) return;
  string msg;
  QMutexLocker sl(&m_mutex);

  TRasterImageP image(img);
  TRasterP ras = image->getRaster();
  int lx       = ras->getLx();
  int ly       = ras->getLy();

  ras->lock();
  void *buffer = image->getRaster()->getRawData();
  int pixSize  = image->getRaster()->getPixelSize();
  if (pixSize != 4) {
    msg = "Unsupported pixel type";
    goto error;
  }

  if (!m_properties) m_properties = new Tiio::MovWriterProperties();

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

  QTAtomContainer atoms;
  QTNewAtomContainer(&atoms);
  fromPropertiesToAtoms(*prop, atoms);
  ComponentInstance ci =
      OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
  if (SCSetSettingsFromAtomContainer(ci, atoms)) assert(false);

  /*
CodecType compression = prop->getCurrentCodec();
CodecQ quality = prop->getCurrentQuality();
*/

  if (!m_initDone) {
    Rect frame;
    QDErr err;

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

    if ((err = GetMoviesError() != noErr)) {
      msg = "can't create video track";
      goto error;
    }

    m_dataRef    = nil;
    m_hMovieData = NewHandle(0);

    // Construct the Handle data reference
    err = PtrToHand(&m_hMovieData, &m_dataRef, sizeof(Handle));

    if ((err = GetMoviesError() != noErr)) {
      msg = "can't create Data Ref";
      goto error;
    }

    m_videoMedia =
        NewTrackMedia(m_videoTrack, VideoMediaType, (TINT32)m_frameRate,
                      m_dataRef, HandleDataHandlerSubType);

    OpenADefaultComponent(MovieExportType, '3gpp', &m_myExporter);
    if (TSystem::doHaveMainLoop())
      err = (short)MovieExportDoUserDialog(m_myExporter, m_movie, 0, 0, 0,
                                           &m_cancelled);
    if (m_cancelled) {
      msg = "user abort of 3gp creation";
      goto error;
    }
    if ((err = GetMoviesError() != noErr)) {
      msg = "can't create video media";
      goto error;
    }

    if ((err = BeginMediaEdits(m_videoMedia)) != noErr) {
      msg = "can't begin edit video media";
      goto error;
    }

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

    if ((err = NewGWorld(&(m_gworld), pixSize * 8, &frame, 0, 0, 0)) != noErr) {
      msg = "can't create movie buffer";
      goto error;
    }

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

    if ((err = MemError()) != noErr) {
      msg = "can't allocate compressed data for movie";
      goto error;
    }

    if ((err = MemError()) != noErr) {
      msg = "can't allocate img handle";
      goto error;
    }

    m_pixmap = GetGWorldPixMap(m_gworld);
    if (!LockPixels(m_pixmap)) {
      msg = "can't lock pixels";
      goto error;
    }

    buf    = (PixelXRGB *)(*(m_pixmap))->baseAddr;
    buf_lx = lx;
    buf_ly = ly;

    m_initDone = true;
  }

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

  QDErr err;

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

  copy(ras, buf, buf_lx, buf_ly);

  if ((err = (QDErr)SCCompressImage(ci, m_gworld->portPixMap, &frame,
                                    &img_descr, &compressedData)) != noErr) {
    msg = "can't compress image";
    goto error;
  }

  /*
if ((err = CompressImage(m_gworld->portPixMap,
                         &frame,
                                                                                           quality, compression,
                                                                                           img_descr, compressed_data_ptr))!=noErr)
throw TImageException(getFilePath(), "can't compress image");
*/
  if ((err = AddMediaSample(
           m_videoMedia, compressedData, 0, (*img_descr)->dataSize, 1,
           (SampleDescriptionHandle)img_descr, 1, 0, 0)) != noErr) {
    msg = "can't add image to movie media";
    goto error;
  }

  DisposeHandle((Handle)img_descr);
  DisposeHandle(compressedData);
  ras->unlock();
  return;

error:
  ras->unlock();
  throw TImageException(getFilePath(), msg);
}

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

TLevelWriter3gp::TLevelWriter3gp(const TFilePath &path, TPropertyGroup *winfo)
    : TLevelWriter(path, winfo)
    , m_initDone(false)
    , m_IOError(QTNoError)
    , m_pixmap(0)
    , m_gworld(0)
    , m_videoMedia(0)
    , m_videoTrack(0)
    , m_movie(0)
    , m_cancelled(false)
    , m_soundDataRef(0)
    , m_hSoundMovieData(0)

{
  m_frameRate = 12.;

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

  QDErr err;

  m_movie = NewMovie(0);
  if ((err = GetMoviesError() != noErr))
    throw TImageException(getFilePath(), "can't create movie");
}

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

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

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

void TLevelWriter3gp::saveSoundTrack(TSoundTrack *st) {
  Track theTrack;
  OSErr myErr = noErr;
  SoundDescriptionV1Handle mySampleDesc;
  Media myMedia;
  Handle myDestHandle;
  SoundComponentData sourceInfo;
  SoundComponentData destInfo;
  SoundConverter converter;
  CompressionInfo compressionInfo;
  int err;

  if (m_cancelled) return;

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

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

  theTrack = NewMovieTrack(m_movie, 0, 0, kFullVolume);
  myErr    = GetMoviesError();

  FailIf(myErr != noErr, CompressErr);

  myDestHandle = NewHandle(0);

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

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

  //////////
  //
  // create a media for the track passed in
  //
  //////////

  // set new track to be a sound track

  m_soundDataRef    = nil;
  m_hSoundMovieData = NewHandle(0);

  // Construct the Handle data reference
  err = PtrToHand(&m_hSoundMovieData, &m_soundDataRef, sizeof(Handle));

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

  myMedia = NewTrackMedia(theTrack, SoundMediaType, st->getSampleRate(),
                          m_soundDataRef,
                          HandleDataHandlerSubType);  // track->rate >> 16
  myErr   = GetMoviesError();
  FailIf(myErr != noErr, Exit);

  // start a media editing session
  myErr = BeginMediaEdits(myMedia);
  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
  //
  //////////

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

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

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

  myErr =
      InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
  FailIf(myErr != noErr, MediaErr);

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

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

TLevelWriter3gp::~TLevelWriter3gp() {
  if (m_pixmap) UnlockPixels(m_pixmap);
  if (m_gworld) DisposeGWorld(m_gworld);
  QDErr err;

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

  if (m_videoTrack)
    if ((err = InsertMediaIntoTrack(m_videoTrack, 0, 0,
                                    GetMediaDuration(m_videoMedia), fixed1))) {
    }  // throw TImageException(getFilePath(), "can't insert media into track");

  short resId = movieInDataForkResID;
  if (m_movie) {
    FSSpec fspec;
    long myFlags      = 0L;
    OSErr myErr       = noErr;
    UCHAR myCancelled = FALSE;
    char *pStr        = filePath2unichar(m_path);

    NativePathNameToFSSpec(pStr, &fspec, kFullNativePath);

    myFlags = createMovieFileDeleteCurFile;  // |
    // movieFileSpecValid | movieToFileOnlyExport;

    myErr =
        ConvertMovieToFile(m_movie,                 // the movie to convert
                           NULL,                    // all tracks in the movie
                           &fspec,                  // the output file
                           '3gpp',                  // the output file type
                           FOUR_CHAR_CODE('TVOD'),  // the output file creator
                           smSystemScript,          // the script
                           &resId,         // no resource ID to be returned
                           myFlags,        // export flags
                           m_myExporter);  // no specific exp
  }

  DisposeHandle(m_hMovieData);
  DisposeHandle(m_dataRef);
  if (m_hSoundMovieData) DisposeHandle(m_hMovieData);
  if (m_hSoundMovieData) DisposeHandle(m_hSoundMovieData);

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

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

TImageWriterP TLevelWriter3gp::getFrameWriter(TFrameId fid) {
  if (m_cancelled) return 0;

  if (m_IOError) throw TImageException(m_path, buildQTErrorString(m_IOError));
  if (!fid.getLetter().isEmpty()) return TImageWriterP(0);
  int index = fid.getNumber() - 1;

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

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

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

  char *pStr = filePath2unichar(m_path);

  if ((err = NativePathNameToFSSpec(pStr, &fspec, kFullNativePath)) != noErr) {
    delete[] pStr;
    pStr = 0;
    throw TImageException(path, "can't open file");
  }
  delete[] pStr;
  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;

  DisposeHandle((Handle)imageH);

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

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

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

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

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

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

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

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

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

TLevelP TLevelReader3gp::loadInfo() {
  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 = 1;
  // 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);
    level->setFrame(frame, TImageP());
    currentTimes.push_back(nextTime);
    f++;
  }
  currentTime = nextTime;
  while (nextTime != -1) {
    GetMovieNextInterestingTime(m_movie, nextTimeMediaSample, 1, &mediaType,
                                currentTime, 0, &nextTime, 0);
    if (nextTime != -1) {
      TFrameId frame(f);
      level->setFrame(frame, TImageP());
      currentTimes.push_back(nextTime);
      f++;
    }
    currentTime = nextTime;
  }
  return level;
}

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

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

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

  return TRasterImageP(ret);
}

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

void TImageReader3gp::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->unlock();
}

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

void TLevelReader3gp::load(const TRasterP &rasP, int frameIndex,
                           const TPoint &pos, int shrinkX, int shrinkY) {
  {
    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;

    GWorldPtr offscreenGWorld;
    OSErr err;
    ras->lock();
    err = QTNewGWorldFromPtr(&offscreenGWorld, k32BGRAPixelFormat, &rect, 0, 0,
                             0, ras->getRawData(), ras->getWrap() * 4);

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

    SetMovieBox(m_movie, &rect);
    err = GetMoviesError();
    if (err != noErr) {
      m_IOError = QTUnableToSetMovieBox;
      DisposeGWorld(offscreenGWorld);
      goto error;
    }

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

    TimeValue currentTime = currentTimes[frameIndex];

    SetMovieTimeValue(m_movie, currentTime);

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

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

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

    DisposeGWorld(offscreenGWorld);
    ras->unlock();
  }

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

  return;

error:
  rasP->unlock();
  throw TImageException(m_path, buildQTErrorString(m_IOError));
}

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

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

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

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

#endif
