#include "MCDFileDownloader.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 "otx/api/OTXAPI.hpp"

namespace otx {
	namespace flash {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		void MCDFileDownloader::onPrimitiveProgressInfo(asam::d::MCDDiagComPrimitive& primitive, asam::mcd::MCDLogicalLink& link, asam::A_UINT8 progress) {
			//printf("onPrimitiveProgressInfo %d\r\n", progress);
			ECUFlasherImpl* flasher = dynamic_cast<ECUFlasherImpl*>(this->mFlasher);

			{ // zone of lock.
				std::lock_guard<std::mutex> lock(flasher->mFieldLock);
				size_t downloadedSize = (double)this->mEcuFile->getSize() * ((double)progress / (double)100);
				size_t increase = downloadedSize - this->mEcuFile->getLastDownloadedSize();
				if (increase > 0) {
					flasher->mDownloadedSize += increase;
					this->mEcuFile->increaseDownloadedSize(increase); // 会设置最近一次下载大小。
				}
			}
			// 重新算一次，精度提高，且java计算的进度检测到从99%倒退回98%的情况，重新计算后就不会了，前面对increase做了判断。
			size_t newProgress = 100 * (double)this->mEcuFile->getDownloadedSize() / (double)this->mEcuFile->getSize();
			otx::flash::ECUFlasherFileDownloadProgressEventArg pearg(this->mFlasher, this->mECU, this->mEcuFile, newProgress);
			flasher->postFileDownloadProgress(pearg);
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		bool MCDFileDownloader::downloadFile(ECU* ecu, File* ecuFile) {
			this->mEcuFile = ecuFile;
			this->mECU = ecu;
			// step 1: 创建到ECU的逻辑连接。
			// NOTE: 关于MCD对象的使用，我们采用与OTX运行时库一致的对象，避免开启多个System客户端。
			//       OTX运行时库会导出MCDProjectProvider类，其静态实例中保存了正在共享的MCD对象。
			asam::mcd::MCDSystem* mcdSystem = otx::diagcom::MCDProjectProvider::getMCDSystem();
			asam::mcd::MCDProject* mcdProject = otx::diagcom::MCDProjectProvider::getSelectedProject();
			asam::d::MCDInterface* interface = otx::diagcom::MCDProjectProvider::getConnectedInterface();

			asam::mcd::MCDDatatypeShortName shortNameDbLogicalLink = ecu->getDbLogicalLinkName();
			asam::mcd::MCDDatatypeShortName variantName = ecu->getName();

			// 这个逻辑连接是个代理对象。
			asam::mcd::MCDLogicalLink* logicalLink = mcdProject->createLogicalLinkByVariantAndInterface(shortNameDbLogicalLink, variantName, *interface);
			asam::mcd::MCDLogicalLinkState state = logicalLink->getState();
			try {
				if (state == asam::mcd::MCDLogicalLinkState::eCREATED) {
					logicalLink->open();
					logicalLink->gotoOnline();
				}
			}
			catch (...) {
				printf("%s,%s\r\n", __FUNCTION__, to_string(__LINE__));
				return false;
			}


			// 创建刷写服务。
			asam::d::MCDAccessKey* accessKey = logicalLink->getIdentifiedVariantAccessKeys()->getItemByIndex(0);
			asam::mcd::MCDDbObject* dbObject = mcdProject->getDbProject()->getDbElementByAccessKey(*accessKey);
			asam::mcd::MCDDbLocation* location = dynamic_cast<asam::mcd::MCDDbLocation*>(dbObject);
			// asam::mcd::MCDDatatypeShortName name = "DiagnosticSessionControl";
			asam::d::MCDDbDiagComPrimitives* dbPrimitives = location->getDbDiagComPrimitivesByType(asam::mcd::MCDObjectType::eMCDDBFLASHJOB);
			asam::d::MCDDbDiagComPrimitive* dbPrimitive = dbPrimitives->getItemByIndex(0);
			delete dbPrimitives;

			asam::d::MCDDiagComPrimitive* cop = logicalLink->createDiagComPrimitiveByDbObject(*dbPrimitive);
			asam::d::MCDFlashJob* job = dynamic_cast<asam::d::MCDFlashJob*>(cop);
			// 添加事件监听器。
			asam::A_UINT32 eventId = job->setEventHandler(*this);

			// set flash file.

			// asam::mcd::MCDDatatypeShortName flashSessionName="session_EXE";
			// job->setSessionByName(flashSessionName);
			// asam::d::MCDDbFlashJob *dbJob=dynamic_cast<asam::d:eMCDDBFLASHJOB*>(dbPrimitive);
			asam::d::MCDDbFlashSession* flashSession = location->getDbFlashSessions()->getItemByIndex(0);
			asam::d::MCDDbFlashDataBlock* datablock = flashSession->getDbDataBlocks()->getItemByIndex(0);
			asam::A_ASCIISTRING temporaryDataFileName = ecuFile->getPath(); //"1150210050AD.vbf";
			// 如果参数包含了路径信息就需要指定全路径，否则只需要文件名，如果指定文件名，则
			// 系统会在项目配置的目录下寻找该文件，默认是项目根目录下的flashdata目录。
			datablock->getDbFlashData()->setActiveFileName(temporaryDataFileName);
			job->setSession(*flashSession);

			// set flash job parameters.

			asam::mcd::MCDDatatypeShortName pname_pincode = "PA_PINCode";
			asam::mcd::MCDDatatypeShortName pname_flashfileflag = "PA_FlashFileFlag";
			asam::d::MCDRequest* jobRequest = job->getRequest();
			asam::d::MCDRequestParameters* requestParameters = jobRequest->getRequestParameters();
			asam::d::MCDRequestParameter* paramPincode = requestParameters->getItemByName(pname_pincode);
			asam::d::MCDRequestParameter* paramFlashfileflag = requestParameters->getItemByName(pname_flashfileflag);
			// 设置参数的一种方法:直接设置内部编码值。
			// 另一种方法是直接设置参数的物理值。
			// ......
			std::wstring_convert<std::codecvt_utf8_utf16<char16_t, 0x10ffff, std::little_endian>, char16_t> conv;
			asam::mcd::MCDValue* physicalValue = paramPincode->createValue();
			std::string strValue = ecuFile->getPinCode(); //"FFFFFFFF";
			asam::A_UNICODE2STRING pstr = conv.from_bytes(strValue);
			physicalValue->setUnicode2string(pstr);
			//paramPincode->setValue(*physicalValue);

			asam::mcd::MCDValue* physicalValue2 = paramFlashfileflag->createValue();
			strValue = std::to_string(ecuFile->getFileType()); //"2";
			pstr = conv.from_bytes(strValue);
			physicalValue2->setUnicode2string(pstr);
			//paramFlashfileflag->setValue(*physicalValue);

			// execute job.

			asam::mcd::MCDResult* result = job->executeSync();
			// release event handler.
			job->releaseEventHandler(eventId);
			// release logical link.
			// do clear cop queue.
			// do goto offline.
			// do close.
			// do remove from project's logical link collection.
			mcdProject->removeLogicalLink(*logicalLink);
			// print job result.

			if (result->getType() == asam::mcd::MCDResultType::eRESPONSE) {

				asam::mcd::MCDResponses* resps = result->getResponses();
				int numOfResps = resps->getCount();
				for (int i = 0; i < numOfResps; i++) {
					asam::mcd::MCDResponse* item = resps->getItemByIndex(i);
					asam::mcd::MCDResponseParameters* respParams = item->getResponseParameters();
					int numOfRespParams = respParams->getCount();
					for (int index = 0; index < numOfRespParams; index++) {
						asam::mcd::MCDResponseParameter* respParam = respParams->getItemByIndex(index);
						//printResponseParameter(respParam, 0);
					}
				}

				asam::mcd::MCDResponse* firstResp = resps->getItemByIndex(0);
				asam::mcd::MCDResponseParameters* firstRespParams = firstResp->getResponseParameters();
				asam::mcd::MCDResponseParameter* rootParam = firstRespParams->getItemByIndex(0);
				asam::mcd::MCDDatatypeShortName sn = "PA_JobStatuHEX";
				asam::mcd::MCDResponseParameter* statusHexParam = rootParam->getParameters()->getItemByName(sn);
				this->mErrorCode = statusHexParam->getValue().getUint32();
				sn = "PA_AdditErrorInfor";
				asam::mcd::MCDResponseParameter* errInfoParam = rootParam->getParameters()->getItemByName(sn);
				std::wstring_convert<std::codecvt_utf8_utf16<char16_t, 0x10ffff, std::little_endian>, char16_t> conv;
				this->mMessage = conv.to_bytes(errInfoParam->getValue().getUnicode2string()); // errInfoParam->getValue().getUint32();
			}
			// 特别注意：result对象是Java层创建的，JVM会在合适时机回收掉它，如果
			// 这里也释放将发生二次释放危险，一旦二次释放，系统必定崩溃。
			// 等JVM回收是一种方案，另一种方案是采用内存登记管理制度，底层杜绝多次释放。
			// 现在使用克隆版本，需要释放，因为JVM创建的对象可能在任何时候被gc回收，如果回收后使用它必然崩溃。
			delete (result); // must do this.
			return true;
		}
		ECUFlasher* MCDFileDownloader::getFlasher() {
			return this->mFlasher;
		}
		void MCDFileDownloader::setFlasher(ECUFlasher* flasher) {
			this->mFlasher = flasher;
		}

		void MCDFileDownloader::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 MCDFileDownloader::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 MCDFileDownloader::printLevel(int level) {
			int width = level * 2;
			for (int i = 0; i < width; i++) {
				printf(" ");
			}
		}

	} // namespace flash
} // namespace otx