#define __USE_SYSBASE
#include <stdio.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <proto/asyncio.h>
#include <proto/ahi.h>

#ifdef __PPC__
#include <powerup/gcclib/powerup_protos.h>
#undef SetSignal
#define SetSignal PPCSetSignal
#endif

#include "common.h"

static const char version[]="$VER: RAPlay 2.0 (15.06.01) The Amiga RealAudio 14.4/28.8 player/decoder, thanks to all!";
ULONG __nocommandline;
ULONG __stack = 16384;

#if 0
BOOL FastTable = 0;
#endif

struct Library    *AsyncIOBase = NULL;
AsyncFile         *InputFile   = NULL;
AsyncFile         *OutputFile  = NULL;
Real_144          *Real144     = NULL;
Real_288          *Real288     = NULL;

struct MsgPort    *AHImp       = NULL;
struct AHIRequest *AHIio       = NULL;
struct AHIRequest *AHIio2      = NULL;
BYTE               AHIDevice   = -1;

struct RDArgs     *rdargs      = NULL;
#if 0
STRPTR Template = "INPUT,OUTPUT,U=UNIT/K/N,FAST/S";
enum { TEM_INPUT, TEM_OUTPUT, TEM_UNIT, TEM_FAST, TEM_NUMARGS };
#else
STRPTR Template = "INPUT,OUTPUT,U=UNIT/K/N";
enum { TEM_INPUT, TEM_OUTPUT, TEM_UNIT, TEM_NUMARGS };
#endif

signed short *outbuf=NULL,*dubbuf=NULL;

#ifndef __GNUC__
static int break_cleanup(void)
{
	/* Just a dummy so atexit() will work */
	return 1;
}
#endif

static void exit_cleanup(void)
{
	if(outbuf);
	{
		FreeMem(outbuf,AUDIOBUFFER*sizeof(short));
		outbuf = NULL;
	}

	if(dubbuf);
	{
		FreeMem(dubbuf,AUDIOBUFFER*sizeof(short));
		dubbuf = NULL;
	}

	if(Real144);
	{
		free_144(Real144);
		Real144 = NULL;
	}

	if(Real288);
	{
		free_288(Real288);
		Real288 = NULL;
	}

	if(InputFile);
	{
		CloseAsync(InputFile);
		InputFile = NULL;
	}

	if(OutputFile);
	{
		CloseAsync(OutputFile);
		OutputFile = NULL;
	}

	if(AsyncIOBase)
	{
		CloseLibrary(AsyncIOBase);
		AsyncIOBase = NULL;
	}

	if(!AHIDevice)
	{
		CloseDevice((struct IORequest *)AHIio);
		AHIDevice = -1;
	}

	if(AHIio)
	{
		DeleteIORequest((struct IORequest *)AHIio);
		AHIio = NULL;
	}

	if(AHIio2)
	{
		DeleteIORequest((struct IORequest *)AHIio2);
		AHIio2 = NULL;
	}

	if(AHImp)
	{
		DeleteMsgPort(AHImp);
		AHImp = NULL;
	}

	if(rdargs)
	{
		FreeArgs(rdargs);
		rdargs = NULL;
	}
}

void Fail(STRPTR reason)
{
	BPTR StdErr;

	if ((StdErr = Open("CONSOLE:", MODE_NEWFILE)))
	{
		FPrintf(StdErr, "%s\n", reason);
		Close(StdErr);
	}

	exit(20);
}

int main(void)
{
  ULONG unit=0;
  BOOL terminated=FALSE;
  ULONG signals,AHIcount=0;
  struct AHIRequest *link = NULL;
  LONG ArgArray[TEM_NUMARGS];
  BPTR StdIn, StdOut;
  unsigned int s,x,bufsiz,size,blocksize,chunksize;
  unsigned char in[DATACHUNK2];
  unsigned char deint[DATACHUNK2];
  void *tmp;

  for(x=0;x<TEM_NUMARGS;ArgArray[x++]=NULL);
  if(!(rdargs=ReadArgs(Template,ArgArray,NULL))) Fail("Unable to read arguments.");

  StdIn  = Input();
  StdOut = Output();

  if(!ArgArray[TEM_INPUT])
  {
    if(IsInteractive(StdIn)) Fail("No input specified.");
    else if(IsInteractive(StdOut)) Fail("Please redirect output or use proper arguments instead.");
  }

  if(ArgArray[TEM_UNIT]) unit=*((ULONG *)ArgArray[TEM_UNIT]);
  if(unit>3) Fail("Illegal unit number!");

#if 0
  if(ArgArray[TEM_FAST]) FastTable=1;
#endif

  if(ArgArray[TEM_INPUT])
  {
    if(!(AsyncIOBase=OpenLibrary("asyncio.library", 39))) Fail("Unable to open asyncio.library.");

#ifndef __GNUC__
    onbreak(break_cleanup);
#endif
    atexit(exit_cleanup);

    if(!(InputFile=OpenAsync((STRPTR)ArgArray[TEM_INPUT], MODE_READ, 16384))) Fail("Unable to open input file.");

    if(ArgArray[TEM_OUTPUT])
    {
      if(!(OutputFile=OpenAsync((STRPTR)ArgArray[TEM_OUTPUT], MODE_WRITE, 32768))) Fail("Unable to open output file.");
    }
    else
    {
      if(AHImp=CreateMsgPort())
        if(AHIio=(struct AHIRequest *)CreateIORequest(AHImp,sizeof(struct AHIRequest)))
        {
          AHIio->ahir_Version = 4;
          AHIDevice=OpenDevice(AHINAME,unit,(struct IORequest *)AHIio,NULL);
        }
      if(AHIDevice) Fail("Unable to open ahi.device v4");

      if(!(AHIio2=AllocMem(sizeof(struct AHIRequest), MEMF_ANY))) Fail("Unable to allocate needed memory.");
      CopyMem(AHIio, AHIio2, sizeof(struct AHIRequest));
    }

    if(ReadAsync(InputFile,in,6) <= 0) Fail("Unexpected EOF or error while reading.");
  }
  else Read(StdIn,in,6);

  if(in[0]=='.'&&in[1]=='R'&&in[2]=='M'&&in[3]=='F') Fail("RealMedia streams are not supported!!!");
  if(in[0]=='.'&&in[1]=='r'&&in[2]=='a'&&in[3]==0xfd)
  {
    if(in[5]==4)
    {
      if(ArgArray[TEM_INPUT])
      {
        if(ReadAsync(InputFile,in,55) <= 0) Fail("Unexpected EOF or error while reading.");
      }
      else Read(StdIn,in,55);

      if(in[54]=='0') Fail("Int0 28.8 streams are not supported!!!");
      if(in[54]=='r') Fail("RealMedia streams are not supported!!!");

      x=(in[12]<<24|in[13]<<16|in[14]<<8|in[15])-45;

      if(ArgArray[TEM_INPUT])
      {
        if(ReadAsync(InputFile,in,x) <= 0) Fail("Unexpected EOF or error while reading.");
      }
      else Read(StdIn,in,x);

      blocksize=DATABLOCK2;
      chunksize=DATACHUNK2;
      Real288 = init_288();
    }
    else if(in[5]==3)
    {
      if(ArgArray[TEM_INPUT])
      {
        if(ReadAsync(InputFile,in,2) <= 0) Fail("Unexpected EOF or error while reading.");
      }
      else Read(StdIn,in,2);

      while(in[0])
      {
        if(ArgArray[TEM_INPUT])
        {
          if(ReadAsync(InputFile,in,256) <= 0) Fail("Unexpected EOF or error while reading.");
        }
        else Read(StdIn,in,256);
      }

      if(ArgArray[TEM_INPUT])
      {
        if(ReadAsync(InputFile,in,in[1]) <= 0) Fail("Unexpected EOF or error while reading.");
      }
      else Read(StdIn,in,in[1]);

      blocksize=DATABLOCK1;
      chunksize=DATACHUNK1;
      Real144 = init_144();
    }
    else Fail("Not a 14.4 or 28.8 stream!!!");
  }
  else Fail("Unrecognized format!!!");

  if(AHImp) if(!(dubbuf = (signed short *)AllocMem(AUDIOBUFFER*sizeof(short), MEMF_ANY | MEMF_CLEAR))) Fail("Unable to allocate needed memory.");
  if(!(outbuf=(signed short *)AllocMem(AUDIOBUFFER*sizeof(short), MEMF_ANY | MEMF_CLEAR))) Fail("Unable to allocate needed memory.");

  if(ArgArray[TEM_INPUT])
  {
    while((s=size=ReadAsync(InputFile,in,chunksize))>0)
    {
      bufsiz=0;
      if (!(size/=blocksize)) break;

      if(blocksize==DATABLOCK1) {
        for(x=0;x<size;x++)
        {
          decode_144(Real144,in+blocksize*x,&outbuf[bufsiz]);
          bufsiz+=AUDIOBLOCK;
        }
      } else {
        deinterleave(in,deint,s);
        for(x=0;x<size;x++)
        {
          decode_288(Real288,deint+blocksize*x,&outbuf[bufsiz]);
          bufsiz+=AUDIOBLOCK;
        }
      }

      if(AHImp)
      {
        AHIio->ahir_Std.io_Message.mn_Node.ln_Pri = 0;
        AHIio->ahir_Std.io_Command  = CMD_WRITE;
        AHIio->ahir_Std.io_Data     = outbuf;
        AHIio->ahir_Std.io_Length   = bufsiz*sizeof(short);
        AHIio->ahir_Std.io_Offset   = 0;
        AHIio->ahir_Frequency       = 8000;
        AHIio->ahir_Type            = AHIST_M16S;
        AHIio->ahir_Volume          = 0x10000;
        AHIio->ahir_Position        = 0x8000;
        AHIio->ahir_Link            = link;
        SendIO((struct IORequest *) AHIio);
        AHIcount++;

        if(link)
        {
          signals=Wait(SIGBREAKF_CTRL_C | (1L << AHImp->mp_SigBit));
#ifdef __PPC__
          if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
#else
          if(signals & SIGBREAKF_CTRL_C)
#endif
          {
            terminated=TRUE;
            break;
          }
          if(WaitIO((struct IORequest *) link)) Fail("I/O request failed.");
        }

        link   = AHIio;

        tmp    = outbuf;
        outbuf = dubbuf;
        dubbuf = tmp;

        tmp    = AHIio;
        AHIio  = AHIio2;
        AHIio2 = tmp;
      }
      else
      {
        WriteAsync(OutputFile,outbuf,bufsiz*sizeof(short));
        if(SetSignal(0,0) & SIGBREAKF_CTRL_C) break;
      }
    }
  }
  else
  {
    while((s=size=Read(StdIn,in,chunksize))>0)
    {
      bufsiz=0;
      if (!(size/=blocksize)) break;

      if(blocksize==DATABLOCK1) {
        for(x=0;x<size;x++)
        {
          decode_144(Real144,in+blocksize*x,&outbuf[bufsiz]);
          bufsiz+=AUDIOBLOCK;
        }
      } else {
        deinterleave(in,deint,s);
        for(x=0;x<size;x++)
        {
          decode_288(Real288,deint+blocksize*x,&outbuf[bufsiz]);
          bufsiz+=AUDIOBLOCK;
        }
      }

      Write(StdOut,outbuf,bufsiz*sizeof(short));
      if(SetSignal(0,0) & SIGBREAKF_CTRL_C) break;
    }
  }

  if(AHIcount)
  {
    if(terminated)
    {
      AbortIO((struct IORequest *) AHIio);
      WaitIO((struct IORequest *) AHIio);

      if(AHIcount>1)
      {
        AbortIO((struct IORequest *) AHIio2);
        WaitIO((struct IORequest *) AHIio2);
      }
    }
    else WaitIO((struct IORequest *) AHIio2);
  }

  return 0;
}
