package com.moparisthebest.jdbc.dto;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * This class is meant to test the java compiler's in-lining behavior for final variables
 */
public class FinalDTO {

	private static final boolean useReflectionNotUnsafe = true;

	private final long finalPrimitiveLongDirect = 2L;
	private long effectiveFinalPrimitiveLongDirect = 2L;
	private final long finalPrimitiveLongConstructor;

	{
		this.finalPrimitiveLongConstructor = 2L;
	}

	private final String finalStringDirect = "2";
	private String effectiveFinalStringDirect = "2";
	private final String finalStringConstructor;

	{
		this.finalStringConstructor = "2";
	}

	private final Long finalLongDirect = 2L;
	private final Long finalLongConstructor;

	{
		this.finalLongConstructor = 2L;
	}

	public long getFinalPrimitiveLongDirect() {
		return finalPrimitiveLongDirect;
	}

	public long getEffectiveFinalPrimitiveLongDirect() {
		return effectiveFinalPrimitiveLongDirect;
	}

	public long getFinalPrimitiveLongConstructor() {
		return finalPrimitiveLongConstructor;
	}

	public String getFinalStringDirect() {
		return finalStringDirect;
	}

	public String getEffectiveFinalStringDirect() {
		return effectiveFinalStringDirect;
	}

	public String getFinalStringConstructor() {
		return finalStringConstructor;
	}

	public Long getFinalLongDirect() {
		return finalLongDirect;
	}

	public Long getFinalLongConstructor() {
		return finalLongConstructor;
	}

	@Override
	public String toString() {
		return "FinalTest{" +
				"finalPrimitiveLongDirect=" + finalPrimitiveLongDirect +
				", reflection finalPrimitiveLongDirect=" + getField("finalPrimitiveLongDirect") +
				", effectiveFinalPrimitiveLongDirect=" + effectiveFinalPrimitiveLongDirect +
				", reflection effectiveFinalPrimitiveLongDirect=" + getField("effectiveFinalPrimitiveLongDirect") +
				", finalPrimitiveLongConstructor=" + finalPrimitiveLongConstructor +
				", reflection finalPrimitiveLongConstructor=" + getField("finalPrimitiveLongConstructor") +
				", finalStringDirect=" + finalStringDirect +
				", reflection finalStringDirect=" + getField("finalStringDirect") +
				", effectiveFinalStringDirect=" + effectiveFinalStringDirect +
				", reflection effectiveFinalStringDirect=" + getField("effectiveFinalStringDirect") +
				", finalStringConstructor=" + finalStringConstructor +
				", reflection finalStringConstructor=" + getField("finalStringConstructor") +
				", finalLongDirect=" + finalLongDirect +
				", reflection finalLongDirect=" + getField("finalLongDirect") +
				", finalLongConstructor=" + finalLongConstructor +
				", reflection finalLongConstructor=" + getField("finalLongConstructor") +
				'}';
	}

	public boolean directEqualsReflection() {
		return directEqualsReflection(finalPrimitiveLongDirect, "finalPrimitiveLongDirect")
				&& directEqualsReflection(effectiveFinalPrimitiveLongDirect, "effectiveFinalPrimitiveLongDirect")
				&& directEqualsReflection(finalPrimitiveLongConstructor, "finalPrimitiveLongConstructor")
				&& directEqualsReflection(finalStringDirect, "finalStringDirect")
				&& directEqualsReflection(effectiveFinalStringDirect, "effectiveFinalStringDirect")
				&& directEqualsReflection(finalStringConstructor, "finalStringConstructor")
				&& directEqualsReflection(finalLongDirect, "finalLongDirect")
				&& directEqualsReflection(finalLongConstructor, "finalLongConstructor")
				;
	}

	private boolean directEqualsReflection(final Object object, final String fieldName) {
		return equals(object, getField(fieldName));
	}

	public Object getField(final String fieldName) {
		return getField(this, fieldName);
	}

	public boolean setField(final String fieldName, final Object fieldValue) {
		setField(this, fieldName, fieldValue);
		return directEqualsReflection();
	}

	public static boolean equals(Object a, Object b) {
		return (a == b) || (a != null && a.equals(b));
	}

	private static Object getField(final Object object, final String fieldName) {
		try {
			final Field field = object.getClass().getDeclaredField(fieldName);
			if (useReflectionNotUnsafe) {
				field.setAccessible(true);
				return field.get(object);
			} else {
				final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
				unsafeField.setAccessible(true);
				final Unsafe unsafe = (Unsafe) unsafeField.get(null);

				final long offset = unsafe.objectFieldOffset(field);

				if (field.getType().isPrimitive()) {
					if (field.getType().equals(long.class))
						return unsafe.getLong(object, offset);
					else
						throw new RuntimeException("unsupported primitive type");
				} else
					return unsafe.getObject(object, offset);
			}
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	private static void setField(final Object object, final String fieldName, final Object fieldValue) {
		try {
			final Field field = object.getClass().getDeclaredField(fieldName);
			if (useReflectionNotUnsafe) {
				field.setAccessible(true);
				field.set(object, fieldValue);
			} else {
				final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
				unsafeField.setAccessible(true);
				final Unsafe unsafe = (Unsafe) unsafeField.get(null);

				final long offset = unsafe.objectFieldOffset(field);

				if (field.getType().isPrimitive()) {
					if (fieldValue instanceof Long)
						unsafe.putLong(object, offset, (Long) fieldValue);
					else
						throw new RuntimeException("unsupported primitive type");
				} else
					unsafe.putObject(object, offset, fieldValue);
			}
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	public static void main(final String[] args) {
		final FinalDTO object = new FinalDTO();
		System.out.println("1: " + object);
		setField(object, "finalPrimitiveLongDirect", 1L);
		System.out.println("2: " + object);
		setField(object, "finalPrimitiveLongConstructor", 1L);
		System.out.println("3: " + object);
		setField(object, "finalStringDirect", "1");
		System.out.println("4: " + object);
		setField(object, "finalStringConstructor", "1");
		System.out.println("5: " + object);
		setField(object, "finalLongDirect", 1L);
		System.out.println("6: " + object);
		setField(object, "finalLongConstructor", 1L);
		System.out.println("7: " + object);
		setField(object, "effectiveFinalPrimitiveLongDirect", 1L);
		System.out.println("8: " + object);
		setField(object, "effectiveFinalStringDirect", "1");
		System.out.println("9: " + object);
	}
}
