package puzzle;

import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import puzzle.Export.ClueAt;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Export.LetterVisit.LetterAt;
import puzzle.Export.PuzzleResult;
import puzzle.Export.Rewards;
import puzzle.Main.Opts;
import puzzle.Masker.Clues;
import puzzle.SwedishGenerator.Rng;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
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.ExportFormatTest.Clue.DOWN;
import static puzzle.ExportFormatTest.Clue.LEFT;
import static puzzle.ExportFormatTest.Clue.RIGHT;
import static puzzle.ExportFormatTest.Clue.UP;
import static puzzle.SwedishGenerator.Dict;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.STACK_SIZE;
import static puzzle.SwedishGenerator.Slotinfo;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.SwedishGeneratorTest.AB;
import static puzzle.SwedishGeneratorTest.AZ;
import static puzzle.SwedishGeneratorTest.CLUE_LEFT;
import static puzzle.SwedishGeneratorTest.CLUE_RIGHT;
import static puzzle.SwedishGeneratorTest.CLUE_UP;
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0;
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_1;
import static puzzle.SwedishGeneratorTest.Idx.IDX_1_0;
import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1;
import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1;
import static puzzle.SwedishGeneratorTest.LETTER_A;
import static puzzle.SwedishGeneratorTest.LETTER_Z;
import static puzzle.SwedishGeneratorTest.OFF_0_0;
import static puzzle.SwedishGeneratorTest.OFF_0_1;
import static puzzle.SwedishGeneratorTest.OFF_0_2;
import static puzzle.SwedishGeneratorTest.OFF_1_1;
import static puzzle.SwedishGeneratorTest.OFF_1_2;
import static puzzle.SwedishGeneratorTest.OFF_2_1;
import static puzzle.SwedishGeneratorTest.OFF_2_3;

public class MainTest {
   
   static final Opts opts = new Main.Opts() {{
      this.seed          = 12348;
      this.clueSize      = 4;
      this.pop           = 4;    // Tiny population
      this.offspring     = 18;
      this.gens          = 20;   // Very few generations
      this.minSimplicity = 0;
      this.threads       = 1;
      this.tries         = 1;
      this.verbose       = false;
   }};
   final        Dict dict = DictData.DICT;//loadDict(opts.wordsPath);
   @Test
   void testExtractSlots() {
      
      var clues = Masker.Clues.createEmpty();
      val key   = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
      clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
      var grid = new Gridded(clues.toGrid());
      val g    = grid.grid().g;
      GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
      
      var slots = Masker.extractSlots(clues, dict.index());
      assertEquals(1, slots.length);
      var s = slots[0];
      assertEquals(8, Masker.Slot.length(s.lo(), s.hi()));
      var cells = s.walk().toArray();
      assertEquals(0, SwedishGenerator.IT[cells[0]].r());
      assertEquals(1, SwedishGenerator.IT[cells[0]].c());
      assertEquals(0, SwedishGenerator.IT[cells[1]].r());
      assertEquals(2, SwedishGenerator.IT[cells[1]].c());
   }
   
   @Test
   void testStaticSlotMethods() {
      // Test static horiz
      // dir 1 (right) is horizontal
      assertTrue(Masker.Slot.horiz(1));
      // dir 0 (down) is vertical
      assertFalse(Masker.Slot.horiz(0));
   }
   
   @Test
   void testForEachSlot() {
      var clues = Masker.Clues.createEmpty();
      clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
      var count = new AtomicInteger(0);
      clues.forEachSlot((key, lo, hi) -> {
         count.incrementAndGet();
         assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi));
         assertEquals(0, SwedishGenerator.IT[Long.numberOfTrailingZeros(lo)].r());
         assertEquals(1, SwedishGenerator.IT[Long.numberOfTrailingZeros(lo)].c());
      });
      assertEquals(1, count.get());
   }
   @Test
   public void testHoriz() {
      assertTrue(Masker.Slot.horiz(1)); // Right
      assertTrue(Masker.Slot.horiz(3)); // Left
      assertFalse(Masker.Slot.horiz(0)); // Down
      assertFalse(Masker.Slot.horiz(2)); // Up
   }
   @Test
   public void testGridBasics() {
      var clues = new Clued(Masker.Clues.createEmpty());
      val key   = Masker.Slot.packSlotKey(OFF_2_1, CLUE_UP);
      clues.setClueLo(IDX_2_1.lo, CLUE_UP);
      var grid = new Gridded(clues.toGrid());
      
      // Test set/get
      GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ);
      val map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
      Assertions.assertEquals(LETTER_A, map.get(OFF_1_1));
      Assertions.assertEquals(LETTER_Z, map.get(OFF_0_1));
      var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(1, clueMap.size());
      Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1));
      
      // Test isLetterAt
      Assertions.assertTrue(clues.notClue(OFF_0_0));
      Assertions.assertTrue(clues.notClue(OFF_1_2));
      Assertions.assertTrue(clues.notClue(OFF_2_3));
      Assertions.assertFalse(clues.isClueLo(OFF_1_1));
      
      // Test isDigitAt
      Assertions.assertFalse(clues.isClueLo(OFF_0_0));
      Assertions.assertTrue(clues.isClueLo(OFF_2_1));
      clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1));
      Assertions.assertFalse(clues.isClueLo(OFF_2_3));
      Assertions.assertFalse(clues.isClueLo(OFF_1_1));
      
      // Test isLettercell
      Assertions.assertTrue(clues.notClue(OFF_0_0)); // 'A' is letter
      Assertions.assertTrue(clues.isClueLo(OFF_2_1)); // digit
      Assertions.assertTrue(clues.notClue(OFF_1_1)); // '#' is lettercell
   }
   @Test
   public void testCluesDeepCopy() {
      var clues = new Clued(Masker.Clues.createEmpty());
      clues.setClueLo(IDX_0_0.lo, RIGHT.dir);
      clues.setClueLo(IDX_0_1.lo, UP.dir);
      clues.setClueLo(IDX_1_0.lo, LEFT.dir);
      clues.setClueLo(IDX_1_1.lo, DOWN.dir);
      
      var copy    = clues.deepCopyGrid();
      var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(CLUE_RIGHT, clueMap.get(OFF_0_0));
      
      copy.setClueLo(IDX_0_0.lo, DOWN.dir);
      var copied = copy.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(DOWN.dir, copied.get(OFF_0_0));
      Assertions.assertEquals(RIGHT.dir, clueMap.get(OFF_0_0));
   }
   @Test
   public void testMini() {
      val idx   = IDX_1_1;
      var clues = Masker.Clues.createEmpty();
      clues.setClueLo(idx.lo, CLUE_LEFT);
      Assertions.assertTrue(clues.isClueLo(idx.index));
   }
   /*@Test
   void testMaskerCreation() {
      var masker = new Masker(new Rng(12348), new int[STACK_SIZE], Masker.Clues.createEmpty());
      var mask   = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
      val clued  = new Clued(mask);
      val map    = clued.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(4, map.size());
      Assertions.assertEquals(RIGHT.dir, map.get(0));
      Assertions.assertEquals(RIGHT.dir, map.get(2));
      Assertions.assertEquals(RIGHT.dir, map.get(5));
      Assertions.assertEquals(LEFT.dir, map.get(71));
   }*/
   @Test
   void testFiller2() {
      val rng = new Rng(-343913721);
      val mask = Clues.parse(
              "1  000000\n" +
              "1        \n" +
              "1        \n" +
              "3  3     \n" +
              "3  0  3   \n" +
              "3        \n" +
              "3        \n" +
              "222     3");
      Assertions.assertEquals(20, mask.clueCount());
      val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
      Assertions.assertEquals(20, map.size());
      var slots = Masker.slots(mask.c(), dict.index());
      //  var filled   = fillMask(rng, slotInfo, grid, false);
      //   val res      = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0));
   }
   @Test
   void testFiller() {
      val rng = new Rng(-343913721);
      val mask = Clues.parse(
              "   3  300\n" +
              " 1       \n" +
              " 1       \n" +
              "   3  0  \n" +
              "  31     \n" +
              " 1       \n" +
              " 1      2\n" +
              "21  22  3");
      var slotInfo = Masker.slots(mask.c(), dict.index());
      var grid     = Slotinfo.grid(slotInfo);
      var filled   = fillMask(rng, slotInfo, grid, false);
      Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");
      Assertions.assertEquals(13, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed");
      Assertions.assertEquals("WAANZIN", Lemma.asWord(slotInfo[0].assign().w));
      Assertions.assertEquals(-1L, grid.lo);
      Assertions.assertEquals(-1L, grid.hi);
      var g = new Gridded(grid);
      g.gridToString(mask.c());
      var aa = new PuzzleResult(mask, g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
      System.out.println(String.join("\n", aa.grid()));
      
   }
   @Test
   public void testAttempt() {
      PuzzleResult res       = null;
      int          foundSeed = -1;
      for (int i = 0; i < 50; i++) {
         int seed = opts.seed + i;
         res = Main.attempt(new Rng(seed), DictData.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: " + Slotinfo.wordCount(0, res.slots()));
            System.out.println("[DEBUG_LOG] Grid:");
            System.out.println(res.grid().renderHuman(res.clues().c()));
            System.out.println(res.grid().gridToString(res.clues().c()));
            System.out.println(res.grid().renderHuman(res.clues().c()));
            break;
         }
      }
      
      Assertions.assertNotNull(res, "Puzzle generation failed (null result)");
      Assertions.assertTrue(res.filled().ok(), "Puzzle generation failed (not ok)");
      Assertions.assertEquals(12348, foundSeed, "Found seed changed");
   }
   boolean isLetter(byte b)          { return (b & 64) != 0; }
   @Test public void testIsLetterA() { assertTrue(isLetter((byte) 'A')); }
   @Test public void testIsLetterZ() { assertTrue(isLetter((byte) 'Z')); }
   
}
