1
/* ipuz-clue-sets.c
2
 *
3
 * Copyright 2023 Jonathan Blandford <jrb@gnome.org>
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
 *
19
 * SPDX-License-Identifier: (LGPL-2.1-or-later OR MIT)
20
 */
21

            
22
#include "libipuz-config.h"
23
#include "ipuz-clue-sets.h"
24
#include <glib/gi18n-lib.h>
25

            
26

            
27
typedef struct _ClueSet
28
{
29
  IPuzClueDirection direction;
30
  IPuzClueDirection original_direction;
31
  gchar *label;
32
  GArray *clues;
33
} ClueSet;
34

            
35
struct _IPuzClueSets
36
{
37
  grefcount ref_count;
38
  guint custom_counter;
39
  GArray *clue_sets;
40
};
41

            
42

            
43
G_DEFINE_BOXED_TYPE (IPuzClueSets, ipuz_clue_sets, ipuz_clue_sets_ref, ipuz_clue_sets_unref);
44

            
45

            
46
/* ClueSet functions */
47

            
48
/* This is called from GArray, which gives us the address of one of its
49
 * elements to clear.  Since we are storing (IPuzClue *) in each element,
50
 * we need double indirection here.
51
 */
52
static void
53
922
free_one_clue (IPuzClue **clue_ptr)
54
{
55
922
  ipuz_clue_free (*clue_ptr);
56
922
  *clue_ptr = NULL;
57
922
}
58

            
59

            
60
static ClueSet *
61
63
clue_set_new (IPuzClueDirection  direction,
62
              const gchar       *label)
63
{
64
  ClueSet *clue_set;
65

            
66
63
  clue_set = g_new0 (ClueSet, 1);
67

            
68
63
  clue_set->direction = direction;
69
63
  clue_set->original_direction = direction;
70
63
  clue_set->label = g_strdup (label);
71
63
  clue_set->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
72
63
  g_array_set_clear_func (clue_set->clues, (GDestroyNotify) free_one_clue);
73

            
74
63
  return clue_set;
75
}
76

            
77
static ClueSet *
78
3
clue_set_copy (ClueSet *src)
79
{
80
  ClueSet *dest;
81

            
82
3
  dest = g_new0 (ClueSet, 1);
83

            
84
3
  dest->direction = src->direction;
85
3
  dest->original_direction = src->original_direction;
86
3
  dest->label = g_strdup (src->label);
87
3
  dest->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
88
3
  g_array_set_clear_func (dest->clues, (GDestroyNotify) free_one_clue);
89

            
90
60
  for (guint i = 0; i < src->clues->len; i++)
91
    {
92
57
      IPuzClue *clue = g_array_index (src->clues, IPuzClue *, i);
93
57
      IPuzClue *new_clue = ipuz_clue_copy (clue);
94
57
      g_array_append_val (dest->clues, new_clue);
95
    }
96

            
97
3
  return dest;
98
}
99

            
100
static void
101
66
clue_set_free (ClueSet *clue_set)
102
{
103
66
  g_assert (clue_set);
104

            
105
66
  g_array_unref (clue_set->clues);
106
66
  g_free (clue_set->label);
107
66
  g_free (clue_set);
108
66
}
109

            
110
static ClueSet *
111
1482
find_clue_set (IPuzClueSets      *clue_sets,
112
               IPuzClueDirection  direction)
113
{
114
1990
  for (guint i = 0; i < clue_sets->clue_sets->len; i++)
115
    {
116
      ClueSet *clue_set;
117

            
118
1974
      clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
119
1974
      if (clue_set->direction == direction)
120
1466
        return clue_set;
121
    }
122

            
123
16
  return NULL;
124
}
125

            
126
/* IPuzClueSets */
127

            
128
static void
129
66
free_one_clue_set (ClueSet **clue_set)
130
{
131
66
  clue_set_free (*clue_set);
132
66
}
133

            
134
IPuzClueSets *
135
43
ipuz_clue_sets_new (void)
136
{
137
  IPuzClueSets *clue_sets;
138

            
139
43
  clue_sets = g_new0 (IPuzClueSets, 1);
140
43
  g_ref_count_init (&clue_sets->ref_count);
141
43
  clue_sets->clue_sets = g_array_new (FALSE, TRUE, sizeof (ClueSet *));
142
43
  g_array_set_clear_func (clue_sets->clue_sets, (GDestroyNotify) free_one_clue_set);
143

            
144
43
  return clue_sets;
145
}
146

            
147
IPuzClueSets *
148
ipuz_clue_sets_ref (IPuzClueSets *clue_sets)
149
{
150
  g_return_val_if_fail (clue_sets != NULL, NULL);
151

            
152
  g_ref_count_inc (&clue_sets->ref_count);
153

            
154
  return clue_sets;
155
}
156

            
157
void
158
43
ipuz_clue_sets_unref (IPuzClueSets *clue_sets)
159
{
160
43
  if (clue_sets == NULL)
161
    return;
162

            
163
43
  if (!g_ref_count_dec (&clue_sets->ref_count))
164
    return;
165

            
166
43
  g_clear_pointer (&clue_sets->clue_sets, g_array_unref);
167
43
  g_free (clue_sets);
168
}
169

            
170
gboolean
171
3
ipuz_clue_sets_equal (IPuzClueSets *a,
172
                      IPuzClueSets *b)
173
{
174
3
  if (a == NULL && b == NULL)
175
    return TRUE;
176
3
  if (a == NULL || b == NULL)
177
    return FALSE;
178
3
  if (a->clue_sets->len != b->clue_sets->len)
179
    return FALSE;
180

            
181
7
  for (guint i = 0; i < a->clue_sets->len; i++)
182
    {
183
      ClueSet *clue_set_a, *clue_set_b;
184

            
185
4
      clue_set_a = g_array_index (a->clue_sets, ClueSet *, i);
186
4
      clue_set_b = g_array_index (a->clue_sets, ClueSet *, i);
187

            
188
4
      if (clue_set_a->original_direction != clue_set_b->original_direction)
189
        return FALSE;
190
4
      if (g_strcmp0 (clue_set_a->label, clue_set_b->label))
191
        return FALSE;
192

            
193
4
      if (clue_set_a->clues)
194
        {
195
4
          if (clue_set_b->clues == NULL)
196
            return FALSE;
197

            
198
4
          if (clue_set_a->clues->len != clue_set_b->clues->len)
199
            return FALSE;
200

            
201
87
          for (guint i = 0; i < clue_set_a->clues->len; i++)
202
            {
203
83
              if (! ipuz_clue_equal (g_array_index (clue_set_a->clues, IPuzClue *, i),
204
83
                                     g_array_index (clue_set_b->clues, IPuzClue *, i)))
205
                return FALSE;
206
            }
207
        }
208
      else if (clue_set_b->clues)
209
        return FALSE;
210
    }
211

            
212
3
  return TRUE;
213
}
214

            
215
void
216
2
ipuz_clue_sets_clone (IPuzClueSets *src,
217
                      IPuzClueSets *dest)
218
{
219
  /* Clear dest->clue_sets, if necessary */
220
2
  g_array_set_size (dest->clue_sets, 0);
221
2
  g_array_set_size (dest->clue_sets, src->clue_sets->len);
222

            
223
2
  dest->custom_counter = src->custom_counter;
224

            
225
5
  for (guint i = 0; i < src->clue_sets->len; i++)
226
    {
227
      ClueSet *src_clue_set;
228
      ClueSet *dest_clue_set;
229

            
230
3
      src_clue_set = g_array_index (src->clue_sets, ClueSet *, i);
231
3
      dest_clue_set = clue_set_copy (src_clue_set);
232
3
      g_array_index (dest->clue_sets, ClueSet *, i) = dest_clue_set;
233
    }
234
2
}
235

            
236
/* Returns TRUE if there are two clue sets with the same direction and
237
 * label. That indicates an invalid puzzle. */
238
static gboolean
239
63
check_for_dupes (IPuzClueSets      *clue_sets,
240
                 IPuzClueDirection  direction,
241
                 const gchar       *label,
242
                 gboolean          *custom_needed)
243
{
244
63
  g_assert (clue_sets);
245
63
  g_assert (clue_sets->clue_sets);
246

            
247
93
  for (guint i = 0; i < clue_sets->clue_sets->len; i++)
248
    {
249
30
      ClueSet *clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
250

            
251
      /* Have we ever had a clue of the same direction? */
252
30
      if (clue_set->original_direction == direction)
253
        {
254
          /* Check to see if there's a duplicate through the same
255
           * label */
256
4
          if (! g_strcmp0 (clue_set->label, label))
257
            return TRUE;
258
4
          *custom_needed = TRUE;
259
        }
260
    }
261
63
  return FALSE;
262
}
263

            
264
gint
265
30
clue_sets_sort_func (gconstpointer a,
266
                     gconstpointer b)
267
{
268
30
  ClueSet *clue_set_a = *((ClueSet **) a);
269
30
  ClueSet *clue_set_b = *((ClueSet **) b);
270

            
271
30
  return (gint) clue_set_a->direction - (gint) clue_set_b->direction;
272
}
273

            
274
IPuzClueDirection
275
63
ipuz_clue_sets_add_set (IPuzClueSets      *clue_sets,
276
                        IPuzClueDirection  direction,
277
                        const gchar       *label)
278
{
279
  ClueSet *clue_set;
280
63
  gboolean custom_needed = FALSE;
281

            
282
63
  g_return_val_if_fail (clue_sets != NULL, IPUZ_CLUE_DIRECTION_NONE);
283

            
284
63
  if (check_for_dupes (clue_sets, direction, label, &custom_needed))
285
    return IPUZ_CLUE_DIRECTION_NONE;
286

            
287
63
  clue_set = clue_set_new (direction, label);
288
  /* If there's already a clue_set with its direction set to
289
   * direction, then we use the CUSTOM direction to indicate it's
290
   * different
291
   */
292
63
  if (custom_needed)
293
3
    clue_set->direction = IPUZ_CLUE_DIRECTION_CUSTOM + clue_sets->custom_counter++;
294

            
295
63
  g_array_append_val (clue_sets->clue_sets, clue_set);
296
63
  g_array_sort (clue_sets->clue_sets, clue_sets_sort_func);
297

            
298
63
  return clue_set->direction;
299
}
300

            
301
void
302
38
ipuz_clue_sets_foreach (IPuzClueSets            *clue_sets,
303
                        IPuzClueSetsForeachFunc  func,
304
                        gpointer                 user_data)
305
{
306
38
  g_return_if_fail (clue_sets != NULL);
307
38
  g_return_if_fail (func != NULL);
308

            
309
104
  for (guint i = 0; i < clue_sets->clue_sets->len; i++)
310
    {
311
      ClueSet *clue_set;
312

            
313
66
      clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
314

            
315
66
      (* func) (clue_sets, clue_set->direction, user_data);
316
    }
317
}
318

            
319
guint
320
59
ipuz_clue_sets_get_n_clue_sets (IPuzClueSets *clue_sets)
321
{
322
59
  g_return_val_if_fail (clue_sets != NULL, 0);
323

            
324
59
  return clue_sets->clue_sets->len;
325
}
326

            
327
IPuzClueDirection
328
31
ipuz_clue_sets_get_direction (IPuzClueSets *clue_sets,
329
                              guint         index)
330
{
331
  ClueSet *clue_set;
332

            
333
31
  g_return_val_if_fail (clue_sets != NULL, IPUZ_CLUE_DIRECTION_NONE);
334
31
  g_return_val_if_fail (index < clue_sets->clue_sets->len, IPUZ_CLUE_DIRECTION_NONE);
335

            
336
31
  clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, index);
337
31
  g_assert (clue_set);
338

            
339
31
  return clue_set->direction;
340
}
341

            
342
/* This will create a clue_set if it doesn't already exist */
343
void
344
127
ipuz_clue_sets_append_clue (IPuzClueSets      *clue_sets,
345
                            IPuzClueDirection  direction,
346
                            IPuzClue          *clue)
347
{
348
  ClueSet *clue_set;
349

            
350
127
  g_return_if_fail (clue_sets != NULL);
351
127
  clue_set = find_clue_set (clue_sets, direction);
352
127
  if (clue_set == NULL)
353
    {
354
8
      direction = ipuz_clue_sets_add_set (clue_sets, direction, NULL);
355
8
      clue_set = find_clue_set (clue_sets, direction);
356
8
      g_return_if_fail (clue_set != NULL);
357
    }
358

            
359
127
  g_array_append_val (clue_set->clues, clue);
360
}
361

            
362

            
363
void
364
2
ipuz_clue_sets_remove_clue (IPuzClueSets      *clue_sets,
365
                            IPuzClueDirection  direction,
366
                            IPuzClue          *clue,
367
                            gboolean           remove_empty)
368
{
369
  GArray *clues;
370

            
371
2
  g_return_if_fail (clue_sets != NULL);
372

            
373
2
  clues = ipuz_clue_sets_get_clues (clue_sets, direction);
374
2
  g_return_if_fail (clues != NULL);
375

            
376
2
  for (guint i = 0; i < clues->len; i++)
377
    {
378
2
      if (clue == g_array_index (clues, IPuzClue *, i))
379
        {
380
2
          g_array_remove_index (clues, i);
381
2
          break;
382
        }
383
    }
384

            
385
  /* remove the clue_set if it's empty */
386
2
  if (remove_empty && clues->len == 0)
387
    {
388
2
      for (guint i = 0; i < clue_sets->clue_sets->len; i++)
389
        {
390
          ClueSet *clue_set;
391

            
392
2
          clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
393
2
          if (clue_set->direction == direction)
394
            {
395
2
              g_assert (clue_set->clues == clues);
396
2
              g_array_remove_index (clue_sets->clue_sets, i);
397
2
              break;
398
            }
399
        }
400
    }
401
}
402

            
403
const gchar *
404
5
ipuz_clue_sets_get_label (IPuzClueSets      *clue_sets,
405
                          IPuzClueDirection  direction)
406
{
407
  ClueSet *clue_set;
408

            
409
5
  g_return_val_if_fail (clue_sets != NULL, NULL);
410

            
411
5
  clue_set = find_clue_set (clue_sets, direction);
412
5
  g_return_val_if_fail (clue_set != NULL, NULL);
413

            
414
5
  if (clue_set->label)
415
2
    return clue_set->label;
416

            
417
3
  return ipuz_clue_direction_to_string (clue_set->original_direction);
418
}
419

            
420
GArray *
421
1140
ipuz_clue_sets_get_clues (IPuzClueSets      *clue_sets,
422
                          IPuzClueDirection  direction)
423
{
424
  ClueSet *clue_set;
425

            
426
1140
  g_return_val_if_fail (clue_sets != NULL, NULL);
427

            
428
1140
  clue_set = find_clue_set (clue_sets, direction);
429
1140
  if (clue_set == NULL)
430
8
    return NULL;
431

            
432
1132
  return clue_set->clues;
433
}
434

            
435
IPuzClueDirection
436
202
ipuz_clue_sets_get_original_direction (IPuzClueSets      *clue_sets,
437
                                       IPuzClueDirection  direction)
438
{
439
  ClueSet *clue_set;
440

            
441
202
  g_return_val_if_fail (clue_sets != NULL, IPUZ_CLUE_DIRECTION_NONE);
442

            
443
202
  clue_set = find_clue_set (clue_sets, direction);
444
202
  if (clue_set)
445
202
    return clue_set->original_direction;
446

            
447
  return IPUZ_CLUE_DIRECTION_NONE;
448
}
449

            
450
void
451
ipuz_clue_sets_unlink_direction (IPuzClueSets      *clue_sets,
452
                                 IPuzClueDirection  direction)
453
{
454
  g_return_if_fail (clue_sets != NULL);
455

            
456
  for (guint i = 0; i < clue_sets->clue_sets->len; i++)
457
    {
458
      ClueSet *clue_set;
459

            
460
      clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
461
      if (clue_set->direction == direction)
462
        {
463
          g_array_remove_index (clue_sets->clue_sets, i);
464
          return;
465
        }
466
    }
467
}