/**
 *	@file   liboasis-usb-att.c
 * 	@brief  Library file for the msatt driver
 * 	@author Tomasz Włostowski
 *	@date	October 17th 2018
 */


#include <stdio.h>
#include <errno.h>
#include <ftdi.h>

#include "liboasis-usb-att.h"
#include "usb-enum.h"

#define OAU_VENDOR_ID 0x1556	/**< CERN USB Vendor ID */
#define OAU_DEVICE_ID 0x0443	/**< oau USB Device ID */

#define OAU_MAX_HANDLES 16

#define OAU_NUM_CHANNELS 4

const char * const liboau_att_version_s =
	"liboasis-usb-att version: " GIT_VERSION;

struct oau_context {
	struct ftdi_context *ftdi;
	uint8_t relay_state;
};

static int oau_current_handle = 0;
static struct oau_context oau_contexts[OAU_MAX_HANDLES];

static struct {
	int r1, r2;
} oau_relay_mapping[OAU_NUM_CHANNELS] = {
	{ 0, 1 },
	{ 2, 3 },
	{ 4, 5 },
	{ 6, 7 }
};

int oau_att_open(int lun)
{
	/* not implemented: ignore lun by the moment */
	return -ENOSYS;
}

int oau_att_open_by_serial(char *serial)
{
	int ret;

	if( oau_current_handle >= OAU_MAX_HANDLES )
		return -ENOMEM;

	struct oau_context *ctx = &oau_contexts[ oau_current_handle ];
	int fd = oau_current_handle;
	oau_current_handle++;

	if ((ctx->ftdi = ftdi_new()) == 0) {
		return -ENOMEM;
	}

	if (serial) {
		ret = ftdi_usb_open_desc(ctx->ftdi,
				OAU_VENDOR_ID, OAU_DEVICE_ID, NULL, serial);
		if (ret < 0) {
			ftdi_free(ctx->ftdi);
			return ret;
		}
	} else { /* open first, and maybe only, device at hand */
		ret = ftdi_usb_open(ctx->ftdi, OAU_VENDOR_ID, OAU_DEVICE_ID);
		if (ret < 0) {
			ftdi_free(ctx->ftdi);
			return ret;
		}
	}

	if( (ret = ftdi_set_bitmode(ctx->ftdi, 0xff, BITMODE_BITBANG)) < 0 ) {
		ftdi_usb_close( ctx->ftdi );
		ftdi_free(ctx->ftdi);
		return ret;
	}

	uint8_t pins;

	if( (ret = ftdi_read_pins(ctx->ftdi, &pins) ) < 0 ) {
		ftdi_usb_close( ctx->ftdi );
		ftdi_free(ctx->ftdi);
			return ret;
	}

	ctx->relay_state = pins;

	return fd;
}

int oau_att_close (int fd)
{
	struct oau_context *ctx = &oau_contexts[ fd ];
	int ret;

	if( (ret = ftdi_usb_close( ctx->ftdi )) < 0 )
	{
		return ret;
	}

	ftdi_free( ctx->ftdi );
	return 0;
}

enum oau_att_value_enum oau_att_get_relay (int fd, int channel)
{
	struct oau_context *ctx = &oau_contexts[ fd ];

	if(channel < 1 || channel > OAU_NUM_CHANNELS)
		return -EINVAL;

	uint8_t r1_mask = 1 << oau_relay_mapping[channel-1].r1;
	uint8_t r2_mask = 1 << oau_relay_mapping[channel-1].r2;
	
	int r1 = ( ctx->relay_state & r1_mask );
	int r2 = ( ctx->relay_state & r2_mask );

	if( r1 && !r2 )
		return ATT_1;
	else if ( r1 && r2 )
		return ATT_2;
	else if ( !r1 && !r2 )
		return ATT_NONE;

	return ATT_ERR;
}

int oau_att_set_relay (int fd, int channel, enum oau_att_value_enum val)
{
	struct oau_context *ctx = &oau_contexts[ fd ];

	if(channel < 1 || channel > OAU_NUM_CHANNELS)
		return -EINVAL;

	uint8_t r1_mask = 1 << oau_relay_mapping[channel-1].r1;
	uint8_t r2_mask = 1 << oau_relay_mapping[channel-1].r2;
	

	switch(val)
	{
		case ATT_NONE:
			ctx->relay_state &= ~(r1_mask | r2_mask);
			break;
		case ATT_1:
			ctx->relay_state &= ~r2_mask;
			ctx->relay_state |= r1_mask;
			break;
		case ATT_2:
			ctx->relay_state |= r1_mask;
			ctx->relay_state |= r2_mask;
			break;
		default:
			return -EINVAL;
	}

	int ret  = ftdi_write_data(ctx->ftdi, &ctx->relay_state, 1);
	if(ret < 0)
		return -EIO;

	uint8_t pins;
	ftdi_read_pins(ctx->ftdi, &pins);

	return 0;
}

int oau_att_get_nchannels(int fd)
{

	return OAU_NUM_CHANNELS;
}

int oau_list_devices(void)
{
	int i, devs;
	struct oau_devarray *list;

	list = oau_get_devarray(OAU_VENDOR_ID, OAU_DEVICE_ID);
	oau_display_devarray(list);

	return 0;
}
