package dyvilx.tools.compiler.ast.classes.metadata;

import dyvil.lang.Name;
import dyvil.reflect.Modifiers;
import dyvilx.tools.compiler.ast.classes.ClassBody;
import dyvilx.tools.compiler.ast.classes.IClass;
import dyvilx.tools.compiler.ast.context.IContext;
import dyvilx.tools.compiler.ast.field.IField;
import dyvilx.tools.compiler.ast.field.IProperty;
import dyvilx.tools.compiler.ast.generic.ITypeParameter;
import dyvilx.tools.compiler.ast.generic.ITypeParametric;
import dyvilx.tools.compiler.ast.header.IClassCompilableList;
import dyvilx.tools.compiler.ast.header.ICompilableList;
import dyvilx.tools.compiler.ast.member.MemberKind;
import dyvilx.tools.compiler.ast.method.IMethod;
import dyvilx.tools.compiler.ast.type.IType;
import dyvilx.tools.compiler.ast.type.builtin.Types;
import dyvilx.tools.compiler.ast.type.typevar.TypeVarType;
import dyvilx.tools.compiler.backend.classes.ClassWriter;
import dyvilx.tools.compiler.backend.exception.BytecodeException;
import dyvilx.tools.compiler.util.Markers;
import dyvilx.tools.parsing.marker.MarkerList;

public class ExtensionMetadata implements IClassMetadata
{
	private final IClass theClass;

	public ExtensionMetadata(IClass theClass)
	{
		this.theClass = theClass;
	}

	@Override
	public MemberKind getKind()
	{
		return MemberKind.CLASS;
	}

	// Phases

	@Override
	public void resolveTypesBeforeBody(MarkerList markers, IContext context)
	{
		final IType superType = this.theClass.getSuperType();
		this.theClass.setPosition(superType.getPosition());
		this.theClass.setName(mangleName(superType));

		for (IMethod method : this.theClass.getBody().methods())
		{
			method.getAttributes().addFlag(Modifiers.EXTENSION);

			if (!method.isStatic())
			{
				ITypeParametric.prependTypeParameters(this.theClass, method);

				method.setThisType(replaceTypeVars(superType, this.theClass, method));
			}
		}
	}

	private static IType replaceTypeVars(IType type, ITypeParametric origin, ITypeParametric target)
	{
		return type.getConcreteType(typeParameter -> {
			assert typeParameter.getGeneric() == origin;
			final ITypeParameter methodTypeParameter = target.getTypeParameters().get(typeParameter.getIndex());
			return new TypeVarType(methodTypeParameter);
		});
	}

	private static Name mangleName(IType type)
	{
		final String signature = type.getSignature();
		final StringBuilder mangled = new StringBuilder("extension_");

		for (int i = 0; i < signature.length(); i++)
		{
			final char c = signature.charAt(i);
			switch (c)
			{
			case '<':
				mangled.append("_$__");
				break;
			case '>':
				mangled.append("__$_");
				break;
			case ',':
			case ';':
				mangled.append("__");
				break;
			case '/':
				mangled.append("_");
				break;
			default:
				mangled.append(c);
				break;
			}
		}

		return Name.fromQualified(mangled.toString());
	}

	@Override
	public void check(MarkerList markers, IContext context)
	{
		final ClassBody body = this.theClass.getBody();

		for (IField field : body.fields())
		{
			if (!field.hasModifier(Modifiers.PRIVATE | Modifiers.CONST))
			{
				markers.add(Markers.semanticError(field.getPosition(), "extension.field.invalid"));
			}
		}

		for (IProperty property : body.properties())
		{
			markers.add(Markers.semanticError(property.getPosition(), "extension.property.invalid"));
		}
	}

	@Override
	public void cleanup(ICompilableList compilableList, IClassCompilableList classCompilableList)
	{
		this.theClass.setSuperType(Types.OBJECT);
		this.theClass.setTypeParameters(null);
	}

	// Compilation

	@Override
	public void write(ClassWriter writer) throws BytecodeException
	{

	}
}

@@ -1856,24 +1856,103 @@
 hod));%0A%09%09%09%7D%0A
+%09%09%09else%0A%09%09%09%7B%0A%09%09%09%09// TODO use raw type?%0A%09%09%09%09method.setThisType(superType);%0A%09%09%09%7D%0A
 %09%09%7D%0A%09%7D%0A%0A%09pri

Fix static methods in extension classes