/**
   * Creates all the necessary sudokus. Returns <code>false</code> if interrupted.
   *
   * @return
   */
  private boolean createSudokus() {
    int anzPuzzles = 0;
    for (int i = 0; i < numberTextFields.length; i++) {
      anzPuzzles += getNumberOfPuzzles(i);
    }
    if (anzPuzzles == 0) {
      // nothing to do
      return false;
    }
    sudokus = new Sudoku2[anzPuzzles];
    candidates = new boolean[anzPuzzles];
    int index = 0;
    for (int i = 0; i < numberTextFields.length; i++) {
      DifficultyLevel actDiffLevel =
          Options.getInstance().getDifficultyLevel(levelComboBoxes[i].getSelectedIndex() + 1);
      GameMode actGameMode = null;
      boolean withCandidates = candCheckBoxes[i].isSelected();
      switch (modeComboBoxes[i].getSelectedIndex()) {
        case 0:
          actGameMode = GameMode.PLAYING;
          break;
        case 1:
          actGameMode = GameMode.LEARNING;
          break;
        case 2:
          actGameMode = GameMode.PRACTISING;
          break;
      }
      for (int j = 0; j < getNumberOfPuzzles(i); j++) {
        sudokus[index] = getSudoku(actDiffLevel, actGameMode);
        if (sudokus[index] == null || thread.isInterrupted()) {
          return false;
        }
        candidates[index] = withCandidates;
        // put puzzle in history
        Options.getInstance().addSudokuToHistory(sudokus[index]);
        index++;
        // update progress bar
        setPercentage((int) Math.round(index * 100.0 / anzPuzzles));
        EventQueue.invokeLater(
            new Runnable() {
              @Override
              public void run() {
                setProgress();
              }
            });
      }
    }

    return true;
  }
 /**
  * Retrieves a list of pages linking to the given page from Scalaris.
  *
  * @param connection the connection to Scalaris
  * @param contributor the user name or IP address of the user who created the revision
  * @return a result object with the page list on success
  */
 public static final ValueResult<List<Contribution>> getContributions(
     Connection connection, String contributor) {
   final long timeAtStart = System.currentTimeMillis();
   final String statName = "contributions of " + contributor;
   if (Options.getInstance().WIKI_STORE_CONTRIBUTIONS != STORE_CONTRIB_TYPE.NONE) {
     ValueResult<List<Contribution>> result =
         getPageList3(
             connection,
             ScalarisOpType.CONTRIBUTION,
             Arrays.asList(getContributionListKey(contributor)),
             false,
             timeAtStart,
             statName,
             new ErlangConverter<List<Contribution>>() {
               @Override
               public List<Contribution> convert(ErlangValue v) throws ClassCastException {
                 return v.jsonListValue(Contribution.class);
               }
             });
     if (result.success && result.value == null) {
       result.value = new ArrayList<Contribution>(0);
     }
     return result;
   } else {
     return new ValueResult<List<Contribution>>(
         new ArrayList<InvolvedKey>(0), new ArrayList<Contribution>(0));
   }
 }
 /**
  * Creates a new Sudoku with a given{@link DifficultyLevel} and a given {@link GameMode}. The
  * puzzles are taken from the cache if possible.<br>
  * If no puzzle could be generated (or the generation was aborted), <code>null</code> is returned.
  *
  * @param level
  * @param mode
  * @return
  */
 private Sudoku2 getSudoku(DifficultyLevel level, GameMode mode) {
   if (mode == GameMode.LEARNING) {
     // in LEARNING ANY puzzle is accepted, that has at least one Training Step in it
     level = Options.getInstance().getDifficultyLevel(DifficultyType.EXTREME.ordinal());
   }
   String preGenSudoku = BackgroundGeneratorThread.getInstance().getSudoku(level, mode);
   Sudoku2 tmpSudoku = null;
   SudokuSolver solver = SudokuSolverFactory.getDefaultSolverInstance();
   if (preGenSudoku == null) {
     // no pregenerated puzzle available -> do it in GUI
     GenerateSudokuProgressDialog dlg = new GenerateSudokuProgressDialog(null, true, level, mode);
     dlg.setVisible(true);
     tmpSudoku = dlg.getSudoku();
   } else {
     tmpSudoku = new Sudoku2();
     tmpSudoku.setSudoku(preGenSudoku, true);
     Sudoku2 solvedSudoku = tmpSudoku.clone();
     solver.solve(
         level,
         solvedSudoku,
         true,
         null,
         false,
         Options.getInstance().solverSteps,
         Options.getInstance().getGameMode());
     tmpSudoku.setLevel(solvedSudoku.getLevel());
     tmpSudoku.setScore(solvedSudoku.getScore());
   }
   if (tmpSudoku == null) {
     // couldnt create anything or was aborted
     return null;
   }
   if (mode == GameMode.LEARNING) {
     // solve the sudoku up until the first trainingStep
     List<SolutionStep> steps = solver.getSteps();
     for (SolutionStep step : steps) {
       if (step.getType().getStepConfig().isEnabledTraining()) {
         break;
       } else {
         // System.out.println("doStep(): " + step.getType().getStepName());
         solver.doStep(tmpSudoku, step);
       }
     }
   }
   return tmpSudoku;
 }
  /**
   * Retrieves a list of pages from Scalaris.
   *
   * @param <T>
   * @param connection the connection to Scalaris
   * @param opType operation type indicating what is being read
   * @param scalaris_keys the keys under which the page list is stored in Scalaris
   * @param failNotFound whether the operation should fail if the key is not found or not (the value
   *     contains null if not failed!)
   * @param timeAtStart the start time of the method using this method
   * @param statName name for the time measurement statistics
   * @return a result object with the page list on success
   */
  protected static final <T> ValueResult<List<T>> getPageList3(
      Connection connection,
      ScalarisOpType opType,
      Collection<String> scalaris_keys,
      boolean failNotFound,
      final long timeAtStart,
      String statName,
      ErlangConverter<List<T>> conv) {
    List<InvolvedKey> involvedKeys = new ArrayList<InvolvedKey>();

    if (connection == null) {
      return new ValueResult<List<T>>(
          false,
          involvedKeys,
          "no connection to Scalaris",
          true,
          statName,
          System.currentTimeMillis() - timeAtStart);
    }

    final MyScalarisSingleOpExecutor executor =
        new MyScalarisSingleOpExecutor(new TransactionSingleOp(connection), involvedKeys);

    final ScalarisReadListOp1<T> readOp =
        new ScalarisReadListOp1<T>(
            scalaris_keys, Options.getInstance().OPTIMISATIONS.get(opType), conv, failNotFound);
    executor.addOp(readOp);
    try {
      executor.run();
    } catch (Exception e) {
      return new ValueResult<List<T>>(
          false,
          involvedKeys,
          e.getClass().getCanonicalName()
              + " reading page list at \""
              + involvedKeys.toString()
              + "\" from Scalaris: "
              + e.getMessage(),
          e instanceof ConnectionException,
          statName,
          System.currentTimeMillis() - timeAtStart);
    }

    return new ValueResult<List<T>>(
        involvedKeys, readOp.getValue(), statName, System.currentTimeMillis() - timeAtStart);
  }
  /**
   * Retrieves a random page title from Scalaris.
   *
   * @param connection the connection to Scalaris
   * @param random the random number generator to use
   * @return a result object with the page list on success
   */
  public static final ValueResult<NormalisedTitle> getRandomArticle(
      Connection connection, Random random) {
    final long timeAtStart = System.currentTimeMillis();
    final String statName = "random article";

    final Optimisation optimisation =
        Options.getInstance().OPTIMISATIONS.get(ScalarisOpType.PAGE_LIST);
    final ErlangConverter<List<ErlangValue>> conv =
        new ErlangConverter<List<ErlangValue>>() {
          @Override
          public List<ErlangValue> convert(ErlangValue v) throws ClassCastException {
            return v.listValue();
          }
        };
    final List<String> scalarisKeys = Arrays.asList(getPageListKey(0));

    if (optimisation instanceof APPEND_INCREMENT_BUCKETS) {
      List<InvolvedKey> involvedKeys = new ArrayList<InvolvedKey>();

      if (connection == null) {
        return new ValueResult<NormalisedTitle>(
            false,
            involvedKeys,
            "no connection to Scalaris",
            true,
            statName,
            System.currentTimeMillis() - timeAtStart);
      }

      final MyScalarisSingleOpExecutor executor =
          new MyScalarisSingleOpExecutor(new TransactionSingleOp(connection), involvedKeys);

      final ScalarisReadRandomListEntryOp1<ErlangValue> readOp =
          new ScalarisReadRandomListEntryOp1<ErlangValue>(
              scalarisKeys, optimisation, conv, true, random);
      executor.addOp(readOp);
      try {
        executor.run();
      } catch (Exception e) {
        return new ValueResult<NormalisedTitle>(
            false,
            involvedKeys,
            e.getClass().getCanonicalName()
                + " reading page list at \""
                + involvedKeys.toString()
                + "\" from Scalaris: "
                + e.getMessage(),
            e instanceof ConnectionException,
            statName,
            System.currentTimeMillis() - timeAtStart);
      }

      // return if successful, otherwise fall back and read the whole list
      // as with no optimisation
      if (readOp.getValue() != null) {
        return new ValueResult<NormalisedTitle>(
            involvedKeys,
            NormalisedTitle.fromNormalised(readOp.getValue().stringValue()),
            statName,
            System.currentTimeMillis() - timeAtStart);
      }
    }

    ValueResult<List<ErlangValue>> result =
        getPageList3(
            connection, ScalarisOpType.PAGE_LIST, scalarisKeys, true, timeAtStart, statName, conv);
    ValueResult<NormalisedTitle> vResult;
    if (result.success) {
      String randomTitle = result.value.get(random.nextInt(result.value.size())).stringValue();
      vResult =
          new ValueResult<NormalisedTitle>(
              result.involvedKeys, NormalisedTitle.fromNormalised(randomTitle));
    } else {
      vResult =
          new ValueResult<NormalisedTitle>(
              false, result.involvedKeys, result.message, result.connect_failed);
    }
    vResult.stats = result.stats;
    return vResult;
  }
  /**
   * The actual printing is handled here. Note, that the method can be called more than once for
   * every page.
   *
   * @param graphics
   * @param pageFormat
   * @param pageIndex
   * @return
   * @throws PrinterException
   */
  @Override
  public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
      throws PrinterException {
    if (pageIndex >= numberOfPages) {
      return Printable.NO_SUCH_PAGE;
    }
    if (printBooklet && manualDuplex && pageIndex >= (numberOfPages / 2)) {
      return Printable.NO_SUCH_PAGE;
    }

    setPercentage(-(pageIndex + 1));
    EventQueue.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            setProgress();
          }
        });

    // CAUTION: The Graphics2D object is created with the native printer
    // resolution, but scaled down to 72dpi using an AffineTransform.
    // To print in high resolution this downscaling has to be reverted.
    Graphics2D printG2 = (Graphics2D) graphics;
    double scale = SudokuUtil.adjustGraphicsForPrinting(printG2);
    printG2.translate(
        (int) (pageFormat.getImageableX() * scale), (int) (pageFormat.getImageableY() * scale));
    printWidth = (int) (pageFormat.getImageableWidth() * scale);
    printHeight = (int) (pageFormat.getImageableHeight() * scale);
    //        printG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    // RenderingHints.VALUE_ANTIALIAS_ON);
    //        printG2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
    // RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

    if (!initialized) {
      // create a few global attributes for that job
      // scale fonts up too fit the printer resolution
      Font tmpFont = Options.getInstance().getSmallFont();
      smallFont =
          new Font(tmpFont.getName(), tmpFont.getStyle(), (int) (tmpFont.getSize() * scale));
      printG2.setFont(smallFont);
      // gaps between puzzles, depend on layout
      footerHeight = 0;
      if (printRating) {
        FontMetrics metrics = printG2.getFontMetrics();
        footerHeight = metrics.getHeight() * 2;
      }
      horizontalGap = 0;
      verticalGap = 0;
      borderWidth = printWidth;
      borderHeight = printHeight;
      if (!printBooklet) {
        // for layouts 1, 2 and 4 (no booklet) a vertical gap is needed
        if (layout == 1 || layout == 2 || layout == 4) {
          // 2 puzzles vertically
          verticalGap = (int) (borderHeight * GAP_FACTOR);
          borderHeight = (borderHeight - verticalGap) / 2;
        }
        // for layouts 2, 3 and 4 (no booklet) a horizontal gap is needed
        if (layout == 2 || layout == 3 || layout == 4) {
          // 2 puzzles horizontally
          horizontalGap = (int) (borderWidth * GAP_FACTOR);
          borderWidth = (borderWidth - horizontalGap) / 2;
        }
      } else {
        // if printing a booklet, layouts 3 and 4 need a horizontal gap,
        // 4 needs a vertical gap
        // in a booklet the gap in the middle is the sum of
        // insets (left inset - puzzle - right inset - left inset - puzzle - right inset)
        if (layout == 3 || layout == 4) {
          // should always be true...
          horizontalGap = (int) ((pageFormat.getWidth() - pageFormat.getImageableWidth()) * scale);
          borderWidth = (borderWidth - horizontalGap) / 2;
        }
        if (layout == 4) {
          verticalGap = (int) ((pageFormat.getHeight() - pageFormat.getImageableHeight()) * scale);
          borderHeight = (borderHeight - verticalGap) / 2;
        }
      }
      // ok: we know the space available for our sudoku; now determine the
      // actual imageSize
      imagePrintSize =
          (borderWidth < (borderHeight - footerHeight))
              ? borderWidth
              : (borderHeight - footerHeight);

      // Construct Sudoku2
      panel = new SudokuPanel(null);

      initialized = true;
    }

    // ok - draw the puzzles
    // first determine, which puzzles must be printed on the current page
    int leftStartIndex = 0;
    int rightStartIndex = 0;
    if (!printBooklet) {
      // int dummy = layout > 2 ? layout - 2 : layout;
      leftStartIndex = (int) Math.round(pageIndex * PPP[layout]);
      rightStartIndex = leftStartIndex + 1;
      if (PPP[layout] == 4) {
        rightStartIndex = leftStartIndex + 2;
      }
    } else {
      // number of puzzles per half page
      int dummy = layout - 2;
      // booklet contains numberOfPages * dummy * 2 puzzles;
      // the first puzzle on the right is in the middle
      int firstRightIndex = numberOfPages * dummy;
      int actPage = pageIndex;
      if (printBooklet && manualDuplex) {
        if (firstHalf) {
          actPage = (numberOfPages - pageIndex * 2 - 1);
        } else {
          actPage = (numberOfPages - (pageIndex + 1) * 2);
        }
      }
      leftStartIndex = firstRightIndex - (actPage + 1) * dummy;
      rightStartIndex = firstRightIndex + actPage * dummy;
    }
    // now print them
    // lets do every case on its own; bloats the code, but makes things easier
    switch (layout) {
      case 0:
        printSudoku(printG2, leftStartIndex, 0, scale);
        break;
      case 1:
        printSudoku(printG2, leftStartIndex, 0, scale);
        printSudoku(printG2, leftStartIndex + 1, 2, scale);
        break;
      case 2:
        printSudoku(printG2, leftStartIndex, 0, scale);
        printSudoku(printG2, leftStartIndex + 1, 1, scale);
        printSudoku(printG2, leftStartIndex + 2, 2, scale);
        printSudoku(printG2, leftStartIndex + 3, 3, scale);
        break;
      case 3:
        if (!printBooklet) {
          printSudoku(printG2, leftStartIndex, 0, scale);
          printSudoku(printG2, rightStartIndex, 1, scale);
        } else if ((manualDuplex && !firstHalf) || (!manualDuplex && (pageIndex % 2) == 1)) {
          printSudoku(printG2, leftStartIndex, 1, scale);
          printSudoku(printG2, rightStartIndex, 0, scale);
        } else {
          printSudoku(printG2, leftStartIndex, 0, scale);
          printSudoku(printG2, rightStartIndex, 1, scale);
        }
        break;
      case 4:
        if (!printBooklet) {
          printSudoku(printG2, leftStartIndex, 0, scale);
          printSudoku(printG2, leftStartIndex + 1, 2, scale);
          printSudoku(printG2, rightStartIndex, 1, scale);
          printSudoku(printG2, rightStartIndex + 1, 3, scale);
        } else if ((manualDuplex && !firstHalf) || (!manualDuplex && (pageIndex % 2) == 1)) {
          printSudoku(printG2, leftStartIndex, 1, scale);
          printSudoku(printG2, leftStartIndex + 1, 3, scale);
          printSudoku(printG2, rightStartIndex, 0, scale);
          printSudoku(printG2, rightStartIndex + 1, 2, scale);
        } else {
          printSudoku(printG2, leftStartIndex, 0, scale);
          printSudoku(printG2, leftStartIndex + 1, 2, scale);
          printSudoku(printG2, rightStartIndex, 1, scale);
          printSudoku(printG2, rightStartIndex + 1, 3, scale);
        }
        break;
    }
    return Printable.PAGE_EXISTS;
  }