//..............................................................................
//
// Modbus PDU log record representation
//

import "log_Representation.jnc"
import "io_Modbus.jnc"
import "std_String.jnc"

//..............................................................................

enum {
	MaxDigestValueCount = 4
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

string_t getModbusCoilsDigest(
	uint8_t const* p,
	size_t count
) {
	switch (count) {
	case 0:
		return "[\e[31mNONE\e[m]";

	case 1:
		return $"\e[34m$(*p & 1)\e[m" ;
	}

	std.StringBuilder digest;
	digest.append("{");

	size_t digestCount = count < MaxDigestValueCount ? count : MaxDigestValueCount;
	for (size_t i = 0; i < digestCount; i++) {
		bool value = (*p & (1 << i)) != 0;
		digest.append($" \e[34m$value\e[m");
	}

	if (count > MaxDigestValueCount)
		digest.append(" ...");

	digest.append(" }");
	return digest.detachString();
}

void representModbusCoils(
	log.Representation* representation,
	uint8_t const* p,
	size_t count,
	size_t baseOffset,
	size_t offsetMultiplier,
	uint_t foldFlags
) {
	switch (count) {
	case 0:
		representation.addHyperText("    Value:     [\e[31mNONE\e[m]");
		return;

	case 1:
		representation.addHyperText(
			$"    Value:     \e^!%1,%2\e[34m%3\e[m" (
			baseOffset,
			sizeof(uint8_t)* offsetMultiplier,
			*p & 1
		));
		return;
	}


	if (foldFlags & 2) {
		representation.addHyperText($"    [\e^-2\e[34m-\e[m] Values[$count]");
		representModbusCoilValues(representation, p, count, baseOffset, offsetMultiplier);
	} else {
		representation.addHyperText($"    [\e^+2\e[34m+\e[m] Values[$count]");
		representation.addHyperText($"        $(getModbusCoilsDigest (p, count))");
	}
}

size_t getDecimalLength(uint8_t n) {
	return n < 10 ? 1 : n < 100 ? 2 : 3;
}

void representModbusCoilValues(
	log.Representation* representation,
	uint8_t const* p,
	size_t count,
	size_t baseOffset,
	size_t offsetMultiplier
) {
	char padding[] = "        ";
	size_t basePaddingLength = getDecimalLength(count - 1) + 1;

	size_t size = count / 8;
	if (count & 7)
		size++;

	size_t i = 0;
	for (size_t j = 0; i < count && j < size; j++) {
		uint8_t c = p[j];

		for (uint8_t mask = 1; i < count && mask; i++, mask <<= 1) {
			size_t indexLength = getDecimalLength(i);
			padding[basePaddingLength - indexLength] = 0;

			bool value = (c & mask) != 0;
			representation.addHyperText(
				$"        [%1]:$padding\e^!%2,%3\e[34m%4\e[m" (
				i,
				baseOffset + j * offsetMultiplier,
				sizeof(uint8_t)* offsetMultiplier,
				value
			));
		}
	}
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

string_t getModbusRegistersDigest(
	bigendian uint16_t const* p,
	size_t count
) {
	enum {
		MaxCount = 4
	}

	switch (count) {
	case 0:
		return "[\e[31mNONE\e[m]";

	case 1:
		return $"\e[34m$(*p)\e[m" ;
	}

	std.StringBuilder digest;
	digest.append($"{");

	size_t digestCount = count < MaxDigestValueCount ? count : MaxDigestValueCount;
	for (size_t i = 0; i < digestCount; i++) {
		uint16_t value = p[i];
		digest.append($" \e[34m$value\e[m");
	}

	if (count > MaxDigestValueCount)
		digest.append(" ...");

	digest.append(" }");
	return digest.detachString();
}

void representModbusRegisters(
	log.Representation* representation,
	bigendian uint16_t const* p,
	size_t count,
	size_t baseOffset,
	size_t offsetMultiplier,
	uint_t foldFlags
) {
	switch (count) {
	case 0:
		representation.addHyperText("    Value:     [\e[31mNONE\e[m]");
		return;

	case 1:
		representation.addHyperText(
			$"    Value:     \e^!%1,%2\e[34m0x%(3; 04x) (%3)\e[m" (
			baseOffset,
			sizeof(uint16_t)* offsetMultiplier,
			p[0]
		));
		return;
	}

	if (foldFlags & 2) {
		representation.addHyperText($"    [\e^-2\e[34m-\e[m] Values[$count]");
		representModbusRegisterValues(representation, p, count, baseOffset, offsetMultiplier);
	} else {
		representation.addHyperText($"    [\e^+2\e[34m+\e[m] Values[$count]");
		representation.addHyperText($"        $(getModbusRegistersDigest (p, count))");
	}
}

void representModbusRegisterValues(
	log.Representation* representation,
	bigendian uint16_t const* p,
	size_t count,
	size_t baseOffset,
	size_t offsetMultiplier
) {
	char padding[] = "        ";
	size_t basePaddingLength = getDecimalLength(count - 1) + 1;

	for (size_t i = 0; i < count; i++) {
		size_t indexLength = getDecimalLength(i);
		padding[basePaddingLength - indexLength] = 0;

		representation.addHyperText(
			$"        [%1]:$padding\e^!%2,%3\e[34m0x%(4; 04x) (%4)\e[m" (
			i,
			baseOffset + i * sizeof(uint16_t) * offsetMultiplier,
			sizeof(uint16_t) * offsetMultiplier,
			p[i]
		));
	}
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

string_t formatModbusFuncDescription(
	io.ModbusRole role,
	io.ModbusFunc func
) {
	jnc.EnumType* type = typeof(io.ModbusFunc);

	return (func & io.ModbusFunc.ExceptionFlag) ?
		$"%1 (\e[34mEXCEPTION\e[m)" (
			type.getValueString(func & ~io.ModbusFunc.ExceptionFlag)
		) :
		$"%1 (\e[90m%2\e[m)" (
			type.getValueString(func),
			role == io.ModbusRole.Master ? "Request" : "Reply"
		);

}

void representModbusPdu(
	log.Representation* representation,
	io.ModbusRole role,
	io.ModbusPduHdr const* hdr,
	size_t baseOffset,
	size_t offsetMultiplier = 1,
	uint_t foldFlags
) {
	representation.addHyperText(
		"\e[1mPDU\e[m\n"
		$"    Function:  \e^!%1,%2\e[34m%3\e[m %4" (
		baseOffset + offsetof(hdr.m_func) * offsetMultiplier,
		sizeof(hdr.m_func) * offsetMultiplier,
		hdr.m_func,
		formatModbusFuncDescription(role, hdr.m_func)
	));

	switch (hdr.m_func) {
	case io.ModbusFunc.ReadCoils:
	case io.ModbusFunc.ReadDiscreteInputs:
	case io.ModbusFunc.ReadHoldingRegisters:
	case io.ModbusFunc.ReadInputRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusReadPdu const* pdu = (io.ModbusReadPdu const*)hdr;
			representation.addHyperText(
				$"    Address:   \e^!%1,%2\e[34m0x%(3; 04x)\e[m\n"
				$"    Count:     \e^!%4,%5\e[34m%6\e[m" (

				baseOffset + offsetof(pdu.m_address) * offsetMultiplier,
				sizeof(pdu.m_address) * offsetMultiplier,
				pdu.m_address,

				baseOffset + offsetof(pdu.m_count) * offsetMultiplier,
				sizeof(pdu.m_count) * offsetMultiplier,
				pdu.m_count
			));
		} else {
			io.ModbusReadReplyPdu const* pdu = (io.ModbusReadReplyPdu const*)hdr;

			if (hdr.m_func <= io.ModbusFunc.ReadDiscreteInputs) {
				representation.addHyperText(
					$"    Size:      \e^!%1,%2\e[34m%3\e[m" (
					baseOffset + offsetof(pdu.m_size) * offsetMultiplier,
					sizeof(pdu.m_size) * offsetMultiplier,
					pdu.m_size
				));

				representModbusCoils(
					representation,
					(uint8_t const*)(pdu + 1),
					pdu.m_size * 8,
					baseOffset + sizeof(io.ModbusReadReplyPdu) * offsetMultiplier,
					offsetMultiplier,
					foldFlags
				);
			} else {
				representation.addHyperText(
					$"    Size:      \e^!%1,%2\e[34m%3\e[m%4" (
					baseOffset + offsetof(pdu.m_size) * offsetMultiplier,
					sizeof(pdu.m_size) * offsetMultiplier,
					pdu.m_size,
					(pdu.m_size & 1) ? " [\e[31mINVALID\e[m] \e[90m(expected: a multiple of 2)" : ""
				));

				representModbusRegisters(
					representation, (bigendian uint16_t const*)(pdu + 1),
					pdu.m_size / 2,
					baseOffset + sizeof(io.ModbusReadReplyPdu) * offsetMultiplier,
					offsetMultiplier,
					foldFlags
				);
			}
		}
		break;

	case io.ModbusFunc.WriteSingleCoil:
	case io.ModbusFunc.WriteSingleRegister:
		io.ModbusWriteSinglePdu const* pdu = (io.ModbusWriteSinglePdu const*)hdr;
		representation.addHyperText(
			$"    Address:   \e^!%1,%2\e[34m0x%(3; 04x)\e[m\n"
			$"    Value:     \e^!%4,%5\e[34m0x%(6; 04x) (%6)\e[m" (

			baseOffset + offsetof(pdu.m_address) * offsetMultiplier,
			sizeof(pdu.m_address) * offsetMultiplier,
			pdu.m_address,

			baseOffset + offsetof(pdu.m_value) * offsetMultiplier,
			sizeof(pdu.m_value) * offsetMultiplier,
			pdu.m_value
		));
		break;

	case io.ModbusFunc.WriteMultipleCoils:
	case io.ModbusFunc.WriteMultipleRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusWriteMultiplePdu const* pdu = (io.ModbusWriteMultiplePdu const*)hdr;
			representation.addHyperText(
				$"    Address:   \e^!%1,%2\e[34m0x%(3; 04x)\e[m\n"
				$"    Count:     \e^!%4,%5\e[34m%6\e[m\n"
				$"    Size:      \e^!%7,%8\e[34m%9\e[m" (

				baseOffset + offsetof(pdu.m_address) * offsetMultiplier,
				sizeof(pdu.m_address) * offsetMultiplier,
				pdu.m_address,

				baseOffset + offsetof(pdu.m_count) * offsetMultiplier,
				sizeof(pdu.m_count) * offsetMultiplier,
				pdu.m_count,

				baseOffset + offsetof(pdu.m_size) * offsetMultiplier,
				sizeof(pdu.m_size) * offsetMultiplier,
				pdu.m_size
			));

			if (hdr.m_func == io.ModbusFunc.WriteMultipleCoils)
				representModbusCoils(
					representation,
					(uint8_t const*)(pdu + 1),
					pdu.m_count,
					baseOffset + sizeof(io.ModbusWriteMultiplePdu) * offsetMultiplier,
					offsetMultiplier,
					foldFlags
				);
			else
				representModbusRegisters(
					representation,
					(bigendian uint16_t const*)(pdu + 1),
					pdu.m_count,
					baseOffset + sizeof(io.ModbusWriteMultiplePdu) * offsetMultiplier,
					offsetMultiplier,
					foldFlags
				);
		} else {
			io.ModbusWriteMultipleReplyPdu const* pdu = (io.ModbusWriteMultipleReplyPdu const*)hdr;
			representation.addHyperText(
				$"    Address:   \e^!%1,%2\e[34m0x%(3; 04x)\n"
				$"    Count:     \e^!%4,%5\e[34m%6" (

				baseOffset + offsetof(pdu.m_address) * offsetMultiplier,
				sizeof(pdu.m_address) * offsetMultiplier,
				pdu.m_address,

				baseOffset + offsetof(pdu.m_count) * offsetMultiplier,
				sizeof(pdu.m_count) * offsetMultiplier,
				pdu.m_count
			));
		}
		break;

	case io.ModbusFunc.MaskWriteRegister:
		io.ModbusMaskWritePdu const* pdu = (io.ModbusMaskWritePdu const*)hdr;
		representation.addHyperText(
			$"    Address:   \e^!%1,%2\e[34m0x%(3; 04x)\n"
			$"    AND-mask:  \e^!%4,%5\e[34m0x%(6; 04x)\n"
			$"    OR-mask:   \e^!%7,%8\e[34m0x%(9; 04x)" (

			baseOffset + offsetof(pdu.m_address) * offsetMultiplier,
			sizeof(pdu.m_address) * offsetMultiplier,
			pdu.m_address,

			baseOffset + offsetof(pdu.m_andMask) * offsetMultiplier,
			sizeof(pdu.m_andMask) * offsetMultiplier,
			pdu.m_andMask,

			baseOffset + offsetof(pdu.m_orMask) * offsetMultiplier,
			sizeof(pdu.m_orMask) * offsetMultiplier,
			pdu.m_orMask
		));
		break;

	case io.ModbusFunc.ReadWriteMultipleRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusReadWriteMultipleRegistersPdu const* pdu = (io.ModbusReadWriteMultipleRegistersPdu const*)hdr;
			representation.addHyperText(
				$"    R-address: \e^!%1,%2\e[34m0x%(3; 04x)\e[m\n"
				$"    R-count:   \e^!%4,%5\e[34m%6\e[m\n"
				$"    W-address: \e^!%7,%8\e[34m0x%(9; 04x)\e[m\n"
				$"    W-count:   \e^!%10,%11\e[34m%12\e[m\n"
				$"    W-size:    \e^!%13,%14\e[34m%15\e[m" (

				baseOffset + offsetof(pdu.m_readAddress) * offsetMultiplier,
				sizeof(pdu.m_readAddress) * offsetMultiplier,
				pdu.m_readAddress,

				baseOffset + offsetof(pdu.m_readCount) * offsetMultiplier,
				sizeof(pdu.m_readCount) * offsetMultiplier,
				pdu.m_readCount,

				baseOffset + offsetof(pdu.m_writeAddress) * offsetMultiplier,
				sizeof(pdu.m_writeAddress) * offsetMultiplier,
				pdu.m_writeAddress,

				baseOffset + offsetof(pdu.m_writeCount) * offsetMultiplier,
				sizeof(pdu.m_writeCount) * offsetMultiplier,
				pdu.m_writeCount,

				baseOffset + offsetof(pdu.m_writeSize) * offsetMultiplier,
				sizeof(pdu.m_writeSize) * offsetMultiplier,
				pdu.m_writeSize
			));

			representModbusRegisters(
				representation,
				(bigendian uint16_t const*)(pdu + 1),
				pdu.m_writeCount,
				baseOffset + sizeof(io.ModbusReadWriteMultipleRegistersPdu) * offsetMultiplier,
				offsetMultiplier,
				foldFlags
			);
		} else {
			io.ModbusReadReplyPdu const* pdu = (io.ModbusReadReplyPdu const*)hdr;

			representation.addHyperText(
				$"    Size:      \e^!%1,%2\e[34m%3\e[m%4" (
				baseOffset + offsetof(pdu.m_size) * offsetMultiplier,
				sizeof(pdu.m_size) * offsetMultiplier,
				pdu.m_size,
				(pdu.m_size & 1) ? " [\e[31mINVALID\e[m] \e[90m(expected: a multiple of 2)" : ""
			));

			representModbusRegisters(
				representation, (bigendian uint16_t const*)(pdu + 1),
				pdu.m_size / 2,
				baseOffset + sizeof(io.ModbusReadReplyPdu) * offsetMultiplier,
				offsetMultiplier,
				foldFlags
			);
		}
		break;

	case io.ModbusFunc.ReadExceptionStatus:
	case io.ModbusFunc.Diagnostics:
	case io.ModbusFunc.GetCommEventCounter:
	case io.ModbusFunc.GetCommEventLog:
	case io.ModbusFunc.ReportServerId:
	case io.ModbusFunc.ReadFileRecord:
	case io.ModbusFunc.WriteFileRecord:
	case io.ModbusFunc.ReadFifoQueue:
	case io.ModbusFunc.EncapsulatedInterface:
		break; // not supported yet

	default:
		if (hdr.m_func & io.ModbusFunc.ExceptionFlag) {
			io.ModbusExceptionPdu const* pdu = (io.ModbusExceptionPdu const*)hdr;

			representation.addHyperText(
				$"    Exception: \e^!%1,%2\e[31m%3\e[m - %4" (
				baseOffset + offsetof(pdu.m_exception) * offsetMultiplier,
				sizeof(pdu.m_exception) * offsetMultiplier,
				pdu.m_exception,
				typeof(io.ModbusException).getValueString(pdu.m_exception)
			));
		}
	}
}

string_t getModbusPduDigest(
	io.ModbusRole role,
	io.ModbusPduHdr const* hdr
) {
	std.StringBuilder digest;

	digest.append($"Fn: \e[34m%1\e[m %2" (
		hdr.m_func,
		formatModbusFuncDescription(role, hdr.m_func)
	));

	switch (hdr.m_func) {
	case io.ModbusFunc.ReadCoils:
	case io.ModbusFunc.ReadDiscreteInputs:
	case io.ModbusFunc.ReadHoldingRegisters:
	case io.ModbusFunc.ReadInputRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusReadPdu const* pdu = (io.ModbusReadPdu const*)hdr;
			digest.append($" Addr: \e[34m0x$(pdu.m_address; 04x)\e[m");
			if (pdu.m_count > 1)
				digest.append($" N: \e[34m$(pdu.m_count)\e[m");
		} else {
			io.ModbusReadReplyPdu const* pdu = (io.ModbusReadReplyPdu const*)hdr;
			digest.append(" ");

			if (hdr.m_func <= io.ModbusFunc.ReadDiscreteInputs)
				digest.append(getModbusCoilsDigest(
					(uint8_t const*)(pdu + 1),
					pdu.m_size * 8
				));
			else
				digest.append(getModbusRegistersDigest(
					(bigendian uint16_t const*)(pdu + 1),
					pdu.m_size / 2
				));
		}
		break;

	case io.ModbusFunc.WriteSingleCoil:
	case io.ModbusFunc.WriteSingleRegister:
		io.ModbusWriteSinglePdu const* pdu = (io.ModbusWriteSinglePdu const*)hdr;
		digest.append($" Addr: \e[34m0x$(pdu.m_address; 04x)\e[m Val: \e[34m$(pdu.m_value)\e[m");
		break;

	case io.ModbusFunc.WriteMultipleCoils:
	case io.ModbusFunc.WriteMultipleRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusWriteMultiplePdu const* pdu = (io.ModbusWriteMultiplePdu const*)hdr;
			digest.append($" Addr: \e[34m0x$(pdu.m_address; 04x)\e[m ");

			if (hdr.m_func == io.ModbusFunc.WriteMultipleCoils)
				digest.append(getModbusCoilsDigest(
					(uint8_t const*)(pdu + 1),
					pdu.m_count
				));
			else
				digest.append(getModbusRegistersDigest(
					(bigendian uint16_t const*)(pdu + 1),
					pdu.m_count
				));
		} else {
			io.ModbusWriteMultipleReplyPdu const* pdu = (io.ModbusWriteMultipleReplyPdu const*)hdr;
			digest.append($" Addr: \e[34m0x$(pdu.m_address; 04x) N: \e[34m$(pdu.m_count)\e[m");
		}
		break;

	case io.ModbusFunc.MaskWriteRegister:
		io.ModbusMaskWritePdu const* pdu = (io.ModbusMaskWritePdu const*)hdr;
		digest.append(
			$" Addr: \e[34m0x%(1; 04x)\e[m Masks: \e[34m0x%(2; 04x)\e[m/\e[34m0x%(3; 04x)\e[m"(
			pdu.m_address,
			pdu.m_andMask,
			pdu.m_orMask
		));
		break;

	case io.ModbusFunc.ReadWriteMultipleRegisters:
		if (role == io.ModbusRole.Master) {
			io.ModbusReadWriteMultipleRegistersPdu const* pdu = (io.ModbusReadWriteMultipleRegistersPdu const*)hdr;
			digest.append($" Addr: \e[34m0x$(pdu.m_readAddress; 04x)/0x$(pdu.m_writeAddress; 04x)\e[m ");
			digest.append(getModbusRegistersDigest(
				(bigendian uint16_t const*)(pdu + 1),
				pdu.m_writeCount
			));
		} else {
			io.ModbusReadReplyPdu const* pdu = (io.ModbusReadReplyPdu const*)hdr;
			digest.append(" ");
			digest.append(getModbusRegistersDigest(
				(bigendian uint16_t const*)(pdu + 1),
				pdu.m_size / 2
			));
		}
		break;

	case io.ModbusFunc.ReadExceptionStatus:
	case io.ModbusFunc.Diagnostics:
	case io.ModbusFunc.GetCommEventCounter:
	case io.ModbusFunc.GetCommEventLog:
	case io.ModbusFunc.ReportServerId:
	case io.ModbusFunc.ReadFileRecord:
	case io.ModbusFunc.WriteFileRecord:
	case io.ModbusFunc.ReadFifoQueue:
	case io.ModbusFunc.EncapsulatedInterface:
		break; // not supported yet

	default:
		if (hdr.m_func & io.ModbusFunc.ExceptionFlag) {
			io.ModbusExceptionPdu const* pdu = (io.ModbusExceptionPdu const*)hdr;

			digest.append(
				$" Err: \e[31m%1\e[m %2" (
				pdu.m_exception,
				typeof(io.ModbusException).getValueString(pdu.m_exception)
			));
		}
	}

	return digest.detachString();
}

//..............................................................................
