/*
    Tucnak - VHF contest log
    Copyright (C) 2023  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/


#include "header.h"

#include "cwdaemon.h"
#include "davac5.h"
#include "fifo.h"
#include "language2.h"
#include "rc.h"


#ifdef HAVE_HIDAPI
int davac5_init(struct cwdaemon* cwda) {
    cwda->hid = NULL;
    MUTEX_LOCK(cwda->hid);
    davac5_open(cwda, 1);
    MUTEX_UNLOCK(cwda->hid);
    return 0;
}

int davac5_free(struct cwdaemon* cwda) {
    dbg("davac5_free(%p)\n", cwda);

    if (!cwda) return 0;

    if (cwda->hid == NULL) return 0;

    hid_close(cwda->hid);
    cwda->hid = NULL;
    return 0;
}

static int davac5_ids[] = {
    0x0d8c, 0x0012, // CM108B
    0x0d8c, 0x000c, // CM108
    0x0, 0x0
};

hid_device *davac5_do_open(){
	int i;
	for (i = 0;; i += 2) {
		int vid = davac5_ids[i];
		int pid = davac5_ids[i + 1];
		if (vid == 0) {
			return NULL;
		}

		hid_device *handle = hid_open(vid, pid, NULL);
		if (handle != NULL) return handle;
	}
	return NULL;
}

int davac5_open(struct cwdaemon* cwda, int verbose) {
    int ret;

    if (cwda->hid != NULL) {
        ret = 0;
        goto x;
    }

    if (hid_init()) {
        if (verbose) log_addf(VTEXT(T_CANT_INIT_HIDAPI));
        ret = 1;
        goto x;
    }

	cwda->hid = davac5_do_open();
	if (cwda->hid == NULL){
        if (verbose) {
			log_addf(TRANSLATE("Can't open Davac5 device"));//  VTEXT(T_CANT_OPEN_DAVAC5), vid, pid);
        }
        davac5_free(cwda);
        dbg("davac5_open error\n");
        ret = -1;
        goto x;
    }

    ret = 0;
    cwda->hid_state = 0x00;

x:;
    dbg("davac5_open=%d\n", ret);
    return ret;
}


int davac5_reset(struct cwdaemon* cwda) {
    davac5_ptt(cwda, 0);
    davac5_cw(cwda, 0);
    davac5_ssbway(cwda, 0);
    return 0;
}

static int davac5_write(struct cwdaemon* cwda) {
    unsigned char raw[5];
    raw[0] = 0; // Report ID
    raw[1] = 0; // HID output report
    raw[2] = cwda->hid_state;
    raw[3] = 0x0d; // data direction register (GPIO2 = input, 1,3,4 = output)
    raw[4] = 0;

    int ret = hid_write(cwda->hid, raw, 5);
    if (ret != 5) {
        const wchar_t* errw = hid_error(cwda->hid);
        log_addf("Can't write to davac5: %ls", errw);
        //VTEXT(T_CANT_WRITE_TO_DAVAC5)
        davac5_free(cwda);
    }
    return ret;
}

int davac5_cw(struct cwdaemon* cwda, int onoff) {
    int ret;

    MUTEX_LOCK(cwda->hid);
    if (!cwda || cwda->hid == NULL) {// function called from worker thread, exit silently
        ret = 1;
        goto x;
    }

    if (onoff)
        cwda->hid_state |= 0x08;
    else
        cwda->hid_state &= ~0x08;

    ret = davac5_write(cwda);
x:;
    MUTEX_UNLOCK(cwda->hid);
    return ret;
}

int davac5_ptt(struct cwdaemon* cwda, int onoff) {
    int ret;

    MUTEX_LOCK(cwda->hid);
    dbg("davac5_ptt cwda->hid=%p\n", cwda->hid);

    if (!cwda || cwda->hid == NULL) {
        ret = davac5_open(cwda, 0);
        if (ret) goto x;
    }
    if (onoff)
        cwda->hid_state |= 0x04;
    else
        cwda->hid_state &= ~0x04;

    ret = davac5_write(cwda);

x:;
    MUTEX_UNLOCK(cwda->hid);
    return ret;
}

/* 0=microphone, 1=soundcard */
int davac5_ssbway(struct cwdaemon* cwda, int onoff) {
    int ret;

    MUTEX_LOCK(cwda->hid);
    if (!cwda || cwda->hid == NULL) {
        ret = davac5_open(cwda, 0);
        if (ret) goto x;
    }
    if (onoff)
        cwda->hid_state |= 0x01;   /* soundcard only*/
    else
        cwda->hid_state &= ~0x01;  /* microphone + soundcard (no sound) */

    ret = davac5_write(cwda);

x:;
    MUTEX_UNLOCK(cwda->hid);
    return ret;
}


#define MAX_WSTR 255
#define MAX_STR 255

void hid_info(GString* gs) {
    g_string_append_printf(gs, "\n  hid_info:\n");

    iconv_t cd = iconv_open("ISO_8859-2", "WCHAR_T");
    if (cd == (iconv_t)-1) {
        g_string_append_printf(gs, "Can't init iconv\n");
        return;
    }

    int i;
    for (i = 0; ; i += 2) {
        int vid = davac5_ids[i];
        int pid = davac5_ids[i + 1];
        if (vid == 0) break;

        hid_device *handle = hid_open(vid, pid, NULL);
        if (!handle) continue;

        g_string_append_printf(gs, "%04x:%04x  ", vid, pid);

        wchar_t wstr[MAX_WSTR];
        char str[MAX_STR];
        int res = hid_get_manufacturer_string(handle, wstr, MAX_WSTR);
        if (res == 0) {
            char* inptr = (char*)wstr;
            size_t insize = wcslen(wstr) * sizeof(wchar_t);
            char* wrptr = (char*)str;
            size_t avail = MAX_STR;
            memset(str, 0, sizeof(str));
            int nconv = iconv(cd, &inptr, &insize, &wrptr, &avail);
            if (nconv != (size_t)-1) {
                g_string_append_printf(gs, "%s  ", str);
            }
        }

        res = hid_get_product_string(handle, wstr, MAX_WSTR);
        if (res == 0) {
            char* inptr = (char*)wstr;
            size_t insize = wcslen(wstr) * sizeof(wchar_t);
            char* wrptr = (char*)str;
            size_t avail = MAX_STR;
            memset(str, 0, sizeof(str));
            int nconv = iconv(cd, &inptr, &insize, &wrptr, &avail);
            if (nconv != (size_t)-1) {
                g_string_append_printf(gs, "%s  ", str);
            }
        }

        res = hid_get_serial_number_string(handle, wstr, MAX_WSTR);
        if (res == 0) {
            char* inptr = (char*)wstr;
            size_t insize = wcslen(wstr) * sizeof(wchar_t);
            char* wrptr = (char*)str;
            size_t avail = MAX_STR;
            memset(str, 0, sizeof(str));
            int nconv = iconv(cd, &inptr, &insize, &wrptr, &avail);
            if (nconv != (size_t)-1) {
                g_string_append_printf(gs, "%s  ", str);
            }
        }

        hid_close(handle);
        g_string_append_printf(gs, "\n");
    }

    iconv_close(cd);
}

static int cm108_write_raw(hid_device *handle, uint8_t addr, uint16_t data){

    unsigned char raw[5];
    raw[0] = 0; // Report ID
    raw[1] = 0x80; // HID output report byte 1: mapped to EEPROM_DATA0
    raw[2] = data & 0xff;
    raw[3] = data >> 8;
    raw[4] = addr;

    dbg("cm108_write_raw: %02x %02x %02x %02x\n", raw[1], raw[2], raw[3], raw[4]);
    int ret = hid_write(handle, raw, 5);
    if (ret != 5) {
        const wchar_t* errw = hid_error(handle);
        log_addf("Can't write to CM108: %ls", errw);
        return -1;
    }
    return ret;
}

static int cm108_read_raw(hid_device *handle, unsigned char *raw, size_t len){
	int ret = hid_read_timeout(handle, raw, len, 1000);
	if (ret != len) {
		const wchar_t* errw = hid_error(handle);
        dbg("cm108_read_raw: timeout\n");
        log_addf("Can't read from CM108: %ls", errw);
		return -1;
	}
    dbg("cm108_read_raw: %02x %02x %02x %02x\n", raw[0], raw[1], raw[2], raw[3]);
    return ret;
}

int cm108_write_u16(hid_device *handle, uint8_t addr, uint16_t data){
    int ret = cm108_write_raw(handle, 0xc0 | addr, data);
    if (ret != 5) {
        return -1;
    }

    while (1) {
        unsigned char raw[4];
        ret = cm108_read_raw(handle, raw, 4);
        if (ret != 4) {
            return -2;
        }
        if ((raw[3] & 0x3f) == addr) break;
        usleep(10000);
    }

	return sizeof(uint16_t);
}


int cm108_read_u16(hid_device *handle, uint8_t addr, uint16_t *data){
    int ret = cm108_write_raw(handle, 0x80 | addr, 0);
    if (ret != 5) {
        return -1;
    }

    unsigned char raw[4];
    while (1) {
        ret = cm108_read_raw(handle, raw, 4);
        if (ret != 4) {
            return -2;
        }
        if ((raw[3] & 0x3f) == addr) break;
        usleep(10000);
    }
    *data = raw[1] | (raw[2] << 8);
	return sizeof(uint16_t);

}

int cm108_write_eeprom(hid_device *handle, struct cm108_eeprom *eeprom){
	int i;
	unsigned char *c;
	uint16_t data[CM108_EE_SIZE];
	memset(data, 0, sizeof(data));

	eeprom->magic = 0x6707; // mixer invalid, serial valid

	data[0x00] = eeprom->magic;
	data[0x01] = eeprom->vid;
	data[0x02] = eeprom->pid;

	data[0x03] = strlen(eeprom->serial) * 2 + 2;
	c = (unsigned char *)&data[0x03];
	memcpy(c + 1, eeprom->serial, strlen(eeprom->serial));

	data[0x0a] = strlen(eeprom->product) * 2 + 2;
	c = (unsigned char *)&data[0x0a];
	memcpy(c + 1, eeprom->product, strlen(eeprom->product));

	data[0x1a] = strlen(eeprom->manufacturer) * 2 + 2;
	c = (unsigned char *)&data[0x1a];
	memcpy(c + 1, eeprom->manufacturer, strlen(eeprom->manufacturer));


    dbg("cm108_write_eeprom:\n");
    for (i = 0; i < CM108_EE_SIZE; i++){
		int ret = cm108_write_u16(handle, i, data[i]);
		if (ret < 0) return ret;
        if (i < 0x2a) dbg("    %02x: %04x\n", i, data[i]);
    }

	return CM108_EE_SIZE * sizeof(uint16_t);
}

int cm108_read_eeprom(hid_device *handle, struct cm108_eeprom *eeprom){
	uint16_t data[CM108_EE_SIZE];
	unsigned char *c;

    dbg("cm108_read_eeprom:\n");
	int i, len;
	for (i = 0; i < CM108_EE_SIZE; i++){
		int ret = cm108_read_u16(handle, i, data + i);
		if (ret < 0) return ret;
        if (i < 0x2a) dbg("    %02x: %04x\n", i, data[i]);
	}


	memset(eeprom, 0, sizeof(struct cm108_eeprom));
	eeprom->magic = data[0x00];
	if ((eeprom->magic & 0xfff0) != 0x6700) return -5;

	eeprom->vid = data[0x01];
	eeprom->pid = data[0x02];

	if (eeprom->magic & 0x02){
		c = (unsigned char *)&data[0x03];
        len = c[0] / 2 - 1;
        if (len > sizeof(eeprom->serial) - 1) len = sizeof(eeprom->serial) - 1;
		memcpy(&eeprom->serial, c + 1, len);
	}

	c = (unsigned char *)&data[0x0a];
    len = c[0] / 2 - 1;
    if (len > sizeof(eeprom->serial) - 1) len = sizeof(eeprom->serial) - 1;
	memcpy(&eeprom->product, c + 1, len);

	c = (unsigned char *)&data[0x1a];
    len = c[0] / 2 - 1;
    if (len > sizeof(eeprom->serial) - 1) len = sizeof(eeprom->serial) - 1;
	memcpy(&eeprom->manufacturer, c + 1, len);

	return CM108_EE_SIZE * sizeof(uint16_t);
}



#endif
