/* -LICENSE-START-
 ** Copyright (c) 2009 Blackmagic Design
 **
 ** Permission is hereby granted, free of charge, to any person or organization
 ** obtaining a copy of the software and accompanying documentation covered by
 ** this license (the "Software") to use, reproduce, display, distribute,
 ** execute, and transmit the Software, and to prepare derivative works of the
 ** Software, and to permit third-parties to whom the Software is furnished to
 ** do so, all subject to the following:
 ** 
 ** The copyright notices in the Software and this entire statement, including
 ** the above license grant, this restriction and the following disclaimer,
 ** must be included in all copies of the Software, in whole or in part, and
 ** all derivative works of the Software, unless such copies or derivative
 ** works are solely in the form of machine-executable object code generated by
 ** a source language processor.
 ** 
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ** DEALINGS IN THE SOFTWARE.
 ** -LICENSE-END-
 */

/* Deck Control Sample Application. 
   This application assumes VTR is compatible with SONY RS-422 9 pin remote protocol. 
 */
#include "DeckLinkAPI.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#define IOSSDATALAT    _IOW('T', 0, unsigned long)	
#define COMM_BUFFER_SIZE		20

int		fdSerial;
bool deviceConnected = false;
struct termios		originalSerialDeviceAttributes;
bool	openSerialDevice(char* serialName);
void	closeSerialDevice();

bool	playCommand();
bool	timeCodeCommand();
bool	stopCommand();

int main (int argc, char* argv[]) {

	printf("DeckControl Test Application\n\n");

	IDeckLinkIterator*		deckLinkIterator;
	IDeckLink*				deckLink;
	int						numDevices = 0;
	HRESULT					result;

	fdSerial = -1;
	
	// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
	deckLinkIterator = CreateDeckLinkIteratorInstance();
	if (deckLinkIterator == NULL)
	{
		fprintf(stderr, "A DeckLink iterator could not be created.  The DeckLink drivers may not be installed.\n");
		return 1;
	}
	
	// Enumerate all cards in this system
	while (deckLinkIterator->Next(&deckLink) == S_OK)
	{
		CFStringRef		deviceNameCFString = NULL;
		
		// Increment the total number of DeckLink cards found
		numDevices++;
		if (numDevices > 1)
			printf("\n\n");	
		
		// *** Print the model name of the DeckLink card
		result = deckLink->GetModelName(&deviceNameCFString);
		if (result == S_OK)
		{
			IDeckLinkAttributes*	deckLinkAttributes;
		
			char			deviceName[64];
			
			HRESULT			attributeResult;
			CFStringRef		serialName;
			bool			serialSupported;
			
			CFStringGetCString(deviceNameCFString, deviceName, sizeof(deviceName), kCFStringEncodingMacRoman);
			printf("Found Blackmagic device: %s\n", deviceName);
			
			attributeResult = deckLink->QueryInterface(IID_IDeckLinkAttributes, (void**)&deckLinkAttributes);
			if (attributeResult != S_OK)
			{
				fprintf(stderr, "Could not obtain the IDeckLinkAttributes interface");
			}
			else
			{
				attributeResult = deckLinkAttributes->GetFlag(BMDDeckLinkHasSerialPort, &serialSupported);	// are serial ports supported on device?
				if (attributeResult == S_OK && serialSupported)			
				{							
					attributeResult = deckLinkAttributes->GetString(BMDDeckLinkSerialPortDeviceName, &serialName);	// get serial port name
					if (attributeResult == S_OK)
					{
						char portName[64];
						CFStringGetCString(serialName, portName, sizeof(portName), kCFStringEncodingMacRoman);
						printf("Serial port name: %s\n",portName);
				
						if (openSerialDevice((char*)&portName)== true)		// open serial port
						{
							printf("Device opened\n");
							playCommand();									// Play deck, 
							printf("Delay 3 seconds\n");
							sleep(3);
							timeCodeCommand();								// DisplayTC
							printf("Delay 3 seconds\n");
							sleep(3);
							stopCommand();									// Stop deck
							closeSerialDevice();							// close serial port
						}					
						else printf("Device open fail\n");									
						CFRelease(serialName);					
					}
					else printf("Unable to get serial port device name\n");
				}
				else printf("Serial port not supported\n");
			}
			CFRelease(deviceNameCFString);		// Release the IDeckLink instance when we've finished with it to prevent leaks
		}		
		deckLink->Release();
	}
	if (deckLinkIterator) deckLinkIterator->Release();
	
	// If no DeckLink cards were found in the system, inform the user
	if (numDevices == 0)
		printf("No Blackmagic Design devices were found.\n");
	printf("\n");	
    return 0;
}


bool	openSerialDevice(char* serialName)
{
	struct termios		options;
	long				dataLat;
	bool				serialOpen = false;
	
	if (serialName)	printf("Opening serial port: %s\n",serialName);
	else return false;
	
	if (deviceConnected)
		return true;							// device already connected

	if ((fdSerial = open(serialName, O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
		goto bail;
	printf("Port opened\n");
	// Obtain exclusive access
	if (ioctl(fdSerial, TIOCEXCL, 0) == -1)
	{		
		close(fdSerial);
		fdSerial = -1;
		printf("Port closed\n");		
		goto bail;
	}	
	serialOpen = true;
	if (fcntl(fdSerial, F_SETFL, 0) == -1)
		goto bail;
	
	// Get the current options and save them for later reset
	if (tcgetattr(fdSerial, &originalSerialDeviceAttributes) == -1)
		goto bail;

	// Set raw input
	// These options are documented in the man page for termios
	// (in Terminal enter: man termios)
	
	options.c_cflag = (CS8 | PARENB | PARODD | CSTOPB | CLOCAL | CREAD);
	options.c_iflag = (IGNBRK | IGNPAR);
	options.c_lflag = (originalSerialDeviceAttributes.c_lflag & ~(ICANON | ECHO | ECHOE | ISIG));
	options.c_oflag = (originalSerialDeviceAttributes.c_oflag & ~OPOST);

	// There is no minimum number of bytes to wait for read() to return, nor is
	// there a minimum time that we should wait for a response from the VTR.
	options.c_cc[VMIN] = 0;
	options.c_cc[VTIME] = 1;
	// Set the speed to 38.4 k baud
	options.c_ispeed = B38400;
	options.c_ospeed = B38400;

	// Set the options
	if (tcsetattr(fdSerial, TCSANOW, &options) == -1)
		goto bail;

	// Set the data read latency
	dataLat = 5;
	ioctl(fdSerial, IOSSDATALAT, &dataLat);		
	deviceConnected = true;
bail:
	if (!deviceConnected)
	{
		if (serialOpen)
			closeSerialDevice();
		printf("we've bailed\n");	
	}
	return deviceConnected;
}


void	closeSerialDevice()
{
	if (fdSerial < 0)
	{
		printf("Invalid serial handle\n");
		return;
	}
	tcsetattr(fdSerial, TCSANOW, &originalSerialDeviceAttributes);
	close(fdSerial);	
	deviceConnected = false;
	printf("Device closed\n");
}


bool	playCommand()
{
	unsigned int bytesRead=0;
	UInt8	transmitBuffer[COMM_BUFFER_SIZE] = {0};
	UInt8	receiveBuffer[COMM_BUFFER_SIZE] = {0};
	
	printf("\nplayCommand");
	transmitBuffer[0] = 0x20;		// Play command
	transmitBuffer[1] = 0x01;
	transmitBuffer[2] = 0x21;

	write(fdSerial, reinterpret_cast<char*>(&transmitBuffer[0]), 3);
	usleep(9000);					// 9ms delay to allow deck to respond
	bytesRead = read(fdSerial, reinterpret_cast<char*>(&receiveBuffer[0]), COMM_BUFFER_SIZE);
	printf("\nReceived: ");	
	for (unsigned char readCount=0; readCount< bytesRead; readCount++)
		printf("%x ",receiveBuffer[readCount]);
	printf("\n");	
	return true;
}

bool	stopCommand()
{
	unsigned int bytesRead=0;
	UInt8	transmitBuffer[COMM_BUFFER_SIZE] = {0};
	UInt8	receiveBuffer[COMM_BUFFER_SIZE] = {0};

	printf("\nstopCommand");	
	transmitBuffer[0] = 0x20;		// Stop command
	transmitBuffer[1] = 0x00;
	transmitBuffer[2] = 0x20;
	
	write(fdSerial, reinterpret_cast<char*>(&transmitBuffer[0]), 3);
	usleep(9000);					// 9ms delay to allow deck to respond
	bytesRead = read(fdSerial, reinterpret_cast<char*>(&receiveBuffer[0]), COMM_BUFFER_SIZE);
	printf("\nReceived: ");
	for (unsigned char readCount=0; readCount< bytesRead; readCount++)
		printf("%x ",receiveBuffer[readCount]);
	printf("\n");	
	return true;
}

bool	timeCodeCommand()
{
	unsigned int bytesRead=0;
	UInt8	transmitBuffer[COMM_BUFFER_SIZE] = {0};
	UInt8	receiveBuffer[COMM_BUFFER_SIZE] = {0};
	
	printf("\ntimeCodeCommand");	
	transmitBuffer[0] = 0x61;		// Get Time Sense command
	transmitBuffer[1] = 0x0c;
	transmitBuffer[2] = 0x03;
	transmitBuffer[3] = 0x70;

	write(fdSerial, reinterpret_cast<char*>(&transmitBuffer[0]), 4);
	usleep(9000);					// 9ms delay to allow deck to respond
	bytesRead = read(fdSerial, reinterpret_cast<char*>(&receiveBuffer[0]), COMM_BUFFER_SIZE);
	printf("\nReceived: ");
	for (unsigned char readCount=0; readCount< bytesRead; readCount++)
		printf("%x ",receiveBuffer[readCount]);
	printf("\n");	

	if ((receiveBuffer[0]==0x74) && ((receiveBuffer[1]==0x04) || (receiveBuffer[1]==0x06)))	// LTC time data
	{
		printf("\nTC %02x:%02x:%02x:%02x\n",receiveBuffer[5],receiveBuffer[4],receiveBuffer[3],receiveBuffer[2]); 		
	}
	return true;
 }
