/*ScianLVR5000.c
  Eric Pepke
  Controls the LVR5000 video recorder

  This file controls the Sony LVR-5000 videodisc recorder.  It may be able 
  to control other models in the Sony line as well, but this is the only one
  that we have.  It currently runs only on the Silicon Graphics IRIS 
  workstation, but should be easy to make run on other systems.

  To make this recorder work, you need to get a video signal from the 
  workstation to the recorder, and connect the recorder to the workstation
  via a serial port.  

  The video signal can either be the standard IRIS RGB signal run through 
  a scan converter, or the direct IRIS RGB output when the IRIS is set to
  "NTSC" mode.  Don't hook the high-resolution output directly to the 
  recorder ever!  The recorder tries to sync with the incoming signal, and
  that can't be good for the servos.

  The serial signal can go to any port on the back of the IRIS.  For now, the 
  port which is used is gotten from the environment variable SCIAN_RECORDER_DEV.
  If that environment variable is not present, then the #define'd value
  of LVR5000_DEV from ScianLVR5000.h is used.  The port is set to 9600 baud.
  Eventually, all this will be changeable via a control panel.

  Oh, yes.  In order for the machine to work, the LVR-5000 has to be set to
  "Remote" operation via the front panel.

  Thanks to Roland Schweitzer for the idea of using an environment variable
  as an interim solution.
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianWindows.h"
#include "ScianDialogs.h"
#include "ScianIDs.h"
#include "ScianLVR5000.h"
#include "ScianColors.h"

#ifdef TERMIO

/*Timeout in seconds*/
#define LVRTIMEOUT		5

/*Retries*/
#define NRETRIES	3

int debug = 0;

static ObjPtr ConnectLVR5000();

/*Internal prototypes*/
#ifdef PROTO
static void SendPort(int, char);
static int OpenPort(void);
static int SendCmd(int, int);
static int WaitForResponse(int);
static int WaitALongTimeForResponse(int);
static int Read5Digits(int);
static Bool WaitForCompletion(int);
static int SendNumber(int, int);
#endif

#ifdef PROTO
static void SendPort(int portDev, char c)
#else
static void SendPort(portDev, c)
int portDev;
char c;
#endif
{
    write(portDev, &c, 1);
}

static int OpenPort()
/*Opens a port to the recorder, returns it if successful*/
{
    int portDev;
    struct termio ioStuff;
    char *devName;

    devName = getenv("SCIAN_RECORDER_DEV");
    if (!devName || !devName[0])
    {
	devName = LVR5000_DEV;
    }

    portDev = open(devName, O_RDWR | O_NDELAY | O_EXCL);
    if (portDev < 0)
    {
	return portDev;
    }
    ioctl(portDev, TCGETA, &ioStuff);
    ioStuff . c_oflag = 0;
    ioStuff . c_iflag = IGNBRK;
    ioStuff . c_lflag = 0;
    ioStuff . c_cc[4] = 1;
    ioStuff . c_cc[0] = 1;

    ioctl(portDev, TCSETA, &ioStuff);
    return portDev;
}

static int SendCmd(portDev, number)
int portDev;
int number;
/*Sends out command name with number.  Returns true iff successful*/
{
    int response; 
    
    SendPort(portDev, number);
    response = WaitForResponse(portDev);

    switch (response)
    {
	case ACK:
	    return true;
	case NAK:
	    return false;
	case -1:
	    return false;
	default:
	    return false;
    }
}

static int WaitForResponse(portDev)
int portDev;
/*Waits for a response from the sony and returns it or -1 if it timed out
  or is closed*/
{
    char retVal;
    long startTime, curTime;
    struct tms buffer;

    startTime = times(&buffer);
    do
    {
	if (read(portDev, &retVal, 1))
	{
	    return retVal;
	}
	curTime = times(&buffer);
    } while (curTime < startTime + LVRTIMEOUT * HEARTBEAT);
    return -1;
}

static int WaitALongTimeForResponse(portDev)
int portDev;
/*Waits a long time for a response from the sony and returns it or -1 if it 
  timed out or is closed*/
{
    char retVal;
    long startTime, curTime;
    struct tms buffer;

    startTime = times(&buffer);
    do
    {
	if (read(portDev, &retVal, 1))
	{
	    return retVal;
	}
	curTime = times(&buffer);
    } while (curTime < startTime + 400 * HEARTBEAT);
    return -1;
}

#if 0
int GetStatus(status)
char status[5];
/*Gets a status from the recorder and returns it in status.  Returns true iff
  successful*/
{
    long startTime, curTime;
    struct tms buffer;
    int k;
    char stat;

    SendPort(STATUS_INQ);
    
    for (k = 0; k < 5; ++k)
    {
	startTime = times(&buffer);
	do
	{
	    if (read(portDev, &stat, 1))
	    {
	    	status[k] = stat;
	    	goto oneMore;
	    }
	    curTime = times(&buffer);
	} while (curTime < startTime + LVRTIMEOUT * HEARTBEAT);
	return 0;
oneMore:;
    }
    return 1;
}
#endif

static ObjPtr ConnectLVR5000(object)
ObjPtr object;
/*Connects to the LVR5000.  Returns true iff successful*/
{
    int portDev;
    int k;

    /*Open a port*/
    portDev = OpenPort();
    if (portDev < 0)
    {
	return ObjFalse;
    }

    /*Now that a port is open, connect to it*/
    for (k = 0; k < NRETRIES; ++k)
    {
	if (SendCmd(portDev, CONNECT_TO_PROCESSOR))
	{
	    SendCmd(portDev, COLOR_MODE);
	    SendCmd(portDev, NTSC_MODE);
	    SendCmd(portDev, ALL_CLEAR);
	    SendCmd(portDev, FRAME_NO_MODE);
	    SetVar(object, PORTNUMBER, NewInt(portDev));
	    return ObjTrue;
	}
    }
    close(portDev);
    return ObjFalse;
}

static int Read5Digits(portDev)
int portDev;
/*Reads a five digit integer from portDev.  Returns -1 on timeout*/
{
    int k;
    int cum;

    cum = 0;
    for (k = 0; k < 5; ++k)
    {
	int digit;
	digit = WaitForResponse(portDev);
	if (digit >= '0' && digit <= '9')
	{
	    cum = cum * 10 + digit - '0';
	}
	else
	{
	    return -1;
	}
    }
    return cum;
}

static int SendNumber(portDev, num)
int portDev;
int num;
/*Sends a number followed by ENTER to the recorder*/
{
    char numStr[10];
    int k;

    sprintf(numStr, "%d", num);
    for (k = 0; numStr[k]; ++k)
    {
	if (!SendCmd(portDev, numStr[k])) return false;
    }
    return SendCmd(portDev, ENTER);
}

static Bool WaitForCompletion(portDev)
int portDev;
{
    if (WaitALongTimeForResponse(portDev) == COMPLETION)
    {
	return true;
    }
    else
    {
	return false;
    }
}

static ObjPtr PrepareToRecordLVR5000(object, nFrames)
ObjPtr object;
long nFrames;
/*Prepares to record nFrames on an LVR5000.  Returns true iff successful.*/
{
    ObjPtr port;

    port = GetVar(object, PORTNUMBER);
    if (port)
    {
	int portDev;
	int begin, end, firstBegin;
	int k;

	portDev = GetInt(port);

	/*Search for blank area and go to record standby*/
	for (k = 0; k < NRETRIES; ++k)
	{
	    SendCmd(portDev, REC_STANDBY);
	    if (WaitForCompletion(portDev))
	    {
		goto okbud;
	    }
#if 0
		AlertUser(UISEVEREALERT, (WinInfoPtr) 0,
		"REC_STANDBY for the LVR-5000 did not complete.",
		0, 0, "Rats");
#else
	    ReportError("PrepareToRecordLVR5000", "REC_STANDBY did not complete");
#endif
	}
	return ObjFalse;
okbud:
	/*Get first blank area*/
	SendPort(portDev, CURRENT_BLANK_INQ);
	firstBegin = begin = Read5Digits(portDev);
	if (begin < 0) return ObjFalse;
	end = Read5Digits(portDev);
	if (end < 0) return ObjFalse;

	/*If not enough, search for more*/
	if (end - begin <= nFrames)
	{
	    do
	    {
		if (SendCmd(portDev, EDGE_RIGHT))
		{
		    if (WaitForCompletion(portDev))
		    {
			SendPort(portDev, CURRENT_BLANK_INQ);
			begin = Read5Digits(portDev);
			if (begin < 0) return ObjFalse;
			end = Read5Digits(portDev);
			if (end < 0) return ObjFalse;
		    }
		    else
		    {
			ReportError("PrepareToRecordLVR5000", "EDGE_RIGHT did not complete");
			return ObjFalse;
		    }
		}
		else
		{
		    ReportError("PrepareToRecordLVR5000", "EDGE_RIGHT did not complete");
		    return ObjFalse;
		}
	    } while (begin != firstBegin && end - begin <= nFrames);

	    if (end - begin <= nFrames)
	    {
		ReportError("PrepareToRecordLVR5000", "There is not enough space on the disc");
		return ObjFalse;
	    }
	}

	/*Say for the range we need*/
	SendNumber(portDev, begin);
	SendNumber(portDev, begin + nFrames);
	SendCmd(portDev, ENTER);
	if (!WaitForCompletion(portDev))
	{
	    return ObjFalse;
	}
	SendCmd(portDev, FRAME_REC);

	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr StopRecordingLVR5000(object)
ObjPtr object;
/*Stops recording*/
{
    ObjPtr port;

    port = GetVar(object, PORTNUMBER);
    if (port)
    {
	int portDev;
	portDev = GetInt(port);

	SendCmd(portDev, ALL_CLEAR);
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr DisconnectLVR5000(object)
ObjPtr object;
{
    int portDev;
    ObjPtr port;

    port = GetVar(object, PORTNUMBER);
    if (port)
    {
	portDev = GetInt(port);
	close(portDev);
	SetVar(object, PORTNUMBER, NULLOBJ);
    }
    return ObjTrue;
}

static ObjPtr SnapOneFrameLVR5000(object)
ObjPtr object;
{
    int portDev;
    int test;
    ObjPtr port;

    port = GetVar(object, PORTNUMBER);
    if (port)
    {
	portDev = GetInt(port);
	test = SendCmd(portDev, REC);
	if (!WaitForCompletion(portDev))
	{
	    return ObjFalse;
	}
	return test ? ObjTrue : ObjFalse;
    }
    else
    {
	return ObjTrue;
    }
}
#endif

ObjPtr NewLVR5000()
/*Return a new LVR-5000 object*/
{
#ifdef TERMIO
    ObjPtr retVal;
    retVal = NewObject(NULLOBJ, 0);
    SetVar(retVal, NAME, NewString("Sony LVR-5000"));
    SetMethod(retVal, CONNECT, ConnectLVR5000);
    SetMethod(retVal, DISCONNECT, DisconnectLVR5000);
    SetMethod(retVal, PREPARETORECORD, PrepareToRecordLVR5000);
    SetMethod(retVal, STOPRECORDING, StopRecordingLVR5000);
    SetMethod(retVal, SNAPONEFRAME, SnapOneFrameLVR5000);
    return retVal;
#else
    return NULLOBJ;
#endif
}
