package puzzle;

import module java.base;
import module java.sql;

public final class ScoreHintsTask {
   
   public static void main(String[] args) throws Exception {
      Class.forName("org.sqlite.JDBC");
      try (Connection conn = DriverManager.getConnection("jdbc:sqlite:tools/hint/hint.sqlite")) {
         updateCrossScores(conn, ScoreHintsTask::crossabilityScore, 1000);
      }
   }
   static final Map<Character, Integer> LETTER_WEIGHT = Map.ofEntries(
           Map.entry('E', 10), Map.entry('N', 9), Map.entry('A', 9), Map.entry('R', 8),
           Map.entry('I', 8), Map.entry('O', 7), Map.entry('S', 7), Map.entry('T', 7),
           Map.entry('D', 6), Map.entry('L', 6), Map.entry('K', 5), Map.entry('M', 5),
           Map.entry('U', 5), Map.entry('P', 4), Map.entry('G', 4), Map.entry('H', 4),
           Map.entry('V', 4), Map.entry('B', 3), Map.entry('W', 3),
           Map.entry('C', 2), Map.entry('F', 2), Map.entry('Z', 2),
           Map.entry('J', 1), Map.entry('Y', 1), Map.entry('Q', 0), Map.entry('X', 0)
                                                                     );
   
   static boolean isVowel(char ch) {
      return ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U';
   }
   
   static int crossabilityScore(String w) {
      var score  = 0;
      var vowels = 0;
      for (var i = 0; i < w.length(); i++) {
         var ch = w.charAt(i);
         score += LETTER_WEIGHT.getOrDefault(ch, 2);
         if (isVowel(ch)) vowels++;
      }
      var ratio = vowels / (double) w.length();
      if (ratio >= 0.35 && ratio <= 0.65) score += 8;
      if (w.indexOf('Q') >= 0 || w.indexOf('X') >= 0) score -= 6;
      if (w.indexOf('Y') >= 0 || w.indexOf('J') >= 0) score -= 2;
      return score;
   }
   /**
    * Updates hints.cross_score by computing a score from hints.word.
    *
    * @param conn           open JDBC connection (PostgreSQL)
    * @param scoreFn        callback: scoreFn.applyAsInt(word)
    * @param batchSize      e.g. 1000
    */
   public static void updateCrossScores(
           Connection conn,
           ToIntFunction<String> scoreFn,
           int batchSize
                                       ) throws SQLException {
      
      // Use a transaction for speed + consistency
      final boolean prevAutoCommit = conn.getAutoCommit();
      conn.setAutoCommit(false);
      
      // Server-side cursor behavior in pgjdbc requires autoCommit=false + fetchSize>0
      final String selectSql =
              "SELECT id, puzzle_norm " +
              "FROM hints " +
              "WHERE puzzle_norm IS NOT NULL"; // optionally add: " AND cross_score IS NULL"
      
      final String updateSql =
              "UPDATE hints SET cross_score = ? WHERE id = ?";
      
      try (PreparedStatement psSel = conn.prepareStatement(selectSql);
           PreparedStatement psUpd = conn.prepareStatement(updateSql)) {
         
         psSel.setFetchSize(batchSize);
         
         int pending = 0;
         
         try (ResultSet rs = psSel.executeQuery()) {
            while (rs.next()) {
               long   id   = rs.getLong("id");
               String word = rs.getString("puzzle_norm");
               
               int score;
               try {
                  score = scoreFn.applyAsInt(word);
               } catch (RuntimeException ex) {
                  ex.printStackTrace();
                  // If scoring fails, decide your policy: skip or set 0.
                  // Here: skip row.
                  continue;
               }
               
               psUpd.setInt(1, score);
               psUpd.setLong(2, id);
               psUpd.addBatch();
               pending++;
               
               if (pending >= batchSize) {
                  psUpd.executeBatch();
                  conn.commit();
                  pending = 0;
               }
            }
         }
         
         if (pending > 0) {
            psUpd.executeBatch();
            conn.commit();
         }
         
      } catch (SQLException e) {
         conn.rollback();
         throw e;
      } finally {
         conn.setAutoCommit(prevAutoCommit);
      }
   }
}
