// #target: macos-x64

module abc_faults;

faultdef UNTERMINATED_TAG, EMPTY_TAG, MISSING_TAG, UNSUPPORTED_TAG;

<* @require Type.kindof == STRUCT *>
module abc{Type};
import std::io, abc_faults, std::collections::list;

alias TextTagList = List{TextTag};


enum TextTagKind: char
{
	STRING,
	TEMPLATE,
}

struct TextTemplate
{
	Allocator allocator;
	String template;
	TextTag[] tags;
	Type data;
}

struct TextTag
{
	usz start;
	usz end;
	TextTagKind kind;
	union {
		String* data;
		TextTemplate* template;
	}
}

<*
 @require self.tags.len == 0 : "template already initialized"
 @require tag_start != tag_end
 *>
fn void? TextTemplate.init(&self, String template, String tag_start = "{{", String tag_end = "}}", Allocator using = allocator::heap())
{
	TextTagList tags;
	String tmpl = template;
	uptr data = (uptr)&self.data;
	while (true)
	{
		usz? start = tmpl.index_of(tag_start);
		if (catch start) break;
		tmpl = tmpl[start + tag_start.len..];

		usz? end = tmpl.index_of(tag_end);
		if (catch end) return abc_faults::UNTERMINATED_TAG?;
		String name = tmpl[:end].trim();
		if (name == "") return abc_faults::EMPTY_TAG?;
		// Check that the tag exists in the data struct.

		TextTag tag @noinit;
		do OUTER:
		{
			$foreach $m : Type.membersof:
				if (name == $m.nameof)
				{
					$switch $m.typeid:
						$case String.typeid:
							tag = {
								.kind = STRING,
								.data = (String*)(data + $m.offsetof),
							};
							break OUTER;
						$default:
							$if $defined($m.get(self.data).as_stream):
								tag = {
									.kind = TEMPLATE,
									.template = self.data.$eval($m.nameof).as_stream(),
								};
								break;
							$endif
					$endswitch
					//return UNSUPPORTED_TAG?;
				}
			$endforeach
			return abc_faults::MISSING_TAG?;
		};
		tmpl = tmpl[end + tag_end.len..];

		tag.start = start;
		tag.end = start + tag_start.len + end + tag_end.len;
		tags.push(tag);
	}

	*self = { .allocator = using, .template = template, .tags = tags.array_view() };
}

fn void? TextTemplate.free(&self)
{
	allocator::free(self.allocator, self.tags);
	*self = {};
}

fn usz? TextTemplate.write_to(&self, OutStream writer)
{
	usz n;
	usz pos;
	foreach (tag : self.tags)
	{
			n += writer.write(self.template[pos:tag.start])!;
			pos += tag.end;
			n += tag.write(writer)!;
	}
	n += writer.write(self.template[pos..])!;
	return n;
}

fn usz? TextTag.write(&self, OutStream writer)
{
	switch (self.kind)
	{
		case STRING:
			return writer.write(*self.data);
		case TEMPLATE:
			return self.template.write_to(writer);
	}
}

module text_test;
import abc;
import std::io;

alias Tmpl = TextTemplate{Data};

struct Data
{
	String user;
	String world;
	bool ok;
}



alias FooTmpl = TextTemplate{Foo};
alias BarTmpl = TextTemplate{Bar};

struct Foo
{
	String foo;
	BarTmpl* bar;
}

struct Bar
{
	String bar;
}

fn void main()
{

	String foo_tmpl = "<<{{foo}} | {{bar}}>>";
	FooTmpl ft;
	ft.init(foo_tmpl, using: tmem)!!;
	defer ft.free()!!;


}

/* #expect: text_test.ll

define void @text_test.main() #0 {
entry:
  %foo_tmpl = alloca %"char[]", align 8
  %ft = alloca %TextTemplate, align 8
  %error_var = alloca i64, align 8
  %indirectarg = alloca %"char[]", align 8
  %varargslots = alloca [1 x %any], align 16
  %indirectarg1 = alloca %"any[]", align 8
  %error_var2 = alloca i64, align 8
  %varargslots7 = alloca [1 x %any], align 16
  %indirectarg9 = alloca %"any[]", align 8
  store %"char[]" { ptr @.str, i64 21 }, ptr %foo_tmpl, align 8
  call void @llvm.memset.p0.i64(ptr align 8 %ft, i8 0, i64 72, i1 false)
  %0 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.mem.allocator.current_temp)
  %lo = load ptr, ptr %foo_tmpl, align 8
  %ptradd = getelementptr inbounds i8, ptr %foo_tmpl, i64 8
  %hi = load i64, ptr %ptradd, align 8
  store %"char[]" { ptr @.str.2, i64 2 }, ptr %indirectarg, align 8
  %1 = call i64 @"abc$text_test.Foo$.TextTemplate.init"(ptr %ft, ptr %lo, i64 %hi, ptr @.str.1, i64 2, ptr byval(%"char[]") align 8 %indirectarg, ptr byval(%any) align 8 %0)
  %not_err = icmp eq i64 %1, 0
  %2 = call i1 @llvm.expect.i1(i1 %not_err, i1 true)
  br i1 %2, label %after_check, label %assign_optional

assign_optional:                                  ; preds = %entry
  store i64 %1, ptr %error_var, align 8
  br label %panic_block

after_check:                                      ; preds = %entry
  br label %noerr_block

panic_block:                                      ; preds = %assign_optional
  %3 = insertvalue %any undef, ptr %error_var, 0
  %4 = insertvalue %any %3, i64 ptrtoint (ptr @"$ct.fault" to i64), 1
  store %any %4, ptr %varargslots, align 16
  %5 = insertvalue %"any[]" undef, ptr %varargslots, 0
  %"$$temp" = insertvalue %"any[]" %5, i64 1, 1
  store %"any[]" %"$$temp", ptr %indirectarg1, align 8
  call void @std.core.builtin.panicf(ptr @.panic_msg,
  unreachable

noerr_block:                                      ; preds = %after_check
  %6 = call i64 @"abc$text_test.Foo$.TextTemplate.free"(ptr %ft)
  %not_err3 = icmp eq i64 %6, 0
  %7 = call i1 @llvm.expect.i1(i1 %not_err3, i1 true)
  br i1 %7, label %after_check5, label %assign_optional4

assign_optional4:                                 ; preds = %noerr_block
  store i64 %6, ptr %error_var2, align 8
  br label %panic_block6

after_check5:                                     ; preds = %noerr_block
  br label %noerr_block10

panic_block6:                                     ; preds = %assign_optional4
  %8 = insertvalue %any undef, ptr %error_var2, 0
  %9 = insertvalue %any %8, i64 ptrtoint (ptr @"$ct.fault" to i64), 1
  store %any %9, ptr %varargslots7, align 16
  %10 = insertvalue %"any[]" undef, ptr %varargslots7, 0
  %"$$temp8" = insertvalue %"any[]" %10, i64 1, 1
  store %"any[]" %"$$temp8", ptr %indirectarg9, align 8
  call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36,
  unreachable

noerr_block10:                                    ; preds = %after_check5
  ret void
}
