// SPDX-License-Identifier: Apache-2.0
/*
 * Copyright 2001-2026 Richard Lesh
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cloud.lesh.CPUSim64;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MathInterruptTest extends BaseTest {
	@Test
	void testConstants() {
		String src = """
			#include <system/math.def>
			INT		iPI
			MOVE 	F1, F0
			INT		iE
			MOVE	F2, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(5, diff.size());
		diff.assertDiff(1, Math.PI);
		diff.assertDiff(2, Math.E);
	}

	@Test
	void testMisc() {
		String src = """
			#include <system/math.def>
			// ABS
			LOAD 	F1, -2.5
			INT		iABS_FP
			MOVE 	F31, F0
			LOAD 	F1, 2.5
			INT		iABS_FP
			MOVE 	F30, F0
			MOVE	R1, -2
			INT		iABS
			MOVE	R27, R0
			MOVE	R1, 2
			INT		iABS
			MOVE	R26, R0
			
			// CEIL
			LOAD 	F1, -2.5
			INT		iCEIL
			MOVE 	F29, F0
			LOAD 	F1, 2.5
			INT		iCEIL
			MOVE 	F28, F0
			
			// FLOOR
			LOAD 	F1, -2.5
			INT		iFLOOR
			MOVE 	F27, F0
			LOAD 	F1, 2.5
			INT		iFLOOR
			MOVE 	F26, F0
			
			// ROUND
			LOAD 	F1, -2.5
			INT		iROUND
			MOVE 	F25, F0
			LOAD 	F1, 2.5
			INT		iROUND
			MOVE 	F24, F0
			
			// MAX/MIN
			LOAD	F1, 2.5
			LOAD	F2, 2.0
			INT		iMAX_FP
			MOVE	F23, F0
			LOAD	F1, 2.0
			LOAD	F2, 2.5
			INT		iMIN_FP
			MOVE	F22, F0
			LOAD	F1, -2.5
			LOAD	F2, -2.0
			INT		iMAX_FP
			MOVE	F21, F0
			LOAD	F1, -2.0
			LOAD	F2, -2.5
			INT		iMIN_FP
			MOVE	F20, F0
			
			MOVE	R1, 4
			MOVE	R2, 2
			INT		iMAX
			MOVE	R25, R0
			MOVE	R1, 4
			MOVE	R2, 2
			INT		iMIN
			MOVE	R24, R0
			MOVE	R1, -4
			MOVE	R2, -2
			INT		iMAX
			MOVE	R23, R0
			MOVE	R1, -4
			MOVE	R2, -2
			INT		iMIN
			MOVE	R22, R0
			STOP
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(26, diff.size());
		diff.assertDiff(31, 2.5);
		diff.assertDiff(30, 2.5);
		diff.assertDiff(27, 2);
		diff.assertDiff(26, 2);
		diff.assertDiff(29, -2.);
		diff.assertDiff(28, 3.);
		diff.assertDiff(27, -3.);
		diff.assertDiff(26, 2.);
		diff.assertDiff(25, -3.);
		diff.assertDiff(24, 3.);
		diff.assertDiff(23, 2.5);
		diff.assertDiff(22, 2.);
		diff.assertDiff(21, -2.);
		diff.assertDiff(20, -2.5);
		diff.assertDiff(25, 4);
		diff.assertDiff(24, 2);
		diff.assertDiff(23, -2);
		diff.assertDiff(22, -4);
	}

	@Test
	void testLogExpPowerRoot() {
		String src = """
			#include <system/math.def>
			LOAD 	F1, 2.
			INT		iSQRT
			MOVE 	F31, F0
			LOAD 	F1, 2.
			INT		iEXP
			MOVE 	F30, F0
			LOAD 	F1, 2.
			INT		iLOG
			MOVE 	F29, F0
			LOAD 	F1, 2.
			LOAD	F2, 3.5
			INT		iPOW
			MOVE 	F28, F0
			LOAD 	F1, 3.5
			LOAD	F2, 2.
			INT		iREMAINDER
			MOVE 	F27, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(10, diff.size());
		diff.assertDiff(31, Math.sqrt(2.));
		diff.assertDiff(30, Math.exp(2.));
		diff.assertDiff(29, Math.log(2.));
		diff.assertDiff(28, Math.pow(2., 3.5));
		diff.assertDiff(27, Math.IEEEremainder(3.5, 2.));
	}

	@Test
	void testRandom() {
		String src = """
			#include <system/math.def>
			#include <system/system.def>
			#include <system/io.def>
			#var i
			#fvar r,zero,one
			load zero, 0.0
			load one, 1.0
			#for 0, i lt 100, 1
				INT		iRANDOM
				MOVE	r, F0
				MOVE	r0, STDOUT
				MOVE	r1, 16
				INT		iPUT_FP
				INT		iPUT_NL
				#macro COMPARE_RANGE(zero, le, r, lt, one)
				jump	z, FAILURE
			#end_for
			move r0, 0
			STOP
			FAILURE:
			move r0, -1
			STOP
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(8, diff.size());
		diff.assertDiff(0, 0);
	}

	@Test
	void testRand() {
		String src = """
			#include <system/math.def>
			#include <system/system.def>
			#include <system/io.def>
			#var i,r,low,high
			MOVE	low, 1
			MOVE	high, 10
			#for 0, i lt 100, 1
				MOVE	R0, low
				MOVE	R1, high
				INT		iRAND
				MOVE	r, r0
				MOVE	r0, STDOUT
				MOVE	r1, r
				INT		iPUT_DEC
				INT		iPUT_NL
				#macro COMPARE_RANGE(low, le, r, le, high)
				jump	z, FAILURE
			#end_for
			move r0, 0
			STOP
			FAILURE:
			move r0, -1
			STOP
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(8, diff.size());
		diff.assertDiff(0, 0);
	}

	@Test
	void testTrig() {
		String src = """
			#include <system/math.def>
			LOAD 	F1, 0.5
			INT		iTO_DEGREES
			MOVE 	F31, F0
			MOVE	F1, F0
			INT		iTO_RADIANS
			MOVE 	F30, F0
			
			LOAD 	F1, 0.5
			INT		iSIN
			MOVE 	F29, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iSIN
			MOVE 	F28, F0
			LOAD 	F1, 2.0
			INT		iSIN
			MOVE 	F27, F0
			
			LOAD 	F1, 0.5
			INT		iCOS
			MOVE 	F26, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iCOS
			MOVE 	F25, F0
			LOAD 	F1, 2.0
			INT		iCOS
			MOVE 	F24, F0
			
			LOAD 	F1, 0.5
			INT		iTAN
			MOVE 	F23, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iTAN
			MOVE 	F22, F0
			LOAD 	F1, 2.0
			INT		iTAN
			MOVE 	F21, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(15, diff.size());
		diff.assertDiff(31, Math.toDegrees(0.5));
		diff.assertDiff(30, 0.5);
		diff.assertDiff(29, Math.sin(0.5));
		diff.assertDiff(28, Math.sin(-0.5));
		diff.assertDiff(27, Math.sin(2.0));
		diff.assertDiff(26, Math.cos(0.5));
		diff.assertDiff(25, Math.cos(-0.5));
		diff.assertDiff(24, Math.cos(2.0));
		diff.assertDiff(23, Math.tan(0.5));
		diff.assertDiff(22, Math.tan(-0.5));
		diff.assertDiff(21, Math.tan(2.0));
	}

	@Test
	void testInvTrig() {
		String src = """
			#include <system/math.def>
			LOAD 	F1, 0.5
			INT		iTO_RADIANS
			MOVE 	F31, F0
			MOVE	F1, F0
			INT		iTO_DEGREES
			MOVE 	F30, F0
			
			LOAD 	F1, 0.5
			INT		iASIN
			MOVE 	F29, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iASIN
			MOVE 	F28, F0
			LOAD 	F1, 2.0
			INT		iASIN
			MOVE 	F27, F0
			
			LOAD 	F1, 0.5
			INT		iACOS
			MOVE 	F26, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iACOS
			MOVE 	F25, F0
			LOAD 	F1, 2.0
			INT		iACOS
			MOVE 	F24, F0
			
			LOAD 	F1, 0.5
			INT		iATAN
			MOVE 	F23, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iATAN
			MOVE 	F22, F0
			LOAD 	F1, 2.0
			INT		iATAN
			MOVE 	F21, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(15, diff.size());
		diff.assertDiff(31, Math.toRadians(0.5));
		diff.assertDiff(30, 0.5);
		diff.assertDiff(29, Math.asin(0.5));
		diff.assertDiff(28, Math.asin(-0.5));
		diff.assertDiff(27, Math.asin(2.0));
		diff.assertDiff(26, Math.acos(0.5));
		diff.assertDiff(25, Math.acos(-0.5));
		diff.assertDiff(24, Math.acos(2.0));
		diff.assertDiff(23, Math.atan(0.5));
		diff.assertDiff(22, Math.atan(-0.5));
		diff.assertDiff(21, Math.atan(2.0));
	}

	@Test
	void testHyperbolicTrig() {
		String src = """
			#include <system/math.def>
			LOAD 	F1, 0.5
			INT		iSINH
			MOVE 	F29, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iSINH
			MOVE 	F28, F0
			LOAD 	F1, 2.0
			INT		iSINH
			MOVE 	F27, F0
			
			LOAD 	F1, 0.5
			INT		iCOSH
			MOVE 	F26, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iCOSH
			MOVE 	F25, F0
			LOAD 	F1, 2.0
			INT		iCOSH
			MOVE 	F24, F0
			
			LOAD 	F1, 0.5
			INT		iTANH
			MOVE 	F23, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iTANH
			MOVE 	F22, F0
			LOAD 	F1, 2.0
			INT		iTANH
			MOVE 	F21, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(13, diff.size());
		diff.assertDiff(29, Math.sinh(0.5));
		diff.assertDiff(28, Math.sinh(-0.5));
		diff.assertDiff(27, Math.sinh(2.0));
		diff.assertDiff(26, Math.cosh(0.5));
		diff.assertDiff(25, Math.cosh(-0.5));
		diff.assertDiff(24, Math.cosh(2.0));
		diff.assertDiff(23, Math.tanh(0.5));
		diff.assertDiff(22, Math.tanh(-0.5));
		diff.assertDiff(21, Math.tanh(2.0));
	}

	@Test
	void testInvHyperbolicTrig() {
		String src = """
			#include <system/math.def>			
			LOAD 	F1, 0.5
			INT		iASINH
			MOVE 	F29, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iASINH
			MOVE 	F28, F0
			LOAD 	F1, 2.0
			INT		iASINH
			MOVE 	F27, F0
			
			LOAD 	F1, 0.5
			INT		iACOSH
			MOVE 	F26, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iACOSH
			MOVE 	F25, F0
			LOAD 	F1, 2.0
			INT		iACOSH
			MOVE 	F24, F0
			
			LOAD 	F1, 0.5
			INT		iATANH
			MOVE 	F23, F0
			LOAD 	F1, 0.5
			NEG		F1
			INT		iATANH
			MOVE 	F22, F0
			LOAD 	F1, 2.0
			INT		iATANH
			MOVE 	F21, F0
			STOP
			""";
		var tuple = runProgram(src);
		var result = tuple.getLeft();
		var sim = tuple.getMiddle();
		var diff = tuple.getRight();
		assertEquals(13, diff.size());
		diff.assertDiff(29, Utils.asinh(0.5));
		diff.assertDiff(28, Utils.asinh(-0.5));
		diff.assertDiff(27, Utils.asinh(2.0));
		diff.assertDiff(26, Utils.acosh(0.5));
		diff.assertDiff(25, Utils.acosh(-0.5));
		diff.assertDiff(24, Utils.acosh(2.0));
		diff.assertDiff(23, Utils.atanh(0.5));
		diff.assertDiff(22, Utils.atanh(-0.5));
		diff.assertDiff(21, Utils.atanh(2.0));
	}
}