package dyvil.tools.compiler.ast.reference;

import dyvil.lang.Map;
import dyvil.lang.Set;

import dyvil.collection.mutable.IdentityHashMap;
import dyvil.collection.mutable.MapBasedSet;
import dyvil.reflect.Modifiers;
import dyvil.reflect.Opcodes;
import dyvil.tools.compiler.ast.context.IContext;
import dyvil.tools.compiler.ast.field.IField;
import dyvil.tools.compiler.ast.member.IClassCompilable;
import dyvil.tools.compiler.ast.structure.IClassCompilableList;
import dyvil.tools.compiler.ast.type.Types;
import dyvil.tools.compiler.backend.ClassWriter;
import dyvil.tools.compiler.backend.MethodWriter;
import dyvil.tools.compiler.backend.exception.BytecodeException;
import dyvil.tools.compiler.lexer.marker.MarkerList;
import dyvil.tools.compiler.lexer.position.ICodePosition;

public class StaticFieldReference implements IReference, IClassCompilable
{
	private static Map<String, Set<IField>>	map	= new IdentityHashMap();
	
	protected IField						field;
	
	private boolean							isUnique;
	private String							className;
	private String							idFieldName;
	private String							refFieldType;
	
	public StaticFieldReference(IField field)
	{
		this.field = field;
	}
	
	private static boolean addToMap(String className, IField field)
	{
		Set<IField> set = map.get(className);
		if (set == null)
		{
			set = new MapBasedSet<>(new IdentityHashMap());
			map.put(className, set);
		}
		
		return set.add(field);
	}
	
	@Override
	public void check(ICodePosition position, MarkerList markers)
	{
		map.clear();
	}
	
	@Override
	public void cleanup(IContext context, IClassCompilableList compilableList)
	{
		compilableList.addCompilable(this);
		
		if (addToMap(this.className, this.field))
		{
			this.isUnique = true;
		}
	}
	
	private String getRefFieldName()
	{
		if (this.idFieldName != null)
		{
			return this.idFieldName;
		}
		
		return this.idFieldName = "$fieldRef$" + this.className.replace('/', '$') + "$" + this.field.getName().qualified;
	}
	
	private String getRefFieldType()
	{
		if (this.refFieldType != null)
		{
			return this.refFieldType;
		}
		
		return this.refFieldType = 'L' + Types.getInternalRef(this.field.getType(), "") + ';';
	}
	
	@Override
	public void setInnerIndex(String internalName, int index)
	{
		this.className = internalName;
	}
	
	@Override
	public void write(ClassWriter writer) throws BytecodeException
	{
		if (!this.isUnique)
		{
			return;
		}
		
		String refFieldName = this.getRefFieldName();
		String refFieldType = this.getRefFieldType();
		
		final int modifiers = Modifiers.PRIVATE | Modifiers.STATIC | Modifiers.SYNTHETIC;
		writer.visitField(modifiers, refFieldName, refFieldType, null, null);
	}
	
	@Override
	public void writeStaticInit(MethodWriter writer) throws BytecodeException
	{
		if (!this.isUnique)
		{
			return;
		}
		
		String fieldName = this.field.getName().qualified;
		String fieldClassType = 'L' + this.field.getTheClass().getInternalName() + ';';
		
		String refFieldName = this.getRefFieldName();
		String refFieldType = this.getRefFieldType();
		String wrapperClass = Types.getInternalRef(this.field.getType(), "unsafe/Unsafe");
		
		int var = writer.localCount();
		
		// Create a new wrapper object
		writer.writeTypeInsn(Opcodes.NEW, wrapperClass);
		writer.writeInsn(Opcodes.DUP);
		
		// Get the Unsafe and store it in a local variable
		writer.writeFieldInsn(Opcodes.GETSTATIC, "dyvil/reflect/ReflectUtils", "unsafe", "Lsun/misc/Unsafe;");
		writer.writeInsn(Opcodes.DUP);
		writer.writeVarInsn(Opcodes.ASTORE, var);
		
		// Get the Class of the field container type
		writer.writeLDC(org.objectweb.asm.Type.getType(fieldClassType));
		writer.writeLDC(fieldName);
		// Get the Field using reflection
		writer.writeInvokeInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
		// Store it in a second local variable
		writer.writeInsn(Opcodes.DUP);
		writer.writeVarInsn(Opcodes.ASTORE, var + 1);
		
		// Get the field base
		writer.writeInvokeInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "staticFieldBase", "(Ljava/lang/reflect/Field;)Ljava/lang/Object;", false);
		
		// Get the field offset
		writer.writeVarInsn(Opcodes.ALOAD, var);
		writer.writeVarInsn(Opcodes.ALOAD, var + 1);
		writer.writeInvokeInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "staticFieldOffset", "(Ljava/lang/reflect/Field;)J", false);
		
		// Init the wrapper object
		writer.writeInvokeInsn(Opcodes.INVOKESPECIAL, wrapperClass, "<init>", "(Ljava/lang/Object;J)V", false);
		
		// Assign the reference field
		writer.writeFieldInsn(Opcodes.PUTSTATIC, this.className, refFieldName, refFieldType);
		
		writer.resetLocals(var);
	}
	
	@Override
	public void writeReference(MethodWriter writer) throws BytecodeException
	{
		if (this.field.hasModifier(Modifiers.STATIC))
		{
			writer.writeFieldInsn(Opcodes.GETSTATIC, this.className, this.getRefFieldName(), this.getRefFieldType());
		}
	}
}

@@ -51,20 +51,26 @@
 t dyvil.
-lang
+collection
 .Map;%0Aim
@@ -84,19 +84,24 @@
 vil.
-lang
+collection
 .Set;%0A
-%0A
 impo
@@ -352,65 +352,8 @@
 ld;%0A
-import dyvil.tools.compiler.ast.member.IClassCompilable;%0A
 impo
@@ -497,32 +497,86 @@
 nd.ClassWriter;%0A
+import dyvil.tools.compiler.backend.IClassCompilable;%0A
 import dyvil.too
@@ -693,30 +693,23 @@
 l.tools.
-compiler.lexer
+parsing
 .marker.
@@ -743,22 +743,15 @@
 ols.
-compiler.lexer
+parsing
 .pos
@@ -892,13 +892,13 @@
 ld%3E%3E
-%09map%09
+ map 
 = ne
@@ -1320,16 +1320,22 @@
 asedSet%3C
+IField
 %3E(new Id
@@ -1347,16 +1347,18 @@
 yHashMap
+%3C%3E
 ());%0A%09%09%09
@@ -1610,49 +1610,8 @@
 %0A%09%7B%0A
-%09%09compilableList.addCompilable(this);%0A%09%09%0A
 %09%09if
@@ -1650,20 +1650,59 @@
 field))%0A
-
 %09%09%7B%0A
+%09%09%09compilableList.addCompilable(this);%0A
 %09%09%09this.
@@ -3571,21 +3571,19 @@
 LDC(
-org.objectweb
+dyvil.tools
 .asm

Update Static Field References