package com.badlogic.gdx.json;

import com.badlogic.gdx.concurrent.ThreadLocalInstance;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.StringBuilder;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Utility functions to (optionally) store floats and doubles using a IEEE-754 bit masks.
 * <p>
 * Reference: http://the-witness.net/news/2011/12/engine-tech-concurrent-world-editing/
 */
public class JsonFloatSerializer {

	private static final String fpRegEx = "(-?[0-9.]+(E[+-][0-9]+)?)";
	private static final Pattern purePattern = Pattern.compile("^" + fpRegEx + "$");
	private static final Pattern ieeePattern = Pattern.compile("^0x([a-fA-F0-9]+)\\|" + fpRegEx + "$");

	private static final ThreadLocal<StringBuilder> stringBuilder =
			new ThreadLocalInstance<>(() -> new StringBuilder(32));

	public static String encodeFloatBits(float value) {

		StringBuilder builder = stringBuilder.get();

		builder.setLength(0);
		builder.append("0x");
		builder.append(String.format("%08X", Float.floatToRawIntBits(value)));
		builder.append("|");
		builder.append(value);

		return builder.toString();
	}

	public static float decodeFloatBits(String value, float defaultValue) {

		if (value == null) {
			return defaultValue;
		}

		Matcher matcher = ieeePattern.matcher(value);

		if (matcher.matches()) {
			String group = matcher.group(1);
			return Float.intBitsToFloat((int) Long.parseLong(group, 16));
		} else {
			matcher = purePattern.matcher(value);
			if (matcher.matches()) {
				String group = matcher.group(1);
				return Float.parseFloat(group);
			}
		}

		throw new GdxRuntimeException("Error parsing float from string: " + value);
	}

	public static String encodeDoubleBits(double value) {

		StringBuilder builder = stringBuilder.get();

		builder.setLength(0);
		builder.append("0x");
		builder.append(String.format("%016X", Double.doubleToRawLongBits(value)));
		builder.append("|");
		builder.append(value);

		return builder.toString();
	}

	public static double decodeDoubleBits(String value, double defaultValue) {

		if (value == null) {
			return defaultValue;
		}

		Matcher matcher = ieeePattern.matcher(value);

		if (matcher.matches()) {
			String group = matcher.group(1);
			return Double.longBitsToDouble(Long.parseLong(group, 16));
		} else {
			matcher = purePattern.matcher(value);
			if (matcher.matches()) {
				String group = matcher.group(1);
				return Double.parseDouble(group);
			}
		}

		throw new GdxRuntimeException("Error parsing double from string: " + value);
	}

}