// 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 java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;

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

public class LabelVisitorTest {
	@Test
	void testPlain() {
		String src = """
			MOV R0, 42
			ADD R2, R0, R1
			""";
		String expected = """
			MOV R0, 42
			ADD R2, R0, R1
			""";
		LabelVisitor labelVisitor = new LabelVisitor();
		String noLabels = labelVisitor.gatherLabels(src);
		assertEquals(expected, noLabels);
		var labelMap = labelVisitor.getLabelMap();
		assertEquals(6, labelMap.size());
		assertEquals(2, labelMap.get("__CODE_END__"));
		assertEquals(2, labelMap.get("__DATA__"));
		assertEquals(2, labelMap.get("__HEAP_START__"));
		assertFalse(labelVisitor.hasErrors());
	}

	@Test
	void testSimple() {
		String src = """
			MOV R0, 42
			LABEL1: ADD R2, R0, R1
			SUB R1, R1, 1
			LABEL2:
				NOP
			STOP
			STOP
			""";
		String expected = """
			MOV R0, 42
			ADD R2, R0, R1
			SUB R1, R1, 1
			NOP
			STOP
			STOP
			""";
		LabelVisitor labelVisitor = new LabelVisitor();
		String noLabels = labelVisitor.gatherLabels(src);
		assertEquals(expected, noLabels);
		var labelMap = labelVisitor.getLabelMap();
		assertEquals(8, labelMap.size());
		assertEquals(0, labelMap.get("__START__"));
		assertEquals(0, labelMap.get("__CODE__"));
		assertEquals(1, labelMap.get("LABEL1"));
		assertEquals(1, labelMap.get("LABEL1"));
		assertEquals(3, labelMap.get("LABEL2"));
		assertEquals(6, labelMap.get("__CODE_END__"));
		assertEquals(6, labelMap.get("__DATA__"));
		assertEquals(6, labelMap.get("__DATA_END__"));
		assertEquals(6, labelMap.get("__HEAP_START__"));
		assertFalse(labelVisitor.hasErrors());
	}

	@Test
	void testData() {
		String src = """
			MOV R0, 42
			LABEL1: ADD R2, R0, R1
			
			SUB R1, R1, 1
			
			LABEL2:
				NOP
			DATA1: .DCI 123
			DATA2: .DCF 3.14
			DATA3: .DCS "Hello, World!"
			DATA4: .DCB 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'
			DATA5: .DCW 1, 2, 3, 4, 5
			DATA6: .DCW 1.2, 3.4, 5.6
			DATA7: .DCW 'x', 'y', 'z', '\\0'
			END:
			STOP
			STOP
			""";
		String expected = """
			MOV R0, 42
			ADD R2, R0, R1
			SUB R1, R1, 1
			NOP
			.DCI 123
			.DCF 3.14
			.DCS "Hello, World!"
			.DCB 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'
			.DCW 1, 2, 3, 4, 5
			.DCW 1.2, 3.4, 5.6
			.DCW 'x', 'y', 'z', '\\0'
			STOP
			STOP
			""";
		LabelVisitor labelVisitor = new LabelVisitor();
		String noLabels = labelVisitor.gatherLabels(src);
		assertEquals(expected, noLabels);
		var labelMap = labelVisitor.getLabelMap();
		assertEquals(16, labelMap.size());
		assertEquals(1, labelMap.get("LABEL1"));
		assertEquals(3, labelMap.get("LABEL2"));
		assertEquals(4, labelMap.get("DATA1"));
		assertEquals(5, labelMap.get("DATA2") );
		assertEquals(6, labelMap.get("DATA3"));
		assertEquals(9, labelMap.get("DATA4"));
		assertEquals(12, labelMap.get("DATA5"));
		assertEquals(18, labelMap.get("DATA6"));
		assertEquals(22, labelMap.get("DATA7"));
		assertEquals(27, labelMap.get("END"));
		assertEquals(29, labelMap.get("__CODE_END__"));
		assertEquals(29, labelMap.get("__DATA__"));
		assertEquals(29, labelMap.get("__HEAP_START__"));
		assertFalse(labelVisitor.hasErrors());
	}

	@Test
	void testOrg() {
		String src = """
			MOV R0, 42
			LABEL1: ADD R2, R0, R1
			SUB R1, R1, 1
			LABEL2: .ORG 100
				NOP
			LABEL3: SUB R3, R3, R4
			.ORG 200
			END:
			STOP
			STOP
			""";
		String expected = """
			MOV R0, 42
			ADD R2, R0, R1
			SUB R1, R1, 1
			.ORG 100
			NOP
			SUB R3, R3, R4
			.ORG 200
			STOP
			STOP
			""";
		LabelVisitor labelVisitor = new LabelVisitor();
		String noLabels = labelVisitor.gatherLabels(src);
		assertEquals(expected, noLabels);
		var labelMap = labelVisitor.getLabelMap();
		assertEquals(10, labelMap.size());
		assertEquals(1, labelMap.get("LABEL1"));
		assertEquals(3, labelMap.get("LABEL2"));
		assertEquals(101, labelMap.get("LABEL3"));
		assertEquals(200, labelMap.get("END"));
		assertEquals(202, labelMap.get("__CODE_END__"));
		assertEquals(202, labelMap.get("__DATA__"));
		assertEquals(202, labelMap.get("__HEAP_START__"));
		assertFalse(labelVisitor.hasErrors());
	}

	@Test
	void testBlock() {
		String src = """
			MOV R0, 42
			$LABEL1: ADD R2, R0, R1
			SUB R1, R1, 1
			.BLOCK BLOCK1
			$LABEL2: NOP
			.BLOCK BLOCK2
			$LABEL3: SUB R3, R3, R4
			.BLOCK_END
			$LABEL4: NOP
			.BLOCK_END
			$LABEL5: NOP
			END:
			STOP
			STOP
			""";
		String expected = """
			MOV R0, 42
			ADD R2, R0, R1
			SUB R1, R1, 1
			.BLOCK BLOCK1
			NOP
			.BLOCK BLOCK2
			SUB R3, R3, R4
			.BLOCK_END
			NOP
			.BLOCK_END
			NOP
			STOP
			STOP
			""";
		LabelVisitor labelVisitor = new LabelVisitor();
		String noLabels = labelVisitor.gatherLabels(src);
		assertEquals(expected, noLabels);
		var labelMap = labelVisitor.getLabelMap();
		assertEquals(12, labelMap.size());
		assertEquals(1, labelMap.get("$LABEL1"));
		assertEquals(3, labelMap.get("BLOCK1$LABEL2"));
		assertEquals(4, labelMap.get("BLOCK1$BLOCK2$LABEL3"));
		assertEquals(5, labelMap.get("BLOCK1$LABEL4"));
		assertEquals(6, labelMap.get("$LABEL5"));
		assertEquals(7, labelMap.get("END"));
		assertEquals(9, labelMap.get("__CODE_END__"));
		assertEquals(9, labelMap.get("__DATA__"));
		assertEquals(9, labelMap.get("__HEAP_START__"));
		assertFalse(labelVisitor.hasErrors());
	}

	@Test
	void testLexerErrors() {
		String src = """
			// Comment			
			/* Comment
			Comment
			*/
			#define PI 3.1415926
				START:
				move	r2, 123456789012345
				buckwheat
				.DCW -326
				spanky
				.DCW 0x1234, 0x5678, 0x9ABC, 0xDEF0
				alfalfa
				.DCW 3.1415926, -2.7182818, 0.
				.DCB 'a', '\\n', 'é', '\\u{1234}'
			#CALL func(r1, r2, r3)
				MOVE	r2, 123
				darla
				FINIS:
				""";
		String errorsString = """
«Test.asm», 8:ASMERROR:missing ':' at '\\n'
«Test.asm», 10:ASMERROR:missing ':' at '\\n'
«Test.asm», 12:ASMERROR:missing ':' at '\\n'
«Test.asm», 17:ASMERROR:missing ':' at '\\n'
				""";
		var loader = new IncludeLoader(Path.of("."));
		PreprocessorVisitor pp = new PreprocessorVisitor("Test.asm", loader);
		String preprocessed = pp.preprocessText(src);
		LiteralRewriter rw = new LiteralRewriter();
		preprocessed = rw.rewrite(preprocessed, pp.getSourceLocations());
		LabelVisitor labelVisitor = new LabelVisitor();
		ConsoleOutputCapturer capturer = new ConsoleOutputCapturer();
		capturer.start(ConsoleOutputCapturer.StdStream.STDERR);
		String noLabels = labelVisitor.gatherLabels(preprocessed);
		String output = capturer.stop();
		assertEquals(errorsString, output);
		assertTrue(labelVisitor.hasErrors());
	}
}
