// pandaJ2534DLL.cpp : Defines the exported functions for the DLL application.
// Protocol derived from the following sites (which shall be referred to as The Protocol Reference #).
// https://web.archive.org/web/20130805013326/https://tunertools.com/prodimages/DrewTech/Manuals/PassThru_API-1.pdf
// http://web.archive.org/web/20170910063536/http://www.tiecar.net/downloads/SAE_J2534_2002.pdf

#include "stdafx.h"
#include "J2534_v0404.h"
#include "panda_shared/panda.h"
#include "J2534Connection.h"
#include "J2534Connection_CAN.h"
#include "J2534Connection_ISO15765.h"
#include "PandaJ2534Device.h"
#include "dllmain.h"

// A quick way to avoid the name mangling that __stdcall liked to do
#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

std::vector<std::shared_ptr<PandaJ2534Device>> pandas;

int J25334LastError = 0;

std::string GetProductAndVersion(TCHAR* szFilename)//std::string & strProductName, std::string & strProductVersion)
{
	// allocate a block of memory for the version info
	DWORD dummy;
	DWORD dwSize = GetFileVersionInfoSize(szFilename, &dummy);
	if (dwSize == 0) {
		return "error";
	}
	std::vector<BYTE> data(dwSize);

	// load the version info
	if (!GetFileVersionInfo(szFilename, NULL, dwSize, &data[0])) {
		return "error";
	}

	// get the name and version strings
	LPVOID pvProductName = NULL;
	unsigned int iProductNameLen = 0;
	LPVOID pvProductVersion = NULL;
	unsigned int iProductVersionLen = 0;

	// 040904b0 is a language id.
	if (!VerQueryValueA(&data[0], "\\StringFileInfo\\040904b0\\ProductName", &pvProductName, &iProductNameLen) ||
		!VerQueryValueA(&data[0], "\\StringFileInfo\\040904b0\\ProductVersion", &pvProductVersion, &iProductVersionLen)) {
		return "error";
	}

	std::string ver_str = std::string((char*)pvProductVersion, iProductVersionLen-1);
	std::string prod_str = std::string((char*)pvProductName, iProductNameLen-1);
	std::string full_ver = prod_str + std::string(": ") + ver_str;
	return full_ver;
}

long ret_code(long code) {
	J25334LastError = code;
	return code;
}

#define EXTRACT_DID(CID) ((CID & 0xFFFF) - 1)
#define EXTRACT_CID(CID) ((CID >> 16) & 0xFFFF)

long check_valid_DeviceID(unsigned long DeviceID) {
	uint16_t dev_id = EXTRACT_DID(DeviceID);
	if (pandas.size() <= dev_id || pandas[dev_id] == nullptr)
		return ret_code(ERR_INVALID_DEVICE_ID);
	return ret_code(STATUS_NOERROR);
}

long check_valid_ChannelID(unsigned long ChannelID) {
	uint16_t dev_id = EXTRACT_DID(ChannelID);
	uint16_t con_id = EXTRACT_CID(ChannelID);

	if (pandas.size() <= dev_id || pandas[dev_id] == nullptr)
		return ret_code(ERR_INVALID_CHANNEL_ID);

	if (pandas[dev_id]->connections.size() <= con_id) return ret_code(ERR_INVALID_CHANNEL_ID);
	if (pandas[dev_id]->connections[con_id] == nullptr) return ret_code(ERR_DEVICE_NOT_CONNECTED);

	return ret_code(STATUS_NOERROR);
}

//Do not call without checking if the device/channel id exists first.
#define get_device(DeviceID) (pandas[EXTRACT_DID(DeviceID)])
#define get_channel(ChannelID) (get_device(ChannelID)->connections[EXTRACT_CID(ChannelID)])

PANDAJ2534DLL_API long PTAPI    PassThruOpen(void *pName, unsigned long *pDeviceID) {
	#pragma EXPORT
	if (pDeviceID == NULL) return ret_code(ERR_NULL_PARAMETER);
	std::string sn = (pName == NULL) ? "" : std::string((char*)pName);
	if (sn == "J2534-2:")
		sn = "";

	auto new_panda = PandaJ2534Device::openByName(sn);
	if (new_panda == nullptr) {
		if(sn == "" && pandas.size() == 1)
			return ret_code(ERR_DEVICE_IN_USE);
		for (auto& pn : pandas) {
			if (pn->panda->get_usb_sn() == sn)
				return ret_code(ERR_DEVICE_IN_USE);
		}
		return ret_code(ERR_DEVICE_NOT_CONNECTED);
	}

	int panda_index = -1;
	for (unsigned int i = 0; i < pandas.size(); i++)
		if (pandas[i] == nullptr) {
			panda_index = i;
			pandas[panda_index] = std::move(new_panda);
			break;
		}

	if (panda_index == -1) {
		if(pandas.size() == 0xFFFF) //device id will be 16 bit to fit channel next to it.
			return ret_code(ERR_FAILED); //Too many pandas. Off the endangered species list.
		pandas.push_back(std::move(new_panda));
		panda_index = pandas.size()-1;
	}

	*pDeviceID = panda_index + 1; // TIS doesn't like it when ID == 0
	return ret_code(STATUS_NOERROR);
}
PANDAJ2534DLL_API long PTAPI	PassThruClose(unsigned long DeviceID) {
	#pragma EXPORT
	if (check_valid_DeviceID(DeviceID) != STATUS_NOERROR) return J25334LastError;
	get_device(DeviceID) = nullptr;
	return ret_code(STATUS_NOERROR);
}
PANDAJ2534DLL_API long PTAPI	PassThruConnect(unsigned long DeviceID, unsigned long ProtocolID,
	                                            unsigned long Flags, unsigned long BaudRate, unsigned long *pChannelID) {
	#pragma EXPORT
	if (pChannelID == NULL) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_DeviceID(DeviceID) != STATUS_NOERROR) return J25334LastError;
	auto& panda = get_device(DeviceID);

	std::shared_ptr<J2534Connection> conn;

	//TODO check if channel can be made
	try {
		switch (ProtocolID) {
			//SW seems to refer to Single Wire. https://www.nxp.com/files-static/training_pdf/20451_BUS_COMM_WBT.pdf
			//SW_ protocols may be touched on here: https://www.iso.org/obp/ui/#iso:std:iso:22900:-2:ed-1:v1:en
		//case J1850VPW: // These protocols are outdated and will not be supported. HDS wants them to not fail to open.
		//case J1850PWM: // ^-- it appears HDS no longer needs this, and TIS needs it disabled --^
		//case J1850VPW_PS:
		//case J1850PWM_PS:
		case ISO9141: //This protocol could be implemented if 5 BAUD init support is added to the panda.
		case ISO9141_PS:
		case ISO14230: //Only supporting Fast init until panda adds support for 5 BAUD init.
		case ISO14230_PS:
			conn = std::make_shared<J2534Connection>(panda, ProtocolID, Flags, BaudRate);
			break;
		case CAN:
		case CAN_PS:
		//case SW_CAN_PS:
			conn = std::make_shared<J2534Connection_CAN>(panda, ProtocolID, Flags, BaudRate);
			break;
		case ISO15765:
		case ISO15765_PS:
			conn = std::make_shared<J2534Connection_ISO15765>(panda, ProtocolID, Flags, BaudRate);
			break;
		//case SW_ISO15765_PS: // SW = Single Wire. GMLAN is a SW CAN protocol
		//case GM_UART_PS: // PS = Pin Select. Handles different ports.
		//Looks like SCI based protocols may not be compatible with the panda:
		//http://mdhmotors.com/can-communications-vehicle-network-protocols/3/
		//case SCI_A_ENGINE:
		//case SCI_A_TRANS:
		//case SCI_B_ENGINE:
			//case SCI_B_TRANS:
		//case J2610_PS:
		default:
			return ret_code(ERR_INVALID_PROTOCOL_ID);
		}
	} catch (int e) {
		return ret_code(e);
	}

	unsigned long channel_index;
	unsigned long err = panda->addChannel(conn, &channel_index);
	if (err == STATUS_NOERROR)
		*pChannelID = (channel_index << 16) | DeviceID;

	return ret_code(err);
}
PANDAJ2534DLL_API long PTAPI	PassThruDisconnect(unsigned long ChannelID) {
	#pragma EXPORT
	unsigned long res = check_valid_DeviceID(ChannelID);
	if (res == ERR_INVALID_DEVICE_ID) return ret_code(ERR_INVALID_CHANNEL_ID);
	if (res != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_device(ChannelID)->closeChannel(EXTRACT_CID(ChannelID)));
}
PANDAJ2534DLL_API long PTAPI	PassThruReadMsgs(unsigned long ChannelID, PASSTHRU_MSG *pMsg,
	                                             unsigned long *pNumMsgs, unsigned long Timeout) {
	#pragma EXPORT
	if (pMsg == NULL || pNumMsgs == NULL) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruReadMsgs(pMsg, pNumMsgs, Timeout));
}
PANDAJ2534DLL_API long PTAPI	PassThruWriteMsgs(unsigned long ChannelID, PASSTHRU_MSG *pMsg, unsigned long *pNumMsgs, unsigned long Timeout) {
	#pragma EXPORT
	if (pMsg == NULL || pNumMsgs == NULL) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruWriteMsgs(pMsg, pNumMsgs, Timeout));
}
PANDAJ2534DLL_API long PTAPI	PassThruStartPeriodicMsg(unsigned long ChannelID, PASSTHRU_MSG *pMsg, unsigned long *pMsgID, unsigned long TimeInterval) {
	#pragma EXPORT
	if (pMsg == NULL || pMsgID == NULL) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruStartPeriodicMsg(pMsg, pMsgID, TimeInterval));
}
PANDAJ2534DLL_API long PTAPI	PassThruStopPeriodicMsg(unsigned long ChannelID, unsigned long MsgID) {
	#pragma EXPORT
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruStopPeriodicMsg(MsgID));
}
PANDAJ2534DLL_API long PTAPI	PassThruStartMsgFilter(unsigned long ChannelID, unsigned long FilterType, PASSTHRU_MSG *pMaskMsg,
	                                                   PASSTHRU_MSG *pPatternMsg, PASSTHRU_MSG *pFlowControlMsg, unsigned long *pFilterID) {
	#pragma EXPORT
	if (FilterType != PASS_FILTER && FilterType != BLOCK_FILTER && FilterType != FLOW_CONTROL_FILTER) return ret_code(ERR_NULL_PARAMETER);
	if (!pFilterID || (!pMaskMsg && !pPatternMsg && !pFlowControlMsg)) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruStartMsgFilter(FilterType, pMaskMsg, pPatternMsg, pFlowControlMsg, pFilterID));
}
PANDAJ2534DLL_API long PTAPI	PassThruStopMsgFilter(unsigned long ChannelID, unsigned long FilterID) {
	#pragma EXPORT
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	return ret_code(get_channel(ChannelID)->PassThruStopMsgFilter(FilterID));
}
PANDAJ2534DLL_API long PTAPI	PassThruSetProgrammingVoltage(unsigned long DeviceID, unsigned long PinNumber, unsigned long Voltage) {
	#pragma EXPORT
	//Unused
	if (check_valid_DeviceID(DeviceID) != STATUS_NOERROR) return J25334LastError;
	auto& panda = get_device(DeviceID);

	switch (Voltage) {
	case SHORT_TO_GROUND:
		break;
	case VOLTAGE_OFF:
		break;
	default:
		if (!(5000 <= Voltage && Voltage <= 20000))
			return ret_code(ERR_NOT_SUPPORTED);
		break;
	}

	return ret_code(STATUS_NOERROR);
}
PANDAJ2534DLL_API long PTAPI	PassThruReadVersion(unsigned long DeviceID, char *pFirmwareVersion, char *pDllVersion, char *pApiVersion) {
	#pragma EXPORT
	if (!pFirmwareVersion || !pDllVersion || !pApiVersion) return ret_code(ERR_NULL_PARAMETER);
	if (check_valid_DeviceID(DeviceID) != STATUS_NOERROR) return J25334LastError;

	auto& panda = get_device(DeviceID);
	auto fw_version = panda->panda->get_version();
	strcpy_s(pFirmwareVersion, 80, fw_version.c_str());

	std::string j2534dll_ver;
	TCHAR pandalib_filename[MAX_PATH + 1] = { 0 };
	if (GetModuleFileName(thisdll, pandalib_filename, MAX_PATH) == 0) {
		j2534dll_ver = "error";
	} else {
		j2534dll_ver = GetProductAndVersion(pandalib_filename);
	}
	std::string fullver = "(" + j2534dll_ver + ")";
	strcpy_s(pDllVersion, 80, fullver.c_str());

	strcpy_s(pApiVersion, 80, J2534_APIVER_NOVEMBER_2004);
	return ret_code(STATUS_NOERROR);
}
PANDAJ2534DLL_API long PTAPI	PassThruGetLastError(char *pErrorDescription) {
	#pragma EXPORT
	if (pErrorDescription == NULL) return ret_code(ERR_NULL_PARAMETER);
	switch (J25334LastError) {
	case STATUS_NOERROR:
		strcpy_s(pErrorDescription, 80, "Function call successful.");
		break;
	case ERR_NOT_SUPPORTED:
		strcpy_s(pErrorDescription, 80, "Device cannot support requested functionality mandated in J2534.");
		break;
	case ERR_INVALID_CHANNEL_ID:
		strcpy_s(pErrorDescription, 80, "Invalid ChannelID value.");
		break;
	case ERR_INVALID_PROTOCOL_ID:
		strcpy_s(pErrorDescription, 80, "Invalid or unsupported ProtocolID, or resource conflict.");
		break;
	case ERR_NULL_PARAMETER:
		strcpy_s(pErrorDescription, 80, "NULL pointer supplied where a valid pointer is required.");
		break;
	case ERR_INVALID_IOCTL_VALUE:
		strcpy_s(pErrorDescription, 80, "Invalid value for Ioctl parameter.");
		break;
	case ERR_INVALID_FLAGS:
		strcpy_s(pErrorDescription, 80, "Invalid flag values.");
		break;
	case ERR_FAILED:
		strcpy_s(pErrorDescription, 80, "Undefined error.");
		break;
	case ERR_DEVICE_NOT_CONNECTED:
		strcpy_s(pErrorDescription, 80, "Unable to communicate with device.");
		break;
	case ERR_TIMEOUT:
		strcpy_s(pErrorDescription, 80, "Read or write timeout:");
		// PassThruReadMsgs() - No message available to read or could not read the specified number of messages. The actual number of messages read is placed in <NumMsgs>.
		// PassThruWriteMsgs() - Device could not write the specified number of messages. The actual number of messages sent on the vehicle network is placed in <NumMsgs>.
		break;
	case ERR_INVALID_MSG:
		strcpy_s(pErrorDescription, 80, "Invalid message structure pointed to by pMsg.");
		break;
	case ERR_INVALID_TIME_INTERVAL:
		strcpy_s(pErrorDescription, 80, "Invalid TimeInterval value.");
		break;
	case ERR_EXCEEDED_LIMIT:
		strcpy_s(pErrorDescription, 80, "Exceeded maximum number of message IDs or allocated space.");
		break;
	case ERR_INVALID_MSG_ID:
		strcpy_s(pErrorDescription, 80, "Invalid MsgID value.");
		break;
	case ERR_DEVICE_IN_USE:
		strcpy_s(pErrorDescription, 80, "Device is currently open.");
		break;
	case ERR_INVALID_IOCTL_ID:
		strcpy_s(pErrorDescription, 80, "Invalid IoctlID value.");
		break;
	case ERR_BUFFER_EMPTY:
		strcpy_s(pErrorDescription, 80, "Protocol message buffer empty.");
		break;
	case ERR_BUFFER_FULL:
		strcpy_s(pErrorDescription, 80, "Protocol message buffer full. Messages may have been lost.");
		break;
	case ERR_BUFFER_OVERFLOW:
		strcpy_s(pErrorDescription, 80, "A buffer overflow occurred and messages were lost.");
		break;
	case ERR_PIN_INVALID:
		strcpy_s(pErrorDescription, 80, "Invalid pin number, or pin number already in use.");
		break;
	case ERR_CHANNEL_IN_USE:
		strcpy_s(pErrorDescription, 80, "Channel number is currently connected.");
		break;
	case ERR_MSG_PROTOCOL_ID:
		strcpy_s(pErrorDescription, 80, "The Message's Protocol does not match the Channel's protocol.");
		break;
	case ERR_INVALID_FILTER_ID:
		strcpy_s(pErrorDescription, 80, "Invalid Filter ID value.");
		break;
	case ERR_NO_FLOW_CONTROL:
		strcpy_s(pErrorDescription, 80, "No flow control filter set or matched.");
		break;
	case ERR_NOT_UNIQUE:
		strcpy_s(pErrorDescription, 80, "This filter already exists.");
		break;
	case ERR_INVALID_BAUDRATE:
		strcpy_s(pErrorDescription, 80, "The desired baud rate cannot be achieved within SAE tolerance.");
		break;
	case ERR_INVALID_DEVICE_ID:
		strcpy_s(pErrorDescription, 80, "Device ID invalid.");
		break;
	}
	return ret_code(STATUS_NOERROR);
}
PANDAJ2534DLL_API long PTAPI	PassThruIoctl(unsigned long ChannelID, unsigned long IoctlID,
	                                          void *pInput, void *pOutput) {
	#pragma EXPORT
	if (check_valid_ChannelID(ChannelID) != STATUS_NOERROR) return J25334LastError;
	auto& dev_entry = get_device(ChannelID);
	//get_channel(ChannelID)

	switch (IoctlID) {
	case GET_CONFIG:
	{
		SCONFIG_LIST *inconfig = (SCONFIG_LIST*)pInput;
		if (inconfig == NULL)
			return ret_code(ERR_NULL_PARAMETER);
		for (unsigned int i = 0; i < inconfig->NumOfParams; i++) {
			try {
				inconfig->ConfigPtr[i].Value = get_channel(ChannelID)->processIOCTLGetConfig(inconfig->ConfigPtr[i].Parameter);
			} catch (int e) {
				return ret_code(e);
			}
		}
		break;
	}
	case SET_CONFIG:
	{
		SCONFIG_LIST *inconfig = (SCONFIG_LIST*)pInput;
		if (inconfig == NULL)
			return ret_code(ERR_NULL_PARAMETER);
		for (unsigned int i = 0; i < inconfig->NumOfParams; i++) {
			try {
				get_channel(ChannelID)->processIOCTLSetConfig(inconfig->ConfigPtr[i].Parameter, inconfig->ConfigPtr[i].Value);
			} catch (int e) {
				return ret_code(e);
			}
		}
		break;
	}
	case READ_VBATT:
		panda::PANDA_HEALTH health = dev_entry->panda->get_health();
		*(unsigned long*)pOutput = health.voltage;
		break;
	case FIVE_BAUD_INIT:
		if (!pInput || !pOutput) return ret_code(ERR_NULL_PARAMETER);
		return ret_code(get_channel(ChannelID)->init5b((SBYTE_ARRAY*)pInput, (SBYTE_ARRAY*)pOutput));
	case FAST_INIT:
		if (!pInput || !pOutput) return ret_code(ERR_NULL_PARAMETER);
		return ret_code(get_channel(ChannelID)->initFast((PASSTHRU_MSG*)pInput, (PASSTHRU_MSG*)pOutput));
	case CLEAR_TX_BUFFER:
		return ret_code(get_channel(ChannelID)->clearTXBuff());
	case CLEAR_RX_BUFFER:
		return ret_code(get_channel(ChannelID)->clearRXBuff());
	case CLEAR_PERIODIC_MSGS:
		return ret_code(get_channel(ChannelID)->clearPeriodicMsgs());
	case CLEAR_MSG_FILTERS:
		return ret_code(get_channel(ChannelID)->clearMsgFilters());
	case CLEAR_FUNCT_MSG_LOOKUP_TABLE:			// LOOKUP TABLE IS RELATED TO J1850 PWM. Unsupported.
		if (!pInput) return ret_code(ERR_NULL_PARAMETER);
		return ret_code(STATUS_NOERROR);
	case ADD_TO_FUNCT_MSG_LOOKUP_TABLE:			// LOOKUP TABLE IS RELATED TO J1850 PWM. Unsupported.
		if (!pInput) return ret_code(ERR_NULL_PARAMETER);
		return ret_code(STATUS_NOERROR);
	case DELETE_FROM_FUNCT_MSG_LOOKUP_TABLE:	// LOOKUP TABLE IS RELATED TO J1850 PWM. Unsupported.
		return ret_code(STATUS_NOERROR);
	case READ_PROG_VOLTAGE:
		*(unsigned long*)pOutput = 0;
		break;
	default:
		printf("Got unknown IIOCTL %X\n", IoctlID);
	}

	return ret_code(STATUS_NOERROR);
}
