package org.luaj.vm2.libs;

import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;

/**
 * Optional helpers for approximate floating-point comparisons.
 * This deliberately does not change Lua's core equality semantics.
 */
public class ApproxLib extends TwoArgFunction {

	private static final double DEFAULT_EPSILON = 1e-9d;

	public LuaValue call(LuaValue modname, LuaValue env) {
		LuaTable lib = new LuaTable(0, 2);
		lib.set("eq", new approx_eq());
		env.set("approx", lib);
		env.set("approx_eq", lib.get("eq"));
		return lib;
	}

	static final class approx_eq extends VarArgFunction {
		public LuaValue invoke(org.luaj.vm2.Varargs args) {
			double a = args.checkdouble(1);
			double b = args.checkdouble(2);
			double epsilon = args.optdouble(3, DEFAULT_EPSILON);
			if (epsilon < 0d) {
				argerror(3, "epsilon must be >= 0");
			}
			return LuaValue.valueOf(approxEqual(a, b, epsilon));
		}
	}

	public static boolean approxEqual(double a, double b, double epsilon) {
		if (Double.doubleToLongBits(a) == Double.doubleToLongBits(b)) {
			return true;
		}
		if (Double.isNaN(a) || Double.isNaN(b)) {
			return false;
		}
		if (Double.isInfinite(a) || Double.isInfinite(b)) {
			return false;
		}
		double diff = Math.abs(a - b);
		if (diff <= epsilon) {
			return true;
		}
		double scale = Math.max(Math.abs(a), Math.abs(b));
		return diff <= scale * epsilon;
	}
}
