/* Convert ARMovie/Eidos' Replay (.RPL) file to uncompressed AVI
 * GPL - Angus Lees <gus@inodes.org>  April 2006
 */

#define WIN32_LEAN_AND_MEAN	/* heh */
#include <windows.h>
#include <vfw.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "streamer.h"

#define STREAMER_AUDIOOUT_BITSIZE  16 /* streamer always outputs SLE16 */

static const char *avierror(HRESULT err) {
  switch (err) {
  case AVIERR_OK: return "OK";
  case AVIERR_UNSUPPORTED: return "UNSUPPORTED";
  case AVIERR_BADFORMAT: return "BADFORMAT";
  case AVIERR_MEMORY: return "MEMORY";
  case AVIERR_INTERNAL: return "INTERNAL";
  case AVIERR_BADFLAGS: return "BADFLAGS";
  case AVIERR_BADPARAM: return "BADPARAM";
  case AVIERR_BADSIZE: return "BADSIZE";
  case AVIERR_BADHANDLE: return "BADHANDLE";
  case AVIERR_FILEREAD: return "FILEREAD";
  case AVIERR_FILEWRITE: return "FILEWRITE";
  case AVIERR_FILEOPEN: return "FILEOPEN";
  case AVIERR_COMPRESSOR: return "COMPRESSOR";
  case AVIERR_NOCOMPRESSOR: return "NOCOMPRESSOR";
  case AVIERR_READONLY: return "READONLY";
  case AVIERR_NODATA: return "NODATA";
  case AVIERR_BUFFERTOOSMALL: return "BUFFERTOOSMALL";
  case AVIERR_CANTCOMPRESS: return "CANTCOMPRESS";
  case AVIERR_USERABORT: return "USERABORT";
  case AVIERR_ERROR: return "ERROR";
  default: return  "Unknown?";
  }
}

static long last_updated;
static PAVISTREAM audio;
static size_t audiobuffersize;

static void sound_callback(LPSOUNDHANDLE shandle) {
  HRESULT res;
  long state;

  /* printf("sound_callback called!\n"); */

  state = Streamer_GetSoundDecodeMode(shandle);

  res = AVIStreamWrite(audio, -1, audiobuffersize*8/STREAMER_AUDIOOUT_BITSIZE,
		       Streamer_GetSoundBuffer(shandle, state),
		       audiobuffersize, 0, 0, 0);
  if (FAILED(res)) {
    fprintf(stderr, "AVIStreamWrite(audio) returned %s\n", avierror(res));
    return;
  }

  Streamer_SetSoundDecodeMode(shandle, SSDM_IDLE);
  last_updated = state;
}

#ifdef __WINE__
static char *convert_path(const char *path) {
  WCHAR *dospath;
  char *newpath;

  dospath = wine_get_dos_file_name(path);
  if (!dospath) {
    fprintf(stderr, "Wine can't get a DOS path for %s\n", path);
    exit(1);
  }

  newpath = malloc(MAX_PATH);	/* leak */
  newpath[0] = '\0';
  WideCharToMultiByte(CP_UNIXCP, 0, dospath, -1, newpath, MAX_PATH, NULL, NULL);

  return newpath;
}
#else
#define convert_path(p) (p)
#endif


int main(int argc, char **argv) {
  char *inputfilename, *outputfilename;
  LONG ret;
  LPMOVIEHANDLE mhandle;
  LPVIDEOHANDLE vhandle;
  LPSOUNDHANDLE shandle = NULL;
  long buffer_pixel_width, buffer_pixel_height;	/* what are these for?? */
  BYTE ap, ac, rp, rc, gp, gc, bp, bc;
  LPBYTE videobuffer, soundbuffer;
  int audio_finished, video_finished;
  PAVIFILE avi;
  PAVISTREAM video;
  AVISTREAMINFO info;
  BITMAPINFOHEADER bi;
  HRESULT res;

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <in.rpl> <out.avi>\n", argv[0]);
    exit(1);
  }

  inputfilename = convert_path(argv[1]);
  outputfilename = convert_path(argv[2]);


  ret = Streamer_InitMovie(&mhandle, NULL, 0, inputfilename,
			   STREAMER_BUFFERSIZE, SIM_NONE);

  if (ret != STREAMER_OK) {
    fprintf(stderr, "Streamer_InitMovie(%s) returned %ld\n",
	    inputfilename, ret);
    exit(1);
  }

  printf("Name: %s\n", Movie_GetName(mhandle));
  printf("Author: %s\n", Movie_GetAuthor(mhandle));
  printf("Copyright: %s\n", Movie_GetCopyright(mhandle));

  AVIFileInit();

  res = AVIFileOpen(&avi, outputfilename, OF_WRITE|OF_CREATE, 0);
  if (FAILED(res)) {
    AVIFileExit();
    fprintf(stderr, "Error writing %s: %s\n", outputfilename, avierror(res));
    exit(1);
  }


  ret = Streamer_InitVideo(&vhandle, mhandle,
			   Movie_GetXSize(mhandle), Movie_GetYSize(mhandle),
			   0, 0, 0, 0, 0, 0,
			   DFLAG_INVIEWPORT,
			   &buffer_pixel_width, &buffer_pixel_height);
  if (ret != STREAMER_OK) {
    fprintf(stderr, "Streamer_InitVideo() returned %ld\n", ret);
    exit(1);
  }
  Streamer_SetVideoPitch(vhandle,
			 Movie_GetXSize(mhandle), Movie_GetYSize(mhandle));


  switch (Movie_GetColourDepth(mhandle)) {
  default:
    fprintf(stderr, "Don't cope with native depth %ld yet, using 24bit..\n",
	    Movie_GetColourDepth(mhandle));
    /* fall through */
  case 32:			/* top byte unused, so really 24bit.. */
  case 24:
    /* BGR */
    ap = 24;
    ac = 0;
    rp = 16;
    rc = 8;
    gp = 8;
    gc = 8;
    bp = 0;
    bc = 8;
    break;    
  case 16:
    /* 555 RGB */
    ap = 15;
    ac = 1;
    rp = 10;
    rc = 5;
    gp = 5;
    gc = 5;
    bp = 0;
    bc = 5;
    break;
  }

  printf("Found %ldx%ldx%ldbpp video stream\n",
	 Movie_GetXSize(mhandle), Movie_GetYSize(mhandle),
	 Movie_GetColourDepth(mhandle));

  ret = Streamer_SetPixelFormat(vhandle, Movie_GetColourDepth(mhandle),
				ap, ac, rp, rc, gp, gc, bp, bc);
  if (ret != STREAMER_OK) {
    fprintf(stderr, "Streamer_SetPixelFormat returned %ld\n", ret);
    exit(1);
  }


  memset(&info, 0, sizeof(info));
  info.fccType = streamtypeVIDEO;
  info.dwScale = 1;
  info.dwRate = Movie_GetFrameRate(mhandle);
  info.dwSuggestedBufferSize = Movie_GetXSize(mhandle) *
    Movie_GetYSize(mhandle) * Movie_GetColourDepth(mhandle);
  SetRect(&info.rcFrame, 0, 0,
	  Movie_GetXSize(mhandle), Movie_GetYSize(mhandle));
  res = AVIFileCreateStream(avi, &video, &info);
  if (FAILED(res)) {
    fprintf(stderr, "AVIFileCreateStream(video) returned %s\n", avierror(res));
    exit(1);
  }

  memset(&bi, 0, sizeof(bi));
  bi.biSize = sizeof(bi);
  bi.biWidth = Movie_GetXSize(mhandle);
  bi.biHeight = Movie_GetYSize(mhandle);
  bi.biBitCount = Movie_GetColourDepth(mhandle);
  bi.biSizeImage = bi.biWidth * bi.biHeight *  bi.biBitCount / 8;
  bi.biPlanes = 1;
  bi.biCompression = BI_RGB;
  res = AVIStreamSetFormat(video, 0, &bi, bi.biSize);
  if (FAILED(res)) {
    fprintf(stderr, "AVIStreamSetFormat(video) returned %s\n", avierror(res));
    exit(1);
  }



  if (Movie_GetSoundChannels(mhandle) &&
      Movie_GetSoundPrecision(mhandle) &&
      Movie_GetSoundRate(mhandle)) {
    long compression;
    BOOL pcm;
    long precision = Movie_GetSoundPrecision(mhandle);
    long channels = Movie_GetSoundChannels(mhandle);
    WAVEFORMATEX wf;

    if (precision == 4) {
      compression = 4;
      pcm = FALSE;
    } else {
      compression = 1;
      pcm = TRUE;
    }

    printf("Found %ldbit %s audio stream\n",
	   precision, channels == 1 ? "mono" : "stereo");

    /* Should be one video frame's worth of (uncompressed) audio, so
     * we get a nicely interleaved AVI file.. */
    audiobuffersize =
      Movie_GetSoundRate(mhandle) * channels * STREAMER_AUDIOOUT_BITSIZE/8 /
      Movie_GetFrameRate(mhandle);

    ret = Streamer_InitSound(sound_callback, &shandle,
			     audiobuffersize,
			     compression, pcm, 4096, channels);
    if (ret != STREAMER_OK) {
      fprintf(stderr, "Streamer_InitSound() returned %ld\n", ret);
      exit(1);
    }

    memset(&info, 0, sizeof(info));
    info.fccType = streamtypeAUDIO;
    info.dwSampleSize = STREAMER_AUDIOOUT_BITSIZE;
    info.dwScale = audiobuffersize;
    info.dwRate = Movie_GetSoundRate(mhandle) * channels * STREAMER_AUDIOOUT_BITSIZE/8;
    info.dwQuality = -1;

    res = AVIFileCreateStream(avi, &audio, &info);
    if (FAILED(res)) {
      fprintf(stderr, "AVIFileCreateStream(audio) returned %s\n", avierror(res));
      exit(1);
    }

    memset(&wf, 0, sizeof(wf));
    wf.wFormatTag = WAVE_FORMAT_PCM;
    wf.nChannels = channels;
    wf.nSamplesPerSec = Movie_GetSoundRate(mhandle);
    wf.wBitsPerSample = info.dwSampleSize;
    wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
    wf.cbSize = 0;

    AVIStreamSetFormat(audio, 0, &wf, sizeof(WAVEFORMATEX));


    soundbuffer = malloc(audiobuffersize * 2);
    if (!soundbuffer) {
      fprintf(stderr, "Malloc failed, out of memory\n");
      exit(1);
    }

    Streamer_SetSoundBuffer(shandle, 0, soundbuffer);
    Streamer_SetSoundBuffer(shandle, 1, soundbuffer + audiobuffersize);
  }

  if (shandle)
    Streamer_SetSoundDecodeMode(shandle, SSDM_IDLE);
  last_updated = SSDM_SECONDBUFFER;

  videobuffer = malloc(Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) *
		       Movie_GetColourDepth(mhandle));
  if (!videobuffer) {
    fprintf(stderr, "Out of memory allocating video buffer\n");
    exit(1);
  }


  /* kicks the file into interleaved mode */
  AVIFileEndRecord(avi);


  /* This prefills audio buffer, etc */
  ret = Streamer_InitStreaming(mhandle, vhandle, shandle);
  if (ret != STREAMER_OK) {
    fprintf(stderr, "Streamer_InitStreaming returned %ld\n", ret);
    exit(1);
  }


  audio_finished = (shandle == NULL);

  /* main loop */
  while (1) {
    LONG framestoplay;

    /* should be 0 if time for next frame hasn't been reached */
    framestoplay = video_finished ? 0 : 1;

    ret = Streamer_Stream(mhandle, vhandle, shandle, NULL,
			  1, /* framestoplay */
			  videobuffer, NULL,
			  NULL, 0);
    if (ret == STREAMER_FINISHEDVIDEO) {
      break;
    } else if (ret == STREAMER_FINISHEDAUDIO) {
      audio_finished = 1;
    } else if (ret != STREAMER_OK) {
      fprintf(stderr, "Streamer_Stream returned %ld\n", ret);
      exit(1);
    }

    /* Streamer calls the audio callback during the *next* _Stream()
     * call, so we need to write the rec marker here, between the call
     * to _Stream() and writing out the "next" video frame */
    AVIFileEndRecord(avi);

    printf("[%ld] ", Movie_GetCurrentFrame(mhandle));

    res = AVIStreamWrite(video, -1, 1, videobuffer,
			 Movie_GetXSize(mhandle) * Movie_GetYSize(mhandle) *
			 Movie_GetColourDepth(mhandle),
			 AVIIF_KEYFRAME, 0, 0);
    if (FAILED(res)) {
      fprintf(stderr, "AVIStreamWrite(video) returned %s\n", avierror(res));
    }

    if (!audio_finished) {
      if (Streamer_GetSoundDecodeMode(shandle) == SSDM_IDLE) {
	/* No need for double buffering, since we're synchronously
	 * writing out the entire buffer to the AVI file.  Left here,
	 * since it shows the general mechanism Streamer expects to
	 * deal with. */
	LONG mode = last_updated == SSDM_FIRSTBUFFER ?
	  SSDM_SECONDBUFFER : SSDM_FIRSTBUFFER;
	Streamer_SetSoundDecodeMode(shandle, mode);
      }
    }
  }

  AVIFileEndRecord(avi);

  printf("Done.\n");

  AVIStreamRelease(video);
  if (shandle) AVIStreamRelease(audio);
  AVIFileRelease(avi);

  Streamer_ShutDownSound(&shandle);
  Streamer_ShutDownVideo(&vhandle);
  Streamer_ShutDownMovie(&mhandle);

#ifndef __WINE__
  /* This throws a stub warning on wine at present (Wine 0.9.9) */
  AVIFileExit();
#endif

  return 0;
}
