/**
		 * Calculates difficulty of Sudoku puzzle. Returned difficulty level is an average
		 * of number of closed routes while performing recursive steps in order to find solution.
		 * This is multi-threading procedure.
		 *
		 * @param sudokuPuzzle   Sudoku puzzle to be rated.
		 * @return               If puzzle does not contain an error then difficulty rating is returned.
		 *                       If puzzle contains obvious error then {@link ErrorCodes#SUDOKUSTORE_CALCULATEPUZZLERATING_PUZZLE_ERROR}.
		 *                       If puzzle has no solutions then {@link ErrorCodes#SUDOKUSTORE_CALCULATEPUZZLERATING_NO_SOLUTION}.
		 *                       If solution is non-unique then {@link ErrorCodes#SUDOKUSTORE_CALCULATEPUZZLERATING_NON_UNIQUE_SOLUTION}.
		 */
		public static int calculatePuzzleRating(int[,] sudokuPuzzle) {
			if (checkPuzzle(sudokuPuzzle) == false)
				return ErrorCodes.SUDOKUSTORE_CALCULATEPUZZLERATING_PUZZLE_ERROR;
			SudokuSolver s = new SudokuSolver(sudokuPuzzle);
			int solType = s.checkIfUniqueSolution();
			if (solType == SudokuSolver.SOLUTION_NOT_EXISTS)
				return ErrorCodes.SUDOKUSTORE_CALCULATEPUZZLERATING_NO_SOLUTION;
			if (solType == SudokuSolver.SOLUTION_NON_UNIQUE)
				return ErrorCodes.SUDOKUSTORE_CALCULATEPUZZLERATING_NON_UNIQUE_SOLUTION;
			/*
			 * Multi-threading implementation
			 */
			int threadIterNum = RATING_DEF_NUM_OF_ITERATIONS / THREADS_NUMBER;
			int[,] results = new int[THREADS_NUMBER, threadIterNum];
			Runner[] runners = new Runner[THREADS_NUMBER];
			Thread[] threads = new Thread[THREADS_NUMBER];
			for (int t = 0; t < THREADS_NUMBER; t++) {
				runners[t] = new Runner(t, threadIterNum, sudokuPuzzle, results);
				threads[t] = new Thread(runners[t].run);
			}
			for (int t = 0; t < THREADS_NUMBER; t++) {
				threads[t].Start();
			}
			for (int t = 0; t < THREADS_NUMBER; t++) {
				try {
					threads[t].Join();
				} catch (ThreadInterruptedException e) {
					Console.WriteLine(e.StackTrace);
					return ErrorCodes.SUDOKUSTORE_CALCULATEPUZZLERATING_THREADS_JOIN_FAILED;
				}
			}
			int sum = 0;
			for (int t = 0; t < THREADS_NUMBER; t++)
				for (int i = 0; i < threadIterNum; i++)
					sum+=results[t, i];
			return sum / (THREADS_NUMBER * threadIterNum);
		}
		/**
		 * Sudoku puzzle generator.
		 *
		 * @return   Sudoku puzzle if process finished correctly, otherwise null.
		 */
		public int[,] generate() {
			if (generatorState != GENERATOR_INIT_FINISHED) {
				generatorState = GENERATOR_GEN_NOT_STARTED;
				addMessage("(SudokuGenerator) Generation process not started due to incorrect initialization.", MSG_ERROR);
				return null;
			}
			long solvingStartTime = DateTimeX.currentTimeMillis();
			generatorState = GENERATOR_GEN_STARTED;
			addMessage("(SudokuGenerator) Generation process started.", MSG_INFO);
			if (randomizeFilledCells == true)
				addMessage("(SudokuGenerator) >>> Will randomize filled cells within cells with the same impact.", MSG_INFO);
			boardCells = new BoardCell[BOARD_CELLS_NUMBER];
			int cellIndex = 0;
			for (int i = 0; i < BOARD_SIZE; i++)
				for (int j = 0; j < BOARD_SIZE; j++) {
					int d = sudokuBoard[i, j];
					if (d != CELL_EMPTY) {
						boardCells[cellIndex] = new BoardCell(i, j, d);
						cellIndex++;
					}
				}
			int filledCells = cellIndex;
			for (int i = 0; i < BOARD_SIZE; i++)
				for (int j = 0; j < BOARD_SIZE; j++) {
					int d = sudokuBoard[i, j];
					if (d == CELL_EMPTY) {
						boardCells[cellIndex] = new BoardCell(i, j, d);
						cellIndex++;
					}
				}
			updateDigitsStillFreeCounts();
			sortBoardCells(0, filledCells - 1);
			do {
				int r = 0;
				int i = boardCells[r].rowIndex;
				int j = boardCells[r].colIndex;
				int d = sudokuBoard[i, j];
				sudokuBoard[i, j] = CELL_EMPTY;
				SudokuSolver s = new SudokuSolver(sudokuBoard);
				if (s.checkIfUniqueSolution() != SudokuSolver.SOLUTION_UNIQUE)
					sudokuBoard[i, j] = d;
				int lastIndex = filledCells - 1;
				if (r < lastIndex) {
					BoardCell b1 = boardCells[r];
					BoardCell b2 = boardCells[lastIndex];
					boardCells[lastIndex] = b1;
					boardCells[r] = b2;
				}
				filledCells--;
				updateDigitsStillFreeCounts();
				if (filledCells > 0) sortBoardCells(0, filledCells - 1);
			} while (filledCells > 0);
			long solvingEndTime = DateTimeX.currentTimeMillis();
			computingTime = (solvingEndTime - solvingStartTime) / 1000.0;
			generatorState = GENERATOR_GEN_FINISHED;
			addMessage("(SudokuGenerator) Generation process finished, computing time: " + computingTime + " s.", MSG_INFO);
			return sudokuBoard;
		}
			public void run() {
				for (int i = 0; i < iterNum; i++) {
					SudokuSolver s = new SudokuSolver(sudokuPuzzle);
					s.solve();
					setTestResult(i, s.getClosedRoutesNumber());
				}
			}
		/**
		 * Board initialization method.
		 * @param initBoard        Initial board.
		 * @param info             The string to pass to the msg builder.
		 */
		private void boardInit(int[,] initBoard, String info) {
			SudokuSolver puzzle;
			if ((initBoard == null) && (generateRandomBoard == true)) {
				puzzle = new SudokuSolver(SudokuPuzzles.PUZZLE_EMPTY);
				puzzle.solve();
				if (puzzle.getSolvingState() == SudokuSolver.SOLVING_STATE_SOLVED) {
					sudokuBoard = puzzle.solvedBoard;
					addMessage("(SudokuGenerator) Generator initialized using random board (" + info + ").", MSG_INFO);
					generatorState = GENERATOR_INIT_FINISHED;
					return;
				} else {
					addMessage("(SudokuGenerator) Generator initialization using random board (" + info + ") failed. Board with error?", MSG_ERROR);
					addMessage(puzzle.getLastErrorMessage(), MSG_ERROR);
					generatorState = GENERATOR_INIT_FAILED;
					return;
				}
			}
			if (SudokuStore.checkPuzzle(initBoard) == false) {
				generatorState = GENERATOR_INIT_FAILED;
				addMessage("(SudokuGenerator) Generator initialization (" + info + ") failed. Board with error?", MSG_ERROR);
				return;
			}
			if (solveBeforeGeneration == true) {
				puzzle = new SudokuSolver(initBoard);
				puzzle.solve();
				if (puzzle.getSolvingState() == SudokuSolver.SOLVING_STATE_SOLVED) {
					sudokuBoard = puzzle.solvedBoard;
					addMessage("(SudokuGenerator) Generator initialized usign provided board + finding solution (" + info + ").", MSG_INFO);
					generatorState = GENERATOR_INIT_FINISHED;
					return;
				} else {
					addMessage("(SudokuGenerator) Generator initialization usign provided board + finding solution (" + info + ") failed. Board with error?", MSG_ERROR);
					addMessage(puzzle.getLastErrorMessage(), MSG_ERROR);
					generatorState = GENERATOR_INIT_FAILED;
					return;
				}
			}
			int[,] board = initBoard;
			puzzle = new SudokuSolver(board);
			if (puzzle.checkIfUniqueSolution() == SudokuSolver.SOLUTION_UNIQUE) {
				sudokuBoard = board;
				addMessage("(SudokuGenerator) Generator initialized usign provided board (" + info + ").", MSG_INFO);
				generatorState = GENERATOR_INIT_FINISHED;
				return;
			} else {
				addMessage("(SudokuGenerator) Generator initialization usign provided board (" + info + ") failed. Solution not exists or is non unique.", MSG_ERROR);
				addMessage(puzzle.getLastErrorMessage(), MSG_ERROR);
				generatorState = GENERATOR_INIT_FAILED;
				return;
			}
		}