﻿#include "BinFileDownloader.hpp"
#include "asam/d/MCDAccessKeys.hpp"
#include "asam/d/MCDFlashJob.hpp"
#include "asam/d/MCDRequest.hpp"
#include "asam/mcd/MCDDbProject.hpp"
#include "asam/mcd/MCDLogicalLink.hpp"
#include "otx/diagcom/asam/MCDProjectProvider.hpp"
#include <codecvt>
#include <locale>
#include "ECUFlasherImpl.hpp"
#include <iostream>
#include <stdio.h>
#include "otx/api/OTXAPI.hpp"
#include "MCDCmdTrans.hpp"
#include "tracer.h"
#include "algorithm.h"

namespace otx {
	namespace flash {

		BinFileDownloader::BinFileDownloader() {
			this->mFlasher = nullptr;
			this->mErrorCode = 0;
		}

		void BinFileDownloader::onConfigurationRecordLoaded(asam::d::MCDConfigurationRecord& configurationRecord) {
			throw - 1;// 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onDefinableDynIdListChanged(asam::mcd::MCDValues& dynIdList, asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onFirstEvent() {
			//printf("onFirstEvent\r\n"); 
		}

		void BinFileDownloader::onInterfaceError(asam::d::MCDInterface& carinterface, asam::mcd::MCDError& error) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}
		void BinFileDownloader::onInterfacesChanged() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onInterfaceStatusChanged(asam::d::MCDInterface& carinterface, asam::d::MCDInterfaceStatus status) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLastEvent() {
			//printf("onLastEvent\r\n"); 
		}

		void BinFileDownloader::onLinkActivityStateIdle(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkActivityStateRunning(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkActivityStateSuspended(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkError(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDError& error) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkOneVariantIdentified(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkOneVariantSelected(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkQueueCleared(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkStateCommunication(asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkStateCreated(asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkStateOffline(asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkStateOnline(asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkVariantIdentified(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkVariantSelected(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onLinkVariantSet(asam::mcd::MCDLogicalLink& link, asam::mcd::MCDLogicalLinkState linkstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onMonitoringFramesReady(asam::d::MCDMonitoringLink& monLink) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveBufferOverflow(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveCanceledDuringExecution(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveCanceledFromQueue(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveError(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::mcd::MCDError& error) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveHasIntermediateResult(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::d::MCDResultState& resultstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveHasResult(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::d::MCDResultState& resultstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveJobInfo(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::A_ASCIISTRING& info) {
			//printf("onPrimitiveJobInfo\r\n");
		}

		void BinFileDownloader::onPrimitiveProgressInfo(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::A_UINT8 progress) {
			printf("onPrimitiveProgressInfo %d\r\n", progress);
		}

		void BinFileDownloader::onPrimitiveRepetitionStopped(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveTerminated(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::d::MCDResultState& resultstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onPrimitiveIdle(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::d::MCDResultState& resultstate) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onStaticInterfaceError(asam::mcd::MCDError& error) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemClampStateChanged(asam::A_ASCIISTRING& clamp, asam::d::MCDClampState clampState) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemConfigurationClosed() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemConfigurationOpened() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemError(asam::mcd::MCDError& error) {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemLocked() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。 
		}

		void BinFileDownloader::onSystemLogicallyConnected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemLogicallyDisconnected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemProjectDeselected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemProjectSelected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemUnlocked() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemVehicleInfoDeselected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		void BinFileDownloader::onSystemVehicleInfoSelected() {
			throw - 1; // 不可能发生的事件我们抛出异常，提醒开发者注意。
		}

		int BinFileDownloader::getErrorCode() { return this->mErrorCode; }
		std::string BinFileDownloader::getMessage() { return this->mMessage; }


		bool BinFileDownloader::downloadFile(ECU* ecu, File* ecuFile) {
			this->mEcuFile = ecuFile;
			this->mECU = ecu;
			string filepath = ecuFile->getPath();
			size_t fileSize = ecuFile->getSize();
			Tracer::debug(ecu->getId() + ":fileSize", to_string(fileSize));

			if (ecuFile->getKeyInfo().empty()) {
				Tracer::error(filepath, "invalid keyInfo");
				return false;
			}

			cmdTrans = new MCDCmdTrans(ecu);
			if (cmdTrans) {
				if (!cmdTrans->EraseMemory())
				{
					Tracer::error("EraseMemory", "failed");
					delete cmdTrans;
					return false;
				}

				if (!cmdTrans->RequestDownload(0, 0, fileSize))
				{
					Tracer::error("RequestDownload", "failed");
					delete cmdTrans;
					return false;
				}

				if (!transferFile(filepath, fileSize, cmdTrans->getFrameSize()))
				{
					Tracer::error("transferFile", "failed");
					delete cmdTrans;
					return false;
				}

				if (!cmdTrans->RequestTransferExit())
				{
					Tracer::error("RequestTransferExit", "failed");
					delete cmdTrans;
					return false;
				}


				if (!cmdTrans->VerifyAuthenticity(ecuFile->getKeyInfo(), true))
				{
					Tracer::error("VerifyAuthenticity", "failed");
					delete cmdTrans;
					return false;
				}

				//TODO 0:Data;  1:SBL;  2:App   3 : PBL
				if (3 == ecuFile->getFileType()) {
					if (!cmdTrans->FinishProgram())
					{
						Tracer::error("FinishProgram", "failed");
						delete cmdTrans;
						return false;
					}

					//reset all ecu
					delete(cmdTrans);
					ECUFlasherImpl* flasher = dynamic_cast<ECUFlasherImpl*>(this->mFlasher);
					otx::flash::ECUFlasherECUEventArg eventArg(flasher, ecu);
					MCDCmdBoardcast* boardcast = MCDCmdBoardcast::getInstance();
					flasher->postECUResetStart(eventArg);
					cmdTrans = new MCDCmdTrans(ecu);
					if (cmdTrans)
					{
						cmdTrans->sendCommand(CBinary("\x11\x81", 2));
						delete(cmdTrans);
						delete(boardcast);
						delay(10000);
					}
					flasher->postECUResetEnd(eventArg);
					
					bool rFlag = otx_reconnect_vehicle_ex(50, 5000);
					if (rFlag) {
						boardcast = MCDCmdBoardcast::getInstance();
						delay(170000);
					}
					else {
						return false;
					}

					boardcast = MCDCmdBoardcast::getInstance();
					if (boardcast == NULL) {
						return false;
					}
					//boardcast->EnterProgramMode();
					
					cmdTrans = new MCDCmdTrans(ecu);
					cmdTrans->EnterProgramMode();
					if (ecu->getSecurityAccess())
					{
						if (!cmdTrans->EnterSystem())
						{
							Tracer::error("EnterSystem", "failed");
							delete cmdTrans;
							return false;
						}
						string pinCode = ecu->getFiles()->at(0).getPinCode();
						if (0 == ecu->getSecurityAccessMode()) {
							if (!cmdTrans->SecurityAccess_ECOS(pinCode))
							{
								if (!cmdTrans->SecurityAccess("FFFFFFFF", 2))
								{
									delete(cmdTrans);
									return false;
								}
							}
						}
						else {
							if (!cmdTrans->SecurityAccess("FFFFFFFF", 2))
							{
								if (!cmdTrans->SecurityAccess_ECOS(pinCode))
								{
									delete(cmdTrans);
									return false;
								}
							}
						}
					}
				}

				delete cmdTrans;
				return true;
			}

			return true;
		}
		void BinFileDownloader::delay(int ms)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(ms));
		}

		bool BinFileDownloader::transferFile(string filepath, uint64_t nDataLength, int32_t nFrameSize)
		{
			Tracer::warn("nDataLength", to_string(nDataLength));
			Tracer::warn("nFrameSize", to_string(nFrameSize));

			bool bRet = true;

			FILE* fp = nullptr;
			fp = fopen(filepath.c_str(), "rb");
			if (fp == nullptr)
			{
				Tracer::error(__FUNCTION__, "invalid filepath: "+filepath);
				return false;
			}

			fseek(fp, 0, SEEK_SET);
			nFrameSize = MIN(1024 * 1024, nFrameSize);//TODO 单包数据大小

			uint8_t* pBuffer = (uint8_t*)malloc(nFrameSize + 8);
			memset(pBuffer, 0, nFrameSize + 8);

			uint8_t nCTR = 1;

			unsigned int nLastFrameSize = nDataLength % (nFrameSize);
			unsigned int nTotalFrames = nDataLength / (nFrameSize);

			size_t oldProgress = -1;
			for (unsigned int i = 0; i < nTotalFrames; i++)
			{
				int nCmdLen = nFrameSize + 2;
				pBuffer[0] = 0x36;
				pBuffer[1] = nCTR;
				int nSize = fread(pBuffer + 2, 1, nFrameSize, fp);
				CBinary binRecv = cmdTrans->SendFrame_Cmd36(pBuffer, nCmdLen);
				if (binRecv.IsEmpty())
				{
					delete[] pBuffer;
					pBuffer = NULL;

					fclose(fp);
					std::cout << "WriteOneBlock:SendFrame failed" << endl;
					bRet = false;
					return bRet;
				}

				nCTR++;
				if (nCTR > 0xFF)
				{
					nCTR = 0;
				}

				//TODO 更新进度
				ECUFlasherImpl* flasher = dynamic_cast<ECUFlasherImpl*>(this->mFlasher);
				size_t mSize = this->mECU->getDownloadedSize() + nFrameSize;
				this->mECU->setDownloadedSize(mSize);
				this->mEcuFile->increaseDownloadedSize(nFrameSize);

				size_t newProgress = 100 * (double)this->mECU->getDownloadedSize() / (double)this->mECU->getTotalSize();
				if (oldProgress != newProgress) {
					otx::flash::ECUFlasherFileDownloadProgressEventArg pearg(this->mFlasher, this->mECU, this->mEcuFile, newProgress);
					flasher->postFileDownloadProgress(pearg);
					oldProgress = newProgress;
				}
			}

			if (nLastFrameSize > 0)
			{
				int nCmdLen = nLastFrameSize + 2;
				int nSize = fread(pBuffer + 2, 1, nLastFrameSize, fp);
				pBuffer[0] = 0x36;
				pBuffer[1] = nCTR;

				CBinary binRecv = cmdTrans->SendFrame_Cmd36(pBuffer, nCmdLen);
				if (binRecv.IsEmpty())
				{
					delete[] pBuffer;
					pBuffer = NULL;

					fclose(fp);
					std::cout << "error" << "WriteOneBlock:SendFrame failed" << endl;
					bRet = false;
					return bRet;
				}

				//TODO 更新进度
				ECUFlasherImpl* flasher = dynamic_cast<ECUFlasherImpl*>(this->mFlasher);
				size_t size = this->mECU->getDownloadedSize() + nLastFrameSize;
				this->mECU->setDownloadedSize(size);
				this->mEcuFile->increaseDownloadedSize(nLastFrameSize);

				size_t newProgress = 100 * (double)this->mECU->getDownloadedSize() / (double)this->mECU->getTotalSize();
				if (oldProgress != newProgress) {
					otx::flash::ECUFlasherFileDownloadProgressEventArg pearg(this->mFlasher, this->mECU, this->mEcuFile, newProgress);
					flasher->postFileDownloadProgress(pearg);
					oldProgress = newProgress;
				}
			}

			if (pBuffer != NULL)
			{
				delete[] pBuffer;
				pBuffer = NULL;
			}

			bRet = true;
			fclose(fp);
			return bRet;
		}

		ECUFlasher* BinFileDownloader::getFlasher() {
			return this->mFlasher;
		}
		void BinFileDownloader::setFlasher(ECUFlasher* flasher) {
			this->mFlasher = flasher;
		}

		void BinFileDownloader::printResponseParameter(asam::mcd::MCDResponseParameter* param, int level) {
			asam::mcd::MCDResponseParameters* subParams = param->getParameters();
			asam::A_ASCIISTRING pname = param->getShortName();

			printLevel(level);
			if (subParams->getCount() == 0) {
				asam::mcd::MCDValue pvalue = param->getValue();
				printValue(pname, pvalue);
			}
			else {
				// printf("+ %s(%u): \r\n", pname.c_str(), pvalue.getUint32());
				printf("+ %s: \r\n", pname.c_str());
				for (int i = 0; i < subParams->getCount(); i++) {
					printResponseParameter(subParams->getItemByIndex(i), level + 1);
				}
			}
		}
		void BinFileDownloader::printValue(asam::A_ASCIISTRING& pname, asam::mcd::MCDValue& pvalue) {
			std::wstring_convert<std::codecvt_utf8_utf16<char16_t, 0x10ffff, std::little_endian>, char16_t> conv;
			if (pvalue.isValid()) {
				asam::mcd::MCDDataType dt = pvalue.getDataType();
				if (dt == asam::mcd::MCDDataType::eA_UINT32) {
					printf("%s=%u\r\n", pname.c_str(), pvalue.getUint32());
				}
				else if (dt == asam::mcd::MCDDataType::eA_UINT16) {
					printf("%s=%u\r\n", pname.c_str(), pvalue.getUint16());
				}
				else if (dt == asam::mcd::MCDDataType::eA_UINT8) {
					printf("%s=%u\r\n", pname.c_str(), pvalue.getUint8());
				}
				else if (dt == asam::mcd::MCDDataType::eA_INT32) {
					printf("%s=%d\r\n", pname.c_str(), pvalue.getInt32());
				}
				else if (dt == asam::mcd::MCDDataType::eA_INT16) {
					printf("%s=%d\r\n", pname.c_str(), pvalue.getInt16());
				}
				else if (dt == asam::mcd::MCDDataType::eA_INT8) {
					printf("%s=%d\r\n", pname.c_str(), pvalue.getInt8());
				}
				else if (dt == asam::mcd::MCDDataType::eA_ASCIISTRING) {
					printf("%s=%s\r\n", pname.c_str(), pvalue.getAsciistring().c_str());
				}
				else if (dt == asam::mcd::MCDDataType::eA_UNICODE2STRING) {
					printf("%s=%s\r\n", pname.c_str(), conv.to_bytes(pvalue.getValueAsString()).c_str());
				}
				else {
					printf("%s other data type,ignored.\r\n", pname.c_str());
				}
			}
			else {
				printf("%s invalid.\r\n", pname.c_str());
			}
		}
		void BinFileDownloader::printLevel(int level) {
			int width = level * 2;
			for (int i = 0; i < width; i++) {
				printf(" ");
			}
		}

	} // namespace flash
} // namespace otx