#include "stdafx.h"
#include "MessageTx_ISO15765.h"
#include "constants_ISO15765.h"

//in microseconsa
#define TIMEOUT_FC 250000 //Flow Control
#define TIMEOUT_CF 250000 //Consecutive Frames

MessageTx_ISO15765::MessageTx_ISO15765(
	std::shared_ptr<J2534Connection> connection_in,
	PASSTHRU_MSG& to_send,
	std::shared_ptr<J2534MessageFilter> filter
) : MessageTxTimeoutable(connection_in, to_send), filter(filter), frames_sent(0),
consumed_count(0), txInFlight(FALSE), sendAll(FALSE), block_size(0), numWaitFrames(0), didtimeout(FALSE), issuspended(FALSE){

	CANid = ((uint8_t)fullmsg.Data[0]) << 24 | ((uint8_t)fullmsg.Data[1]) << 16 |
		((uint8_t)fullmsg.Data[2]) << 8 | ((uint8_t)fullmsg.Data[3]);

	payload = fullmsg.Data.substr(addressLength());

	if (check_bmask(fullmsg.TxFlags, ISO15765_ADDR_TYPE))
		data_prefix = fullmsg.Data[4];

	if (payload.size() <= (7 - data_prefix.size())) {
		isMultipart = FALSE;
		auto framepayload = data_prefix + std::string(1, (char)payload.size()) + payload;
		if (check_bmask(this->fullmsg.TxFlags, ISO15765_FRAME_PAD))
			framepayload += std::string(8 - framepayload.size(), '\x00');
		framePayloads.push_back(framepayload);
	} else {
		isMultipart = TRUE;
		unsigned long first_payload_len = 6 - data_prefix.size(); // 5 or 6
		std::string framepayload = data_prefix +
			(char)(0x10 | ((payload.size() >> 8) & 0xF)) +
			(char)(payload.size() & 0xFF) +
			payload.substr(0, first_payload_len);
		framePayloads.push_back(framepayload);

		unsigned int pktnum = 1;
		uint8_t CFDatSize = 7 - data_prefix.size();
		while (TRUE) {
			framepayload = data_prefix + (char)(0x20 | (pktnum % 0x10)) +
				payload.substr(first_payload_len + (CFDatSize * (pktnum-1)), CFDatSize);

			if (check_bmask(this->fullmsg.TxFlags, ISO15765_FRAME_PAD))
				framepayload += std::string(8 - framepayload.size(), '\x00');
			framePayloads.push_back(framepayload);
			if (first_payload_len + (CFDatSize * pktnum) >= payload.size()) break;
			pktnum++;
		}

	}
};

unsigned int MessageTx_ISO15765::addressLength() {
	return check_bmask(fullmsg.TxFlags, ISO15765_ADDR_TYPE) ? 5 : 4;
}

void MessageTx_ISO15765::execute() {
	if (didtimeout || issuspended) return;
	if (this->frames_sent >= this->framePayloads.size()) return;
	if (block_size == 0 && !sendAll && this->frames_sent > 0) return;
	if (block_size > 0 && !sendAll) block_size--;

	if (auto conn_sp = this->connection.lock()) {
		if (auto panda_dev_sp = conn_sp->getPandaDev()) {
			auto& outFramePayload = this->framePayloads[this->frames_sent];
			if (panda_dev_sp->panda->can_send(this->CANid, check_bmask(this->fullmsg.TxFlags, CAN_29BIT_ID),
				(const uint8_t*)outFramePayload.c_str(), (uint8_t)outFramePayload.size(), panda::PANDA_CAN1) == FALSE) {
				return;
			}

			this->txInFlight = TRUE;
			this->frames_sent++;
			panda_dev_sp->txMsgsAwaitingEcho.push(shared_from_this());
		}
	}
}

//Returns TRUE if receipt is consumed by the msg, FALSE otherwise.
BOOL MessageTx_ISO15765::checkTxReceipt(J2534Frame frame) {
	if (!txInFlight) return FALSE;
	if (frame.Data.size() >= addressLength() + 1 && (frame.Data[addressLength()] & 0xF0) == FRAME_FLOWCTRL) return FALSE;

	if (frame.Data == fullmsg.Data.substr(0, 4) + framePayloads[frames_sent - 1] &&
		((this->fullmsg.TxFlags & CAN_29BIT_ID) == (frame.RxStatus & CAN_29BIT_ID))) { //Check receipt is expected
		txInFlight = FALSE; //Received the expected receipt. Allow another msg to be sent.

		if (this->recvCount == 0 && this->framePayloads.size() > 1)
			scheduleTimeout(TIMEOUT_FC);

		if (frames_sent == framePayloads.size()) { //Check message done
			if (auto conn_sp = std::static_pointer_cast<J2534Connection_ISO15765>(this->connection.lock())) {
				unsigned long flags = (filter == nullptr) ? fullmsg.TxFlags : this->filter->flags;

				J2534Frame outframe(ISO15765);
				outframe.Timestamp = frame.Timestamp;
				outframe.RxStatus = TX_MSG_TYPE | TX_INDICATION | (flags & (ISO15765_ADDR_TYPE | CAN_29BIT_ID));
				outframe.Data = frame.Data.substr(0, addressLength());
				conn_sp->addMsgToRxQueue(outframe);

				if (conn_sp->loopback) {
					J2534Frame outframe(ISO15765);
					outframe.Timestamp = frame.Timestamp;
					outframe.RxStatus = TX_MSG_TYPE | (flags & (ISO15765_ADDR_TYPE | CAN_29BIT_ID));
					outframe.Data = this->fullmsg.Data;
					conn_sp->addMsgToRxQueue(outframe);
				}

			} //TODO what if fails
		} else {
			//Restart timeout if we are waiting for a flow control frame.
			//FC frames are required when we are not sending all, the
			//current block_size batch has not been sent, a FC message has
			//already been received (differentiating from first frame), the
			//message is not finished, and there is more than one frame in
			//the message.
			if (block_size == 0 && recvCount != 0 && !sendAll && !this->isFinished() && this->framePayloads.size() > 1)
				scheduleTimeout(TIMEOUT_CF);
		}
		return TRUE;
	}
	return FALSE;
}

BOOL MessageTx_ISO15765::isFinished() {
	return this->frames_sent == this->framePayloads.size() && !txInFlight;
}

BOOL MessageTx_ISO15765::txReady() {
	return block_size > 0 || sendAll || this->frames_sent == 0;
}

void MessageTx_ISO15765::reset() {
	frames_sent = 0;
	consumed_count = 0;
	block_size = 0;
	txInFlight = FALSE;
	sendAll = FALSE;
	numWaitFrames = 0;
	didtimeout = FALSE;
}

void MessageTx_ISO15765::onTimeout() {
	didtimeout = TRUE;
	if (auto conn_sp = std::static_pointer_cast<J2534Connection_ISO15765>(this->connection.lock())) {
		if (auto panda_dev_sp = conn_sp->getPandaDev()) {
			panda_dev_sp->removeConnectionTopAction(conn_sp, shared_from_this());
		}
	}
}

void MessageTx_ISO15765::flowControlContinue(uint8_t block_size, std::chrono::microseconds separation_time) {
	this->issuspended = FALSE;
	this->block_size = block_size;
	this->delay = separation_time;
	this->sendAll = block_size == 0;
	this->recvCount++;
}

void MessageTx_ISO15765::flowControlWait(unsigned long N_WFTmax) {
	this->issuspended = TRUE;
	this->recvCount++;
	this->numWaitFrames++;
	this->sendAll = FALSE;
	this->block_size = block_size;
	this->delay = std::chrono::microseconds(0);
	//Docs are vague on if 0 means NO WAITS ALLOWED or NO LIMIT TO WAITS.
	//It is less likely to cause issue if NO LIMIT is assumed.
	if (N_WFTmax > 0 && this->numWaitFrames > N_WFTmax) {
		this->onTimeout(); //Trigger self destruction of message.
	} else {
		scheduleTimeout(TIMEOUT_FC);
	}
}

void MessageTx_ISO15765::flowControlAbort() {
	this->recvCount++; //Invalidate future timeout actions.
	this->onTimeout(); //Trigger self destruction of message.
}
