import "log_Representation.jnc"
import "log_RepresentStruct.jnc"
import "ui_ColorRangeTree.jnc"
import "ias_FieldCodec.jnc"

namespace log {

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

enum DynamicLayoutBinPartCode {
	Binary = 1, // blocks marked with the "binary" attribute
	Raw,        // raw block following the dynamic layout representation
}

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

class RepresentDynamicLayoutContext {
	Representation safe* m_representation;
	ui.ColorRangeTree m_colorRangeTree;
	uint_t m_foldFlags;
	uint_t m_rawBinPartCode; // DynamicLayoutBinPartCode.Raw + 0, 1, ...
}

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

typedef void DynamicLayoutFunc(jnc.DynamicLayout* layout);

typedef string_t ToStringFunc(void const* p);

typedef string_t ToStringFuncEx(
	void const* p,
	size_t size,
	jnc.ModuleItemDecl* decl
);

typedef string_t IndexToStringFunc(size_t index);

string_t stdIndexToString(size_t index) {
	return $"[%1]"(index);
}

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

string_t getOffsetHyperlink(
	uint_t partCode,
	size_t offset,
	size_t size
) {
	return $"\e^!%1,%2,%3\e\\"(partCode, offset, size);
}

string_t getItemName(jnc.ModuleItemDecl safe* decl) {
	string_t name = decl.findAttributeValue("displayName");
	if (!name)
		name = decl.m_name;

	string_t attr = decl.findAttributeValue("displayNameAttr");
	return attr ? $"%1%2\e[m"(attr, name) : name;
}

string_t getItemName(
	jnc.ModuleItemDecl safe* decl,
	uint_t partCode,
	size_t offset,
	size_t size
) {
	string_t name = getItemName(decl);
	if (decl.findAttribute("root")) // don't add hyperlink to root groups
		return name;

	string_t hyperlink = getOffsetHyperlink(partCode, offset, size);
	return $"%1%2"(hyperlink, name);
}

string_t getItemDigest(
	jnc.ModuleItemDecl safe* decl,
	void const* p,
	size_t size
) {
	variant_t attr = decl.findAttributeValue("digestFunc");
	if (!attr)
		return null;

	ToStringFuncEx thin* func = attr;
	return func ? func(p, size, decl) : null;
}

string_t getItemValueString(
	jnc.ModuleItemDecl safe* decl,
	jnc.Type safe* type,
	jnc.PtrTypeFlags ptrTypeFlags = 0,
	uint_t bitOffset = 0,
	uint_t bitCount = 0,
	void const* p
) {
	ToStringFuncEx thin* formatFunc = decl.findAttributeValue("formatFunc");
	if (formatFunc)
		return formatFunc(p, type.m_size, decl);

	string_t formatSpec = decl.findAttributeValue("formatSpec");
	jnc.Type* displayType = decl.findAttributeValue("displayType");
	if (!displayType)
		displayType = type;

	if (!(ptrTypeFlags & (jnc.PtrTypeFlags.BitField | jnc.PtrTypeFlags.BigEndian)))
		return displayType.getValueString(p, formatSpec);

	jnc.DataPtrType* displayPtrType = (ptrTypeFlags & jnc.PtrTypeFlags.BitField) ?
		displayType.getDataPtrType(bitOffset, bitCount,,, ptrTypeFlags) :
		displayType.getDataPtrType(,, ptrTypeFlags);

	return displayPtrType.getTargetValueString(p, formatSpec);
}

ToStringFunc* getElementValueStringFunc(
	jnc.ModuleItemDecl safe* decl,
	jnc.Type safe* type,
	jnc.PtrTypeFlags ptrTypeFlags = 0
) {
	ToStringFuncEx thin* formatFunc = decl.findAttributeValue("valueFormatFunc");
	if (formatFunc)
		return formatFunc~(, type.m_size, decl);

	string_t formatSpec = decl.findAttributeValue("valueFormatSpec");
	jnc.Type* displayType = decl.findAttributeValue("valueDisplayType");
	if (!displayType)
		displayType = type;

	if (!(ptrTypeFlags & jnc.PtrTypeFlags.BigEndian)) {
		// choose the correct overload
		typedef string_t GetValueStringFunc(
			void const* p,
			string_t formatSpec
		) const;

		GetValueStringFunc* getValueString = displayType.getValueString;
		return getValueString~(, formatSpec);
	}

	jnc.DataPtrType* ptrType = displayType.getDataPtrType(,, ptrTypeFlags);
	return ptrType.getTargetValueString~(, formatSpec);
}

void setItemBackColor(
	RepresentDynamicLayoutContext safe* context,
	jnc.ModuleItemDecl safe* decl,
	size_t offset,
	size_t length
) {
	jnc.Attribute* attr = decl.findAttribute("backColor");
	if (!attr)
		return;

	uint_t color = attr.m_value;
	uint_t prevColor = context.m_representation.m_lineAttr.m_backColor;
	context.m_representation.m_lineAttr.m_backColor = color;
	context.m_colorRangeTree.add(offset, length, color);
}

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

void representDynamicLayout(
	Representation safe* representation,
	jnc.DynamicLayout safe* layout,
	uint_t foldFlags,
	uint_t rawBinPartCode = DynamicLayoutBinPartCode.Raw,
	string_t indent = ""
) {
	RepresentDynamicLayoutContext context;
	context.m_representation = representation;
	context.m_foldFlags = foldFlags;
	context.m_rawBinPartCode = rawBinPartCode;

	log.LineAttr prevAttr = representation.m_lineAttr;
	bool isExpanded = representDynamicSectionGroup(context, layout, 0, layout.m_p, indent);
	representation.m_lineAttr = prevAttr; // restore

	if (!isExpanded)
		return;

	representation.m_lineAttr.m_textColor = ui.StdColor.BrightBlack;
	representation.addBreak();
	representation.addBin(rawBinPartCode, layout.m_p, layout.m_size);
	representation.m_lineAttr.m_textColor = prevAttr.m_textColor; // restore again

	size_t rangeCount = context.m_colorRangeTree.m_rangeCount;
	if (rangeCount <= 1) // only retro-colorize if there are multiple colors!
		return;

	ui.ColorRange* ranges = new ui.ColorRange[rangeCount];
	context.m_colorRangeTree.flatten(ranges, rangeCount);

	size_t end = layout.m_size;
	for (size_t i = 0; i < rangeCount; i++) {
		ui.ColorRange range = ranges[i];
		ui.TextAttr attr = { ui.ColorFlags.Undefined, range.m_color, ui.FontFlags.Undefined	}
		representation.retroColorize(range.m_offset - layout.m_size, range.m_length, attr);
	}
}

// returns false if the root group is collapsed

bool representDynamicSectionGroup(
	RepresentDynamicLayoutContext safe* context,
	jnc.DynamicSectionGroup safe* group,
	size_t offset0,
	void const* p0,
	string_t indent
) {
	uint_t baseBackColor = context.m_representation.m_lineAttr.m_backColor;

	for (size_t i = 0; i < group.m_sectionCount; i++) {
		jnc.DynamicSection safe* section = group.m_sectionArray[i];
		size_t offset = offset0 + section.m_offset;
		void const* p = p0 + section.m_offset;

		if (section.m_sectionKind == jnc.DynamicSectionKind.Struct) { // shortcut
			jnc.StructType safe* type = dynamic (jnc.StructType*)section.m_type;
			representFields(context, type, offset, p, indent);
			continue;
		}

		jnc.ModuleItemDecl safe* decl = section.m_decl;
		setItemBackColor(context, decl, offset, section.m_size);
		if (representBinary(context, decl, offset, p, section.m_size, indent))
			continue;

		switch (section.m_sectionKind) {
		case jnc.DynamicSectionKind.Array:
			representArray(
				context,
				decl,
				section.m_type,
				section.m_ptrTypeFlags,
				section.m_elementCount,
				offset,
				p,
				indent
			);
			break;

		case jnc.DynamicSectionKind.Field:
			representField(
				context,
				decl,
				section.m_type,
				section.m_ptrTypeFlags,
				section.m_bitOffset,
				section.m_bitCount,
				offset,
				p,
				indent
			);
			break;

		case jnc.DynamicSectionKind.Group:
			string_t subIndent;
			if (section.m_decl.findAttribute("ungroup"))
				subIndent = indent;
			else {
				if (!representGroupHeader(context, decl, offset, p, section.m_size, indent))
					return false;

				subIndent = indent + "    ";
			}

			representDynamicSectionGroup(context, section, offset0, p0, subIndent);
			break;
		}

		context.m_representation.m_lineAttr.m_backColor = baseBackColor; // restore
	}

	return true;
}

// returns true if expanded

bool representGroupHeader(
	RepresentDynamicLayoutContext safe* context,
	jnc.ModuleItemDecl safe* decl,
	size_t offset,
	void const* p,
	size_t size,
	string_t indent
) {
	string_t name = getItemName(decl, context.m_rawBinPartCode, offset, size);
	uint_t foldFlag = decl.findAttributeValue("foldFlag");

	// always add \t to preserve the name-value table column position

	if (!foldFlag)
		context.m_representation.addHyperText($"%1%2:\t"(indent, name));
	else if (context.m_foldFlags & foldFlag)
		context.m_representation.addHyperText($"%1[\e^-%2\e[34m-\e[m] %3:\t"(
			indent,
			foldFlag,
			name
		));
	else {
		string_t digest = getItemDigest(decl, p, size);
		if (digest.m_length)
			context.m_representation.addHyperText($"%1[\e^+%2\e[34m+\e[m] %3:\t%4"(
				indent,
				foldFlag,
				name,
				digest
			));
		else
			context.m_representation.addHyperText($"%1[\e^+%2\e[34m+\e[m] %3\t"(
				indent,
				foldFlag,
				name
			));

		return false;
	}

	return true;
}

// returns true if this is a binary (or encoded) field and it was represented

bool representBinary(
	RepresentDynamicLayoutContext safe* context,
	jnc.ModuleItemDecl safe* decl,
	size_t offset,
	void const* p,
	size_t size,
	string_t indent
) {
	jnc.ClassType* codecType = decl.findAttributeValue("codec");
	if (!codecType ||
		codecType.findBaseTypeOffset(typeof(ias.FieldCodec)) == -1
	) {
		if (decl.findAttribute("binary")) {
			if (representGroupHeader(context, decl, offset, p, size, indent))
				context.m_representation.addBin(DynamicLayoutBinPartCode.Binary, p, size);
			return true;
		}

		return false;
	}

	if (!representGroupHeader(context, decl, offset, p, size, indent))
		return true;

	ias.FieldCodec safe* codec = dynamic (ias.FieldCodec*)codecType.createObject();
	std.Buffer buffer;
	codec.decode(buffer, p, size);

	DynamicLayoutFunc thin* layoutFunc = decl.findAttributeValue("packetTemplate");
	if (!layoutFunc) {
		uint_t prevColor = context.m_representation.m_lineAttr.m_textColor;
		context.m_representation.m_lineAttr.m_textColor = ui.StdColor.BrightBlack;
		context.m_representation.addBin(DynamicLayoutBinPartCode.Binary, buffer.m_p, buffer.m_size);
		context.m_representation.m_lineAttr.m_textColor = prevColor;
		return true;
	}

	jnc.DynamicLayout subLayout(jnc.DynamicLayoutMode.Save, buffer.m_p, buffer.m_size);
	layoutFunc(subLayout);

	representDynamicLayout(
		context.m_representation,
		subLayout,
		context.m_foldFlags,
		context.m_rawBinPartCode + 1,
		indent + "    "
	);

	return true;
}

void representArray(
	RepresentDynamicLayoutContext safe* context,
	jnc.ModuleItemDecl safe* decl,
	jnc.Type* elementType,
	jnc.PtrTypeFlags ptrTypeFlags,
	size_t elementCount,
	size_t offset,
	void const* p,
	string_t indent
) {
	size_t elementSize = elementType.m_size;
	size_t arraySize = elementSize * elementCount;

	if (!decl.findAttribute("ungroup")) {
		ToStringFuncEx thin* formatFunc = decl.findAttributeValue("formatFunc");
		string_t value =
			formatFunc ? formatFunc(p, arraySize, decl) :
			elementType.m_typeKind == jnc.TypeKind.Char ? string_t(p, elementCount) :
			null;

		if (value) {
			context.m_representation.addHyperText($"%1%2:\t\e[34m%3"(
				indent,
				getItemName(decl, context.m_rawBinPartCode, offset, arraySize),
				value
			));
			return;
		}

		if (!representGroupHeader(context, decl, offset, p, arraySize, indent))
			return;

		indent += "    ";
	}

	if (elementType.m_typeKindFlags & jnc.TypeKindFlags.Derivable) {
		jnc.DerivableType safe* derivableType = dynamic (jnc.DerivableType*)elementType;
		string_t nextIndent = indent + "    ";
		uint_t prevFoldFlags = context.m_foldFlags;
		context.m_foldFlags = -1; // no individual folds for array elements

		for (size_t i = 0; i < elementCount; i++) {
			context.m_representation.addHyperText($"%1%2[%3]:"(
				indent,
				getOffsetHyperlink(context.m_rawBinPartCode, offset, elementSize),
				i
			));

			representFields(context, derivableType, offset, p, nextIndent);
			p += elementSize;
			offset += elementSize;
		}

		context.m_foldFlags = prevFoldFlags; // restore
	} else {
		ToStringFunc* getValueStringFunc = getElementValueStringFunc(decl, elementType, ptrTypeFlags);
		IndexToStringFunc* getIndexStringFunc = decl.findAttributeValue("indexFormatFunc");
		if (!getIndexStringFunc)
			getIndexStringFunc = stdIndexToString;

		for (size_t i = 0; i < elementCount; i++) {
			context.m_representation.addHyperText($"%1%2%3:\t\e[34m%4"(
				indent,
				getOffsetHyperlink(context.m_rawBinPartCode, offset, elementSize),
				getIndexStringFunc(i),
				getValueStringFunc(p)
			));

			offset += elementSize;
			p += elementSize;
		}
	}
}

void representFields(
	RepresentDynamicLayoutContext safe* context,
	jnc.DerivableType safe* type,
	size_t offset0,
	void const* p0,
	string_t indent
) {
	size_t count = type.m_baseTypeCount;
	for (size_t i = 0; i < count; i++) { // ungroup base types unconditionally
		jnc.BaseTypeSlot* baseTypeSlot = type.m_baseTypeArray[i];
		representFields(
			context,
			baseTypeSlot.m_type,
			offset0 + baseTypeSlot.m_offset,
			p0 + baseTypeSlot.m_offset,
			indent
		);
	}

	uint_t baseBackColor = context.m_representation.m_lineAttr.m_backColor;
	count = type.m_fieldCount;
	for (size_t i = 0; i < count; i++) {
		jnc.Field* field = type.m_fieldArray[i];
		jnc.Type* fieldType = field.m_type;
		size_t offset = offset0 + field.m_offset;
		void const* p = p0 + field.m_offset;

		setItemBackColor(context, field, offset, fieldType.m_size);

		if (!representBinary(context, field, offset, p, fieldType.m_size, indent))
			representField(
				context,
				field,
				fieldType,
				field.m_ptrTypeFlags,
				field.m_bitOffset,
				field.m_bitCount,
				offset,
				p,
				indent
			);

		context.m_representation.m_lineAttr.m_backColor = baseBackColor; // restore
	}
}

void representField(
	RepresentDynamicLayoutContext safe* context,
	jnc.ModuleItemDecl safe* decl,
	jnc.Type safe* type,
	jnc.PtrTypeFlags ptrTypeFlags,
	uint_t bitOffset = 0,
	uint_t bitCount = 0,
	size_t offset,
	void const* p,
	string_t indent
) {
	if ((type.m_typeKindFlags & jnc.TypeKindFlags.Derivable) &&
		!decl.findAttribute("stringify")
	) {
		jnc.DerivableType* derivableType = dynamic (jnc.DerivableType*)type;
		if (decl.findAttribute("ungroup"))
			representFields(context, derivableType, offset, p, indent);
		else if (representGroupHeader(context, decl, offset, p, type.m_size, indent))
			representFields(context, derivableType, offset, p, indent + "    ");
	} else {
		string_t value = getItemValueString(
			decl,
			type,
			ptrTypeFlags,
			bitOffset,
			bitCount,
			p
		);

		context.m_representation.addHyperText($"%1%2:\t\e[34m%3"(
			indent,
			getItemName(decl, context.m_rawBinPartCode, offset, type.m_size),
			value
		));
	}
}

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

} // namespace log
