package puzzle;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.val;
import precomp.Neighbors9x8;
import static java.lang.Long.*;
import static java.lang.Long.numberOfTrailingZeros;
import static java.nio.charset.StandardCharsets.US_ASCII;

/**
 * NOTE:
 * A) generate randoms for and idx and direction (clue together)
 * B) convert IDX_PATH_0_BASE into bit-pattern (i think) Length should not be checked, just left empty if not good.
 * C) store more information in the byte[] even consider the long[] or try sqeeze the 288 options (clue) in a byte.
 * F) pre-determine random clue arrangements, so they do not involve impossible clue's such as bottom at last or the line before that and such to all sides
 * G) Check 3wall, the current implementation may be faster, but the accuracy is lower, degrade performance on the filler
 */

/**
 * SwedishGenerator.java
 *
 * Usage:
 *   javac SwedishGenerator.java
 *   java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt]
 */
@SuppressWarnings("ALL")
public record SwedishGenerator() {
   
   public static final long   X                        = 0L;
   public static final int    LOG_EVERY_MS             = 200;
   public static final int    BAR_LEN                  = 22;
   public static final int    C                        = Neighbors9x8.C;
   public static final int    R                        = Neighbors9x8.R;
   public static final int    SIZE                     = Neighbors9x8.SIZE;// ~18
   public static final int    SIZE_MIN_1               = Neighbors9x8.SIZE_MIN_1;// ~18
   public static final double SIZED                    = Neighbors9x8.SIZED;// ~18
   public static final long   MASK_LO                  = -1L;
   public static final long   MASK_HI                  = Neighbors9x8.MASK_HI;//(1L << (SIZE - 64)) - 1;
   public static final int    MAX_WORD_LENGTH          = Neighbors9x8.R;
   public static final int    MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
   public static final int    MIN_LEN                  = Neighbors9x8.MIN_LEN;//Config.MIN_LEN;
   public static final int    MAX_TRIES_PER_SLOT       = 500;//Config.MAX_TRIES_PER_SLOT;
   public static final int    STACK_SIZE               = 128;
   public static final long   RANGE_0_SIZE             = Neighbors9x8.RANGE_0_SIZE;// (long) SIZE_MIN_1 - 0L + 1L
   public static final long   RANGE_0_624              = Neighbors9x8.RANGE_0_624;//624L - 0L + 1L;
   static final        int    PICK_NOT_DONE            = -1;
   static final        int    PICK_DONE                = 0;
   public static boolean isLo(int n) { return (n & 64) == 0; }
   interface Bit1029 {
      
      static long[] bit1029()                       { return new long[2048]; }
      private static int wordIndex(int bitIndex)    { return bitIndex >> 6; }
      static boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != X; }
      static void set(long[] bits, int bitIndex)    { bits[wordIndex(bitIndex)] |= 1L << bitIndex; }
      static void clear(long[] bits, int bitIndex)  { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); }
   }
   
   //@formatter:off
                                       public static record      Dict(DictEntry[] index, int length) { }
                                       public static record      DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
   @AllArgsConstructor   @NoArgsConstructor   static final class Assign    {      long w;   }
                                       public static final class FillStats {      public double simplicity; }
   @AllArgsConstructor                 public static final class Grid      {      public final byte[] g; public       long   lo, hi;    }
                                       public static record      FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { }
   //@formatter:on
   
   public static final long[] PATH_LO = Neighbors9x8.PATH_LO;
   public static final long[] PATH_HI = Neighbors9x8.PATH_HI;
   
   public static final class Rng {
      
      private int x;
      public Rng(int seed) {
         var s = seed;
         if (s == 0) s = 1;
         this.x = s;
      }
      public int nextU32() {
         var y = x;
         y ^= (y << 13);
         y ^= (y >>> 17);
         y ^= (y << 5);
         x = y;
         return y;
      }
      static final byte[] BYTE = new byte[]{ 0, 1, 2, 3, 4, 5 };
      public byte randomClueDir()       { return rand(BYTE); }
      public <T> T rand(T[] p)          { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; }
      public byte rand(byte[] p)        { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; }
      public int randint0_SIZE()        { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); }
      public int randint0_624()         { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); }
      public double nextFloat()         { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
      public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); }
   }
   
   public static interface Lemma {
      
      static final long LETTER_MASK = (1L << 40) - 1;     // low 40 bits
      static final long INDEX_MASK  = (1L << 43) - 1;     // 24 bits
      
      static long from(byte[] word)            { return packShiftIn(word) | ((long) (word.length - 1) << 40); }
      static long pack(long w, int shardIndex) { return w | (((long) shardIndex) << 43) | ((long) length0(w)) << 40; }
      static long packShiftIn(byte[] b) {
         long w = 0;
         for (int i = b.length - 1; i >= 0; i--) w = (w << 5) | ((long) b[i] & 31);
         return w;
      }
      static public long from(String word)   { return packShiftIn(word.getBytes(US_ASCII)) | ((long) (word.length() - 1) << 40); }
      static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111L); }
      static int length0(long word)          { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5); }
      public static String asWord(long word, byte[] bytes) {
         int bi = 0;
         for (long w = word & LETTER_MASK; w != 0; w >>>= 5) bytes[bi++] = (byte) ((w & 31) | 64);
         return new String(bytes, 0, bi, US_ASCII);
      }
      static int unpackIndex(long w)      { return (int) (w >>> 40); }
      static int unpackShardIndex(long w) { return (int) (w >>> 43); }
      static int unpackSize(long w)       { return (int) (w >>> 40) & 7; }
      static int unpackLetters(long w)    { return (int) (w & LETTER_MASK); }
      static long pack43(long w) {
         return w & INDEX_MASK;
      }
   }
   
   public static record Slotinfo(int key, long lo, long hi, int score, Assign assign, DictEntry entry, int minL) {
      
      public static int wordCount(int k, Slotinfo[] arr) {
         for (var n = 1; n < arr.length; n++) if (arr[n].assign.w != X) k++;
         return k;
      }
      public static boolean increasing(int dir) { return (dir & 2) == 0; }
      public static Grid grid(Slotinfo[] slots) {
         long lo = X, hi = X;
         for (var slot : slots) {
            lo |= slot.lo;
            hi |= slot.hi;
         }
         return new Grid(new byte[SIZE], ~lo, ~hi);
      }
   }
   
   public static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) {
      if (((lo & glo) | (hi & ghi)) == X) return X;
      long p = 0;
      int  n = 0, offset, idx;
      if (Slotinfo.increasing(key)) {
         for (long b = lo & glo; b != X; b &= b - 1) {
            idx = numberOfTrailingZeros(b);
            p |= ((long) (bitCount(lo & ((1L << idx) - 1)) * 26 + g[idx])) << (n++ << 3);
         }
         offset = bitCount(lo);
         for (long b = hi & ghi; b != X; b &= b - 1) {
            idx = numberOfTrailingZeros(b);
            p |= ((long) ((offset + bitCount(hi & ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3);
         }
      } else {
         offset = bitCount(hi);
         for (long b = hi & ghi; b != X; b &= b - 1) {
            idx = numberOfTrailingZeros(b);
            p |= ((long) (bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3);
         }
         for (long b = lo & glo; b != X; b &= b - 1) {
            idx = numberOfTrailingZeros(b);
            p |= ((long) ((offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))) * 26 + g[idx])) << (n++ << 3);
         }
      }
      return p;
   }
   
   /// pattern cannot be X
   public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) {
      System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs);
      for (long p = pattern >>> 8; p != X; p >>>= 8) {
         long[] bs = posBitsets[(int) (p & 0xFF) - 1];
         for (int k = 0; k < numLongs; k++) res[k] &= bs[k];
      }
      
      int count = 0;
      for (int k = 0; k < numLongs; k++) count += bitCount(res[k]);
      
      int[] indices = new int[count];
      for (int k = 0, ki = 0; k < numLongs; k++) {
         for (long w = res[k]; w != X; w &= w - 1) indices[ki++] = (k << 6) | numberOfTrailingZeros(w);
      }
      
      return indices;
   }
   /// pattern cannot be X
   public static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) {
      System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs);
      for (long p = pattern >>> 8; p != X; p >>>= 8) {
         long[] bs = posBitsets[(int) (p & 0xFF) - 1];
         for (int k = 0; k < numLongs; k++) res[k] &= bs[k];
      }
      
      int count = 0;
      for (int k = 0; k < numLongs; k++) count += bitCount(res[k]);
      return count;
   }
   
   public static FillResult fillMask(final Rng rng, final Slotinfo[] slots,
                                     final Grid grid) {
      val used   = new long[2048];
      val bitset = new long[2500];
      val g      = grid.g;
      val TOTAL  = slots.length;
      val t0     = System.currentTimeMillis();
      class Solver {
         
         long nodes;
         long backtracks;
         long glo = grid.lo, ghi = grid.hi;
         Slotinfo currentSlot;
         int[]    currentIndices;
         boolean placeWordDec(final long lo, final long hi, final long w) {
            int idx;
            int bcHi = bitCount(hi);
            for (long b = hi & ghi; b != X; b &= b - 1)
               if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false;
            for (long b = lo & glo; b != X; b &= b - 1)
               if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false;
            
            long maskLo = lo & ~glo, maskHi = hi & ~ghi;
            if ((maskLo | maskHi) != X) {
               for (long b = maskHi; b != X; b &= b - 1) g[64 | (idx = numberOfTrailingZeros(b))] = Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))));
               for (long b = maskLo; b != X; b &= b - 1) g[idx = numberOfTrailingZeros(b)] = Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))));
               glo |= maskLo;
               ghi |= maskHi;
            }
            return true;
         }
         boolean placeWordInc(final long lo, final long hi, final long w) {
            int idx;
            for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false;
            int bcLo = bitCount(lo);
            for (long b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false;
            
            long maskLo = lo & ~glo, maskHi = hi & ~ghi;
            if ((maskLo | maskHi) != X) {
               for (long b = maskLo; b != X; b &= b - 1) g[idx = idx = numberOfTrailingZeros(b)] = Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)));
               for (long b = maskHi; b != X; b &= b - 1) g[64 | (idx = numberOfTrailingZeros(b))] = Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)));
               glo |= maskLo;
               ghi |= maskHi;
            }
            return true;
         }
         int chooseMRV() {
            Slotinfo best   = null;
            int      count2 = -1, bestScore = -1;
            for (int i = 0, n = TOTAL; i < n; i++) {
               var s = slots[i];
               if (s.assign.w != X) continue;
               var pattern = patternForSlot(glo, ghi, g, s.key, s.lo, s.hi);
               var index   = s.entry;
               int count   = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong);
               
               if (count == 0) return PICK_NOT_DONE;
               if (best == null
                   || count < count2
                   || (count == count2 && s.score > bestScore)) {
                  best      = s;
                  bestScore = s.score;
                  count2    = count;
                  if (count <= 1) break;
               }
            }
            if (best == null) return PICK_DONE;
            var pattern = patternForSlot(glo, ghi, g, best.key, best.lo, best.hi);
            currentSlot = best;
            var index = best.entry;
            currentIndices = pattern == X ? null : candidateInfoForPattern(bitset, pattern, index.posBitsets, index.numlong);
            return 1;
         }
         boolean backtrack(int depth) {
            if (Thread.currentThread().isInterrupted() || (System.currentTimeMillis() - t0) > 20_000) return false;
            nodes++;
            
            int status = chooseMRV();
            if (status == PICK_DONE) return true;
            if (status == PICK_NOT_DONE) {
               backtracks++;
               return false;
            }
            val  info   = currentIndices;
            val  s      = currentSlot;
            val  inc    = Slotinfo.increasing(s.key);
            val  slo    = s.lo;
            val  shi    = s.hi;
            val  assign = s.assign;
            val  words  = s.entry.words;
            long low, top;
            if (info != null && info.length > 0) {
               var idxs  = info;
               var L     = idxs.length;
               var tries = Math.min(MAX_TRIES_PER_SLOT, L);
               
               for (var t = 0; t < tries; t++) {
                  var w      = words[idxs[rng.biasedIndexPow3(L - 1)]];
                  var lemIdx = Lemma.unpackIndex(w);
                  if (Bit1029.get(used, lemIdx)) continue;
                  low = glo;
                  top = ghi;
                  if (inc) {
                     if (!placeWordInc(slo, shi, w)) continue;
                  } else {
                     if (!placeWordDec(slo, shi, w)) continue;
                  }
                  
                  Bit1029.set(used, lemIdx);
                  assign.w = w;
                  if (backtrack(depth + 1)) return true;
                  assign.w = X;
                  Bit1029.clear(used, lemIdx);
                  glo = low;
                  ghi = top;
               }
               backtracks++;
               return false;
            }
            
            var N = words.length;
            
            for (var t = 0; t < s.minL; t++) {
               var w      = words[rng.biasedIndexPow3(N - 1)];
               var lemIdx = Lemma.unpackIndex(w);
               if (Bit1029.get(used, lemIdx)) continue;
               low = glo;
               top = ghi;
               if (inc) {
                  if (!placeWordInc(slo, shi, w)) continue;
               } else {
                  if (!placeWordDec(slo, shi, w)) continue;
               }
               
               Bit1029.set(used, lemIdx);
               assign.w = w;
               if (backtrack(depth + 1)) return true;
               assign.w = X;
               Bit1029.clear(used, lemIdx);
               
               glo = low;
               ghi = top;
            }
            
            backtracks++;
            return false;
         }
      }
      
      var solver = new Solver();
      var ok     = solver.backtrack(0);
      grid.lo = solver.glo;
      grid.hi = solver.ghi;
      
      return new FillResult(ok, solver.nodes, solver.backtracks, solver.currentSlot == null ? 0 : solver.currentSlot.entry.length, System.currentTimeMillis() - t0,
                            new FillStats());
   }
}
