aboutsummaryrefslogtreecommitdiffstats
path: root/core/trip.c
blob: c35cb0e431cd80217bc8dd82773a5a17c4773a4d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
// SPDX-License-Identifier: GPL-2.0

#include "trip.h"
#include "dive.h"
#include "subsurface-string.h"
#include "selection.h"
#include "table.h"
#include "core/qthelper.h"

struct trip_table trip_table;

#ifdef DEBUG_TRIP
void dump_trip_list(void)
{
	dive_trip_t *trip;
	int i = 0;
	timestamp_t last_time = 0;

	for (i = 0; i < trip_table.nr; ++i) {
		struct tm tm;
		trip = trip_table.trips[i];
		utc_mkdate(trip_date(trip), &tm);
		if (trip_date(trip) < last_time)
			printf("\n\ntrip_table OUT OF ORDER!!!\n\n\n");
		printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n",
		       trip->autogen ? "autogen " : "",
		       i + 1, trip->location,
		       tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
		       trip->dives.nr, trip);
		last_time = trip_date(trip);
	}
	printf("-----\n");
}
#endif

/* free resources associated with a trip structure */
void free_trip(dive_trip_t *trip)
{
	if (trip) {
		free(trip->location);
		free(trip->notes);
		free(trip->dives.dives);
		free(trip);
	}
}

/* Trip table functions */
static MAKE_GET_IDX(trip_table, struct dive_trip *, trips)
static MAKE_GROW_TABLE(trip_table, struct dive_trip *, trips)
static MAKE_GET_INSERTION_INDEX(trip_table, struct dive_trip *, trips, trip_less_than)
static MAKE_ADD_TO(trip_table, struct dive_trip *, trips)
static MAKE_REMOVE_FROM(trip_table, trips)
MAKE_SORT(trip_table, struct dive_trip *, trips, comp_trips)
MAKE_REMOVE(trip_table, struct dive_trip *, trip)
MAKE_CLEAR_TABLE(trip_table, trips, trip)

timestamp_t trip_date(const struct dive_trip *trip)
{
	if (!trip || trip->dives.nr == 0)
		return 0;
	return trip->dives.dives[0]->when;
}

timestamp_t trip_enddate(const struct dive_trip *trip)
{
	if (!trip || trip->dives.nr == 0)
		return 0;
	return dive_endtime(trip->dives.dives[trip->dives.nr - 1]);
}

/* check if we have a trip right before / after this dive */
bool is_trip_before_after(const struct dive *dive, bool before)
{
	int idx = get_idx_by_uniq_id(dive->id);
	if (before) {
		if (idx > 0 && get_dive(idx - 1)->divetrip)
			return true;
	} else {
		if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip)
			return true;
	}
	return false;
}

/* Add dive to a trip. Caller is responsible for removing dive
 * from trip beforehand. */
void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
{
	if (dive->divetrip == trip)
		return;
	if (dive->divetrip)
		SSRF_INFO("Warning: adding dive to trip that has trip set\n");
	insert_dive(&trip->dives, dive);
	dive->divetrip = trip;
}

/* remove a dive from the trip it's associated to, but don't delete the
 * trip if this was the last dive in the trip. the caller is responsible
 * for removing the trip, if the trip->dives.nr went to 0.
 */
struct dive_trip *unregister_dive_from_trip(struct dive *dive)
{
	dive_trip_t *trip = dive->divetrip;

	if (!trip)
		return NULL;

	remove_dive(dive, &trip->dives);
	dive->divetrip = NULL;
	return trip;
}

static void delete_trip(dive_trip_t *trip, struct trip_table *trip_table_arg)
{
	remove_trip(trip, trip_table_arg);
	free_trip(trip);
}

void remove_dive_from_trip(struct dive *dive, struct trip_table *trip_table_arg)
{
	struct dive_trip *trip = unregister_dive_from_trip(dive);
	if (trip && trip->dives.nr == 0)
		delete_trip(trip, trip_table_arg);
}

dive_trip_t *alloc_trip(void)
{
	dive_trip_t *res = calloc(1, sizeof(dive_trip_t));
	res->id = dive_getUniqID();
	return res;
}

/* insert the trip into the trip table */
void insert_trip(dive_trip_t *dive_trip, struct trip_table *trip_table_arg)
{
	int idx = trip_table_get_insertion_index(trip_table_arg, dive_trip);
	add_to_trip_table(trip_table_arg, idx, dive_trip);
#ifdef DEBUG_TRIP
	dump_trip_list();
#endif
}

dive_trip_t *create_trip_from_dive(struct dive *dive)
{
	dive_trip_t *trip;

	trip = alloc_trip();
	trip->location = copy_string(get_dive_location(dive));

	return trip;
}

dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive, struct trip_table *trip_table_arg)
{
	dive_trip_t *dive_trip;

	dive_trip = create_trip_from_dive(dive);

	add_dive_to_trip(dive, dive_trip);
	insert_trip(dive_trip, trip_table_arg);
	return dive_trip;
}

/* random threshold: three days without diving -> new trip
 * this works very well for people who usually dive as part of a trip and don't
 * regularly dive at a local facility; this is why trips are an optional feature */
#define TRIP_THRESHOLD 3600 * 24 * 3

/*
 * Find a trip a new dive should be autogrouped with. If no such trips
 * exist, allocate a new trip. The bool "*allocated" is set to true
 * if a new trip was allocated.
 */
dive_trip_t *get_trip_for_new_dive(struct dive *new_dive, bool *allocated)
{
	struct dive *d;
	dive_trip_t *trip;
	int i;

	/* Find dive that is within TRIP_THRESHOLD of current dive */
	for_each_dive(i, d) {
		/* Check if we're past the range of possible dives */
		if (d->when >= new_dive->when + TRIP_THRESHOLD)
			break;

		if (d->when + TRIP_THRESHOLD >= new_dive->when && d->divetrip) {
			/* Found a dive with trip in the range */
			*allocated = false;
			return d->divetrip;
		}
	}

	/* Didn't find a trip -> allocate a new one */
	trip = create_trip_from_dive(new_dive);
	trip->autogen = true;
	*allocated = true;
	return trip;
}

/* lookup of trip in main trip_table based on its id */
dive_trip_t *get_trip_by_uniq_id(int tripId)
{
	for (int i = 0; i < trip_table.nr; i++) {
		if (trip_table.trips[i]->id == tripId)
			return trip_table.trips[i];
	}
	return NULL;
}

/* Check if two trips overlap time-wise up to trip threshold. */
bool trips_overlap(const struct dive_trip *t1, const struct dive_trip *t2)
{
	/* First, handle the empty-trip cases. */
	if (t1->dives.nr == 0 || t2->dives.nr == 0)
		return 0;

	if (trip_date(t1) < trip_date(t2))
		return trip_enddate(t1) + TRIP_THRESHOLD >= trip_date(t2);
	else
		return trip_enddate(t2) + TRIP_THRESHOLD >= trip_date(t1);
}

/*
 * Collect dives for auto-grouping. Pass in first dive which should be checked.
 * Returns range of dives that should be autogrouped and trip it should be
 * associated to. If the returned trip was newly allocated, the last bool
 * is set to true. Caller still has to register it in the system. Note
 * whereas this looks complicated - it is needed by the undo-system, which
 * manually injects the new trips. If there are no dives to be autogrouped,
 * return NULL.
 */
dive_trip_t *get_dives_to_autogroup(struct dive_table *table, int start, int *from, int *to, bool *allocated)
{
	int i;
	struct dive *lastdive = NULL;

	/* Find first dive that should be merged and remember any previous
	 * dive that could be merged into.
	 */
	for (i = start; i < table->nr; i++) {
		struct dive *dive = table->dives[i];
		dive_trip_t *trip;

		if (dive->divetrip) {
			lastdive = dive;
			continue;
		}

		/* Only consider dives that have not been explicitly removed from
		 * a dive trip by the user.  */
		if (dive->notrip) {
			lastdive = NULL;
			continue;
		}

		/* We found a dive, let's see if we have to allocate a new trip */
		if (!lastdive || dive->when >= lastdive->when + TRIP_THRESHOLD) {
			/* allocate new trip */
			trip = create_trip_from_dive(dive);
			trip->autogen = true;
			*allocated = true;
		} else {
			/* use trip of previous dive */
			trip = lastdive->divetrip;
			*allocated = false;
		}

		// Now, find all dives that will be added to this trip
		lastdive = dive;
		*from = i;
		for (*to = *from + 1; *to < table->nr; (*to)++) {
			dive = table->dives[*to];
			if (dive->divetrip || dive->notrip ||
			    dive->when >= lastdive->when + TRIP_THRESHOLD)
				break;
			if (get_dive_location(dive) && !trip->location)
				trip->location = copy_string(get_dive_location(dive));
			lastdive = dive;
		}
		return trip;
	}

	/* Did not find anyhting - mark as end */
	return NULL;
}

void deselect_dives_in_trip(struct dive_trip *trip)
{
	if (!trip)
		return;
	for (int i = 0; i < trip->dives.nr; ++i)
		deselect_dive(trip->dives.dives[i]);
}

void select_dives_in_trip(struct dive_trip *trip)
{
	struct dive *dive;
	if (!trip)
		return;
	for (int i = 0; i < trip->dives.nr; ++i) {
		dive = trip->dives.dives[i];
		if (!dive->hidden_by_filter)
			select_dive(dive);
	}
}

/* Out of two strings, copy the string that is not empty (if any). */
static char *copy_non_empty_string(const char *a, const char *b)
{
	return copy_string(empty_string(b) ? a : b);
}

/* This combines the information of two trips, generating a
 * new trip. To support undo, we have to preserve the old trips. */
dive_trip_t *combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b)
{
	dive_trip_t *trip;

	trip = alloc_trip();
	trip->location = copy_non_empty_string(trip_a->location, trip_b->location);
	trip->notes = copy_non_empty_string(trip_a->notes, trip_b->notes);

	return trip;
}

/* Trips are compared according to the first dive in the trip. */
int comp_trips(const struct dive_trip *a, const struct dive_trip *b)
{
	/* This should never happen, nevertheless don't crash on trips
	 * with no (or worse a negative number of) dives. */
	if (a->dives.nr <= 0)
		return b->dives.nr <= 0 ? 0 : -1;
	if (b->dives.nr <= 0)
		return 1;
	return comp_dives(a->dives.dives[0], b->dives.dives[0]);
}

bool trip_less_than(const struct dive_trip *a, const struct dive_trip *b)
{
	return comp_trips(a, b) < 0;
}

static bool is_same_day(timestamp_t trip_when, timestamp_t dive_when)
{
	static timestamp_t twhen = (timestamp_t) 0;
	static struct tm tmt;
	struct tm tmd;

	utc_mkdate(dive_when, &tmd);

	if (twhen != trip_when) {
		twhen = trip_when;
		utc_mkdate(twhen, &tmt);
	}

	return (tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year);
}

bool trip_is_single_day(const struct dive_trip *trip)
{
	if (trip->dives.nr <= 1)
		return true;
	return is_same_day(trip->dives.dives[0]->when,
			   trip->dives.dives[trip->dives.nr - 1]->when);
}

int trip_shown_dives(const struct dive_trip *trip)
{
	int res = 0;
	for (int i = 0; i < trip->dives.nr; ++i) {
		if (!trip->dives.dives[i]->hidden_by_filter)
			res++;
	}
	return res;
}