1/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * Copyright © 2014 Parin Porecha
4 * Copyright © 2014 Michael Catanzaro
5 *
6 * This file is part of GNOME Sudoku.
7 *
8 * GNOME Sudoku is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * GNOME Sudoku is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with GNOME Sudoku. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22using Gee;
23
24namespace SudokuGenerator
25{
26    private class Worker : Object
27    {
28        private int nsudokus;
29        private DifficultyCategory level;
30        // FIXME Require Gee.ConcurrentList and remove the mutex
31        // https://bugzilla.gnome.org/show_bug.cgi?id=737507
32        private Gee.List<SudokuBoard> boards_list;
33        private static Mutex mutex;
34        private unowned SourceFunc callback;
35
36        public Worker (int nsudokus, DifficultyCategory level, Gee.List<SudokuBoard> boards_list, SourceFunc callback)
37        {
38            this.nsudokus = nsudokus;
39            this.level = level;
40            this.boards_list = boards_list;
41            this.callback = callback;
42        }
43
44        public void run ()
45        {
46            // Generating a board takes a relatively long time.
47            var board = SudokuGenerator.generate_board (level);
48            mutex.lock ();
49            boards_list.add (board);
50            if (boards_list.size == nsudokus)
51            {
52                // We've added the final board to the list.
53                // Finish the call to generate_boards_async.
54                Idle.add (() => {
55                    callback ();
56                    return Source.REMOVE;
57                });
58            }
59            mutex.unlock ();
60        }
61    }
62
63    private SudokuBoard generate_board (DifficultyCategory category)
64    {
65        var board = new SudokuBoard ();
66        int[] puzzle = new int[board.rows * board.cols];
67        if (category != DifficultyCategory.CUSTOM)
68            puzzle = QQwing.generate_puzzle ((int) category);
69
70        for (var row = 0; row < board.rows; row++)
71            for (var col = 0; col < board.cols; col++)
72            {
73                var val = puzzle[(row * board.cols) + col];
74                if (val != 0)
75                    board.insert (row, col, val, true);
76            }
77        board.difficulty_category = category;
78
79        return board;
80    }
81
82    public async Gee.List<SudokuBoard> generate_boards_async (
83        int nboards, DifficultyCategory category, Cancellable? cancellable) throws ThreadError, IOError
84    {
85        var boards = new ArrayList<SudokuBoard> ();
86        var pool = new ThreadPool<Worker>.with_owned_data ((worker) => {
87            worker.run ();
88        }, (int) get_num_processors (), false);
89
90        if (cancellable != null)
91        {
92            cancellable.connect(() => {
93                ThreadPool.free((owned) pool, true, false);
94                generate_boards_async.callback();
95            });
96        }
97
98        for (var i = 0; i < nboards; i++)
99            pool.add (new Worker (nboards, category, boards, generate_boards_async.callback));
100
101        yield;
102
103        cancellable.set_error_if_cancelled ();
104        return boards;
105    }
106
107    public void print_stats (SudokuBoard board)
108    {
109        var cells = board.get_cells ();
110        var puzzle = new int[board.rows * board.cols];
111
112        for (var row = 0; row < board.rows; row++)
113            for (var col = 0; col < board.cols; col++)
114                puzzle[(row * board.cols) + col] = cells[row, col];
115
116        QQwing.print_stats (puzzle);
117    }
118
119    public string qqwing_version ()
120    {
121        return QQwing.get_version ();
122    }
123}
124