package puzzle;

import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import puzzle.Export.PuzzleResult;
import puzzle.SwedishGenerator.Rng;
import puzzle.SwedishGenerator.Slot;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static puzzle.SwedishGenerator.*;
import static puzzle.SwedishGenerator.DASH;

public class MainTest {
   
   @Test
   void testExtractSlots() {
      var grid = Grid.createEmpty();
      
      // Set up digits on the grid to create slots.
      // '2' (right) at (0,0) -> slot at (0,1), (0,2)
      grid.setClue(0, (byte) '2');
      grid.setByteAt(Grid.offset(0, 1), (byte) 'A');
      grid.setByteAt(Grid.offset(0, 2), (byte) 'B');
      
      var slots = extractSlots(grid);
      assertEquals(1, slots.size());
      var s = slots.getFirst();
      assertEquals(8, s.len());
      assertEquals(0, Grid.r(s.pos(0)));
      assertEquals(1, Grid.c(s.pos(0)));
      assertEquals(0, Grid.r(s.pos(1)));
      assertEquals(2, Grid.c(s.pos(1)));
   }
   
   @Test
   void testStaticSlotMethods() {
      // Test static offset extraction
      // packedPos: offset(1, 1) at index 0, offset(2, 2) at index 1
      var packedPos = ((long) Grid.offset(1, 1)) | (((long) Grid.offset(2, 2)) << 7);
      assertEquals(Grid.offset(1, 1), Slot.offset(packedPos, 0));
      assertEquals(Grid.offset(2, 2), Slot.offset(packedPos, 1));
      
      // Test static horiz
      // dir 2 (right) is horizontal
      assertTrue(Slot.horiz(2));
      // dir 3 (down) is vertical
      assertFalse(Slot.horiz(3));
   }
   
   @Test
   void testForEachSlot() {
      var grid = Grid.createEmpty();
      grid.setClue(0, (byte) '2'); // right
      
      var count = new AtomicInteger(0);
      grid.forEachSlot((key, packedPos, len) -> {
         count.incrementAndGet();
         assertEquals(8, len);
         assertEquals(0, Grid.r(Slot.offset(packedPos, 0)));
         assertEquals(1, Grid.c(Slot.offset(packedPos, 0)));
      });
      assertEquals(1, count.get());
   }
   @Test
   public void testHoriz() {
      assertTrue(Slot.from(2, 0L, 1).horiz());
      assertTrue(Slot.from(4, 0L, 1).horiz());
      assertFalse(Slot.from(1, 0L, 1).horiz());
      assertFalse(Slot.from(3, 0L, 1).horiz());
      assertFalse(Slot.from(5, 0L, 1).horiz());
   }
   @Test
   public void testGridBasics() {
      var grid = Grid.createEmpty();
      
      // Test set/get
      grid.setByteAt(Grid.offset(0, 0), (byte) 'A');
      grid.setClue(Grid.offset(1, 2), (byte) '5');
      grid.setByteAt(Grid.offset(2, 3), (byte) 'Z');
      
      Assertions.assertEquals((byte) 'A', grid.byteAt(Grid.offset(0, 0)));
      Assertions.assertEquals((byte) '5', grid.byteAt(Grid.offset(1, 2)));
      Assertions.assertEquals((byte) 'Z', grid.byteAt(Grid.offset(2, 3)));
      Assertions.assertEquals(DASH, grid.byteAt(Grid.offset(1, 1)));
      
      // Test isLetterAt
      Assertions.assertTrue(grid.isLetterSet(0, 0));
      Assertions.assertFalse(grid.isLetterSet(1, 2));
      Assertions.assertTrue(grid.isLetterSet(2, 3));
      Assertions.assertFalse(grid.isLetterSet(1, 1));
      
      // Test isDigitAt
      Assertions.assertFalse(grid.isDigitAt(0));
      Assertions.assertTrue(grid.isDigitAt(Grid.offset(1, 2)));
      Assertions.assertEquals(5, grid.digitAt(Grid.offset(1, 2)));
      Assertions.assertFalse(grid.isDigitAt(Grid.offset(2, 3)));
      Assertions.assertFalse(grid.isDigitAt(Grid.offset(1, 1)));
      
      // Test isLettercell
      Assertions.assertTrue(grid.isLetterAt(0, 0)); // 'A' is letter
      Assertions.assertFalse(grid.isLetterAt(1, 2)); // '5' is digit
      Assertions.assertTrue(grid.isLetterAt(1, 1)); // '#' is lettercell
   }
   
   @Test
   public void testGridDeepCopy() {
      var grid = Grid.createEmpty();
      grid.setByteAt(Grid.offset(0, 0), (byte) 'A');
      grid.setByteAt(Grid.offset(0, 1), (byte) 'B');
      grid.setByteAt(Grid.offset(1, 0), (byte) 'C');
      grid.setByteAt(Grid.offset(1, 1), (byte) 'D');
      
      var copy = grid.deepCopyGrid();
      Assertions.assertEquals((byte) 'A', copy.byteAt(0));
      
      copy.setByteAt(0, (byte) 'X');
      Assertions.assertEquals((byte) 'X', copy.byteAt(0));
      Assertions.assertEquals((byte) 'A', grid.byteAt(0)); // Original should be unchanged
   }
   
   @Test
   public void testMini() {
      var grid = Grid.createEmpty();
      val idx  = Grid.offset(1, 1);
      grid.setClue(idx, (byte) '1');
      Assertions.assertTrue(grid.isDigitAt(idx));
   }
   @Test
   public void testAttempt() {
      // Arrange
      var opts = new Main.Opts();
      opts.seed          = 12347;
      opts.pop           = 4;    // Tiny population
      opts.gens          = 20;   // Very few generations
      opts.minSimplicity = 0;
      opts.fillTimeout   = 10_000;
      opts.threads       = 1;
      opts.tries         = 1;
      opts.verbose       = false;
      
      var dict = Dict.loadDict(opts.wordsPath);
      
      // Act
      PuzzleResult res       = null;
      int          foundSeed = -1;
      for (int i = 0; i < 50; i++) {
         int seed = opts.seed + i;
         res = Main.attempt(new Rng(seed), dict, opts);
         if (res != null && res.filled().ok()) {
            foundSeed = seed;
            System.out.println("[DEBUG_LOG] Seed found: " + seed);
            System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity);
            System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size());
            System.out.println("[DEBUG_LOG] Grid:");
            System.out.println(res.filled().grid().renderHuman());
            break;
         }
      }
      
      // Assert
      Assertions.assertNotNull(res, "Puzzle generation failed (null result)");
      Assertions.assertTrue(res.filled().ok(), "Puzzle generation failed (not ok)");
      
      // Regression baseline for seed search starting at 12347, pop 4, gens 20
      Assertions.assertEquals(12347, foundSeed, "Found seed changed");
      Assertions.assertEquals(24, res.filled().clueMap().size(), "Number of assigned words changed");
      Assertions.assertEquals(735.7083333333334, res.filled().stats().simplicity, 1e-9, "Simplicity value changed");
      Assertions.assertArrayEquals(new byte[]{ 'E', 'B', 'B', 'E', 'N' }, res.filled().clueMap().get(515).word());
   }
   @Test
   public void testIsLetterA() {
      assertTrue(isLetter((byte) 'A'));
   }
   
   @Test
   public void testIsLetterZ() {
      assertTrue(isLetter((byte) 'Z'));
   }
   
}
