/*
 * SPDX-FileCopyrightText: 2023 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

/**
 *	@file   liboattnusb.c
 * 	@brief  Library file for the oattnusb driver
 * 	@author Tomasz Włostowski
 *	@date	October 17th 2018
 */


#include <stdio.h>
#include <errno.h>
#include <libusb-1.0/libusb.h>
#include <ftdi.h>

#include "liboattnusb.h"
#include "liboattnusb-i.h"
#include "usb-enum.h"

const char * const liboattnusb_version_s =
	"liboattnusb version: " GIT_VERSION;

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

static int oattnusb_current_handle = 1;
static struct oattnusb_context oattnusb_contexts[OATTNUSB_MAX_HANDLES];

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

int oattnusb_open(int lun)
{
	struct oattnusb_devarray *list;

	list = oattnusb_get_devarray(OATTNUSB_VENDOR_ID, OATTNUSB_DEVICE_ID);

	if (lun < 0 || lun >= list->ndevs) {
		return -1;
	}

	return oattnusb_open_by_serial(list->devs[lun].serial);
}

int oattnusb_open_by_serial(const char *serial)
{
	int ret;

	if( oattnusb_current_handle >= OATTNUSB_MAX_HANDLES )
		return -ENOMEM;

	struct oattnusb_context *ctx = &oattnusb_contexts[ oattnusb_current_handle ];
	int fd = oattnusb_current_handle;
	oattnusb_current_handle++;

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

	if (serial) {
		ret = ftdi_usb_open_desc(ctx->ftdi,
				OATTNUSB_VENDOR_ID, OATTNUSB_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, OATTNUSB_VENDOR_ID, OATTNUSB_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 oattnusb_close (int fd)
{
	struct oattnusb_context *ctx = &oattnusb_contexts[ fd ];
	int ret;

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

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

enum oattnusb_value_enum oattnusb_get_relay (int fd, int channel)
{
	struct oattnusb_context *ctx = &oattnusb_contexts[ fd ];

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

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

	if( r1 && !r2 )
		return ATT_MINUS_20dB;
	else if ( r1 && r2 )
		return ATT_ZERO_dB;
	else if ( !r1 && !r2 )
		return ATT_MINUS_40dB;

	return ATT_ERR;
}

int oattnusb_set_relay (int fd, int channel, enum oattnusb_value_enum val)
{
	struct oattnusb_context *ctx = &oattnusb_contexts[ fd ];

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

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

	switch(val)
	{
		case ATT_MINUS_40dB:
			ctx->relay_state &= ~(r1_mask | r2_mask);
			break;
		case ATT_MINUS_20dB:
			ctx->relay_state &= ~r2_mask;
			ctx->relay_state |= r1_mask;
			break;
		case ATT_ZERO_dB:
			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 oattnusb_get_nchannels(int fd)
{

	return OATTNUSB_NUM_CHANNELS;
}

int oattnusb_list_devices(void)
{
	struct oattnusb_devarray *list;

	list = oattnusb_get_devarray(OATTNUSB_VENDOR_ID, OATTNUSB_DEVICE_ID);
	oattnusb_display_devarray(list);

	return 0;
}
