summaryrefslogblamecommitdiffstats
path: root/divelist.c
blob: fcbc7e59c817029eb28103dad789e2a676e30057 (plain) (tree)
1
2
3
4
                
                                             
  
























                                                                           
                                          
                              
   
                   
                   
                   
                 
                 
                       
                   
                
                              
                 
                     
                    
                       
 
                                       
 
                            
 
                             
 


                            
                          
 
                                                                    
                                

                                         


                     
                                      












                                           



































                                                                              
                                              
 
                                                           
                                   

                                 
 






























                                                                                       





                                                            
                                                                        
              
                                                 
 
 
                                             
                                                     
                                          
                                                    
                                           







                                                                                               
                                                                               












                                                                                                   



                                                                            
                                 
 

                                       
                                       
                                   







                                    
         
                                              
                                                           
                                  

                         
 
                                   







                                                                          




                                                                                 
                                       









                                                              
                                                                                    
                                           

                         
                                            
 
                                           
                      
                        
                                                       
                                                                 
                                  
                                          
                        
                                                                   
                                                                                  
                 
                                                                          
         
                         
 


































































                                                                                     



                                                 
                       

                                             
                                      
                                                     
 
                                                                         
 
                                                                        
         
                               
 
                                                                     
 
                                            
                                     
                                
 
                                        
                         
 
                                        
                         
 


                                     
                                                                      
                                                                              
                                                
                                      
                          
 
 






                                                        
                                           





                                                            
                                                                                                      
                                                                               
                                                                                                         


                 
                                 





                                                                    
                                                        
                                                                             
                                            
                           
                                       
                                   
                                                  
                  
                           
                                  
                          
                          
                                                 


                                                                                     
                                                                                                                
                                   
                                                          
         
                                                 

                                                                        
                                                                                      

                                                     
                       


                                        
                       

                                                          
                                                              
                                                                         
                                                                                                      







                                                                                                  
                                                                                     
                                                                                              
                       



                                                                                          
                                                                                            
                                             
                       


                                             
                                
 
                                                    
                           
                                                
                                                           

         





                                                 
                                   
                         
                                               

                 
                                                
 
                                        
 
                                                
                                                            
 
                                            


                                    




























                                                                                                      



                                          
                         
 
                          
                
                                  
                                                              
                             
                                            
                                                                           
                                                                                                 
                                                        
                                            
                                                                                                     
                                             



                          
                                                               
                                                 
 

                                           
                 
                                             
      
                            
         
                                                      
                                  
                 
                             
                                            
                                                                         
                             
                                                                 
         

                    


                                                                      
 
                                              
                                          
                           






                                                                      





                                                       
                                    
                
                                       




                         
                                          
                              
 
                             










                                                    
                                     
                                  


                                                       
 
                                        
 

                                             
         
                          
 
                                             
 
                                   


                                           






                                                           
                              
                                 













                                                           
                                       






                                                
                                                  
 
                                                                
 
                                                               

                                                             
                                
 
                                 
                                          

                         

                                                                        
                          
 
                                            
 
                                  
 

                                        
                 
















                                                                               
                                  
         


                         
 
                             





                                                                    

                                                                     
 





















                                                            
 
                                                
 







                                                       
 
                                            
 
                                       
 

                                                          
 

                                
 
                                    
 
                         
 
                                          

                                                                                   

                                    
         
 
                           



                                          
                                                                  
                                                      



                                                               
                                                                 





                                                               

         
                                       
                                    


                     
                                 
 

                           
                          
 

                                                   
                                          
                                                    
         
 
 
/* divelist.c */
/* core logic for the dive list -
 * accessed through the following interfaces:
 *
 * dive_trip_t *dive_trip_list;
 * unsigned int amount_selected;
 * void dump_selection(void)
 * dive_trip_t *find_trip_by_idx(int idx)
 * int trip_has_selected_dives(dive_trip_t *trip)
 * void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
 * int total_weight(struct dive *dive)
 * int get_divenr(struct dive *dive)
 * double init_decompression(struct dive *dive)
 * void update_cylinder_related_info(struct dive *dive)
 * void get_location(struct dive *dive, char **str)
 * void get_cylinder(struct dive *dive, char **str)
 * void get_suit(struct dive *dive, char **str)
 * void dump_trip_list(void)
 * dive_trip_t *find_matching_trip(timestamp_t when)
 * void insert_trip(dive_trip_t **dive_trip_p)
 * void remove_dive_from_trip(struct dive *dive)
 * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
 * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive)
 * void autogroup_dives(void)
 * void clear_trip_indexes(void)
 * void delete_single_dive(int idx)
 * void add_single_dive(int idx, struct dive *dive)
 * void merge_dive_index(int i, struct dive *a)
 * void select_dive(int idx)
 * void deselect_dive(int idx)
 * void mark_divelist_changed(int changed)
 * int unsaved_changes()
 * void remove_autogen_trips()
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <glib/gi18n.h>
#include <assert.h>
#include <zip.h>
#include <libxslt/transform.h>

#include "dive.h"
#include "divelist.h"
#include "display.h"
#include "webservice.h"

static short dive_list_changed = FALSE;

dive_trip_t *dive_trip_list;

unsigned int amount_selected;

#if DEBUG_SELECTION_TRACKING
void dump_selection(void)
{
	int i;
	struct dive *dive;

	printf("currently selected are %u dives:", amount_selected);
	for_each_dive(i, dive) {
		if (dive->selected)
			printf(" %d", i);
	}
	printf("\n");
}
#endif

dive_trip_t *find_trip_by_idx(int idx)
{
	dive_trip_t *trip = dive_trip_list;

	if (idx >= 0)
		return NULL;
	idx = -idx;
	while (trip) {
		if (trip->index == idx)
			return trip;
		trip = trip->next;
	}
	return NULL;
}

int dive_nr_sort(int idx_a, int idx_b, timestamp_t when_a, timestamp_t when_b)
{
	struct dive *a, *b;
	dive_trip_t *tripa = NULL, *tripb = NULL;

	if (idx_a < 0) {
		a = NULL;
		tripa = find_trip_by_idx(idx_a);
	} else {
		a = get_dive(idx_a);
		if (a)
			tripa = a->divetrip;
	}

	if (idx_b < 0) {
		b = NULL;
		tripb = find_trip_by_idx(idx_b);
	} else {
		b = get_dive(idx_b);
		if (b)
			tripb = b->divetrip;
	}

	/*
	 * Compare dive dates within the same trip (or when there
	 * are no trips involved at all). But if we have two
	 * different trips use the trip dates for comparison
	 */
	if (tripa != tripb) {
		if (tripa)
			when_a = tripa->when;
		if (tripb)
			when_b = tripb->when;
	}
	return when_a - when_b;
}

int trip_has_selected_dives(dive_trip_t *trip)
{
	struct dive *dive;
	for (dive = trip->dives; dive; dive = dive->next) {
		if (dive->selected)
			return 1;
	}
	return 0;
}

/* Get the values as we want to show them. Whole feet. But meters with one decimal for 
 * values less than 20m, without decimals for larger values */
void get_depth_values(int depth, int *depth_int, int *depth_decimal, int *show_decimal)
{
	int integer, frac;

	*show_decimal = 1;
	switch (prefs.units.length) {
	case METERS:
		/* To tenths of meters */
		depth = (depth + 49) / 100;
		integer = depth / 10;
		frac = depth % 10;
		if (integer < 20)
			break;
		if (frac >= 5)
			integer++;
		*show_decimal = 0;
		break;
	case FEET:
		integer = mm_to_feet(depth) + 0.5;
		*show_decimal = 0;
		break;
	default:
		/* can't happen */
		return;
	}
	*depth_int = integer;
	if (*show_decimal)
		*depth_decimal = frac;
}

/*
 * Get "maximal" dive gas for a dive.
 * Rules:
 *  - Trimix trumps nitrox (highest He wins, O2 breaks ties)
 *  - Nitrox trumps air (even if hypoxic)
 * These are the same rules as the inter-dive sorting rules.
 */
void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
{
	int i;
	int maxo2 = -1, maxhe = -1, mino2 = 1000;


	for (i = 0; i < MAX_CYLINDERS; i++) {
		cylinder_t *cyl = dive->cylinder + i;
		struct gasmix *mix = &cyl->gasmix;
		int o2 = mix->o2.permille;
		int he = mix->he.permille;
		struct divecomputer *dc = &dive->dc;
		int used = 0;
		int first_gas_explicit = 0;

		while (dc){
			struct event *event = dc->events;
			while(event){
				if (event->value) {
					if (event->name && !strcmp(event->name, "gaschange")) {
						unsigned int event_he = event->value >> 16;
						unsigned int event_o2 = event->value & 0xffff;

						if (event->time.seconds < 30)
							first_gas_explicit = 1;
						if (is_air(o2, he)){
							if (is_air(event_o2 * 10, event_he * 10))
								used = 1;
						}
						else {
							if (he == event_he*10 && o2 == event_o2*10)
								used = 1;
						}
					}
				}
				event = event->next;
			}
			dc = dc->next;
		}

		/* Unless explicity set, the first gas to use has index 0 */
		if (i == 0 && !first_gas_explicit)
			used = 1;

		if (!used)
			continue;

		if (cylinder_none(cyl))
			continue;
		if (!o2)
			o2 = O2_IN_AIR;
		if (o2 < mino2)
			mino2 = o2;
		if (he > maxhe)
			goto newmax;
		if (he < maxhe)
			continue;
		if (o2 <= maxo2)
			continue;
newmax:
		maxhe = he;
		maxo2 = o2;
	}
	/* All air? Show/sort as "air"/zero */
	if (!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2)
		maxo2 = mino2 = 0;
	*o2_p = maxo2;
	*he_p = maxhe;
	*o2low_p = mino2;
}

int total_weight(struct dive *dive)
{
	int i, total_grams = 0;

	if (dive)
		for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
			total_grams += dive->weightsystem[i].weight.grams;
	return total_grams;
}

static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time)
{
	int o2permille = dive->cylinder[0].gasmix.o2.permille;
	struct event *event = dc->events;

	if (!o2permille)
		o2permille = O2_IN_AIR;

	for (event = dc->events; event; event = event->next) {
		if (event->time.seconds > time.seconds)
			break;
		if (strcmp(event->name, "gaschange"))
			continue;
		o2permille = 10*(event->value & 0xffff);
	}
	return o2permille;
}

/* calculate OTU for a dive - this only takes the first divecomputer into account */
static int calculate_otu(struct dive *dive)
{
	int i;
	double otu = 0.0;
	struct divecomputer *dc = &dive->dc;

	for (i = 1; i < dc->samples; i++) {
		int t;
		int po2;
		struct sample *sample = dc->sample + i;
		struct sample *psample = sample - 1;
		t = sample->time.seconds - psample->time.seconds;
		if (sample->po2) {
			po2 = sample->po2;
		} else {
			int o2 = active_o2(dive, dc, sample->time);
			po2 = o2 / 1000.0 * depth_to_mbar(sample->depth.mm, dive);
		}
		if (po2 >= 500)
			otu += pow((po2 - 500) / 1000.0, 0.83) * t / 30.0;
	}
	return otu + 0.5;
}
/* calculate CNS for a dive - this only takes the first divecomputer into account */
int const cns_table[][3] = {
/* po2, Maximum Single Exposure, Maximum 24 hour Exposure */
	{1600,  45 * 60, 150 * 60},
	{1500, 120 * 60, 180 * 60},
	{1400, 150 * 60, 180 * 60},
	{1300, 180 * 60, 210 * 60},
	{1200, 210 * 60, 240 * 60},
	{1100, 240 * 60, 270 * 60},
	{1000, 300 * 60, 300 * 60},
	{ 900, 360 * 60, 360 * 60},
	{ 800, 450 * 60, 450 * 60},
	{ 700, 570 * 60, 570 * 60},
	{ 600, 720 * 60, 720 * 60}
};

/* this only gets called if dive->maxcns == 0 which means we know that
 * none of the divecomputers has tracked any CNS for us
 * so we calculated it "by hand" */
static int calculate_cns(struct dive *dive)
{
	int i, j, divenr;
	double cns = 0.0;
	struct divecomputer *dc = &dive->dc;
	struct dive *prev_dive;
	timestamp_t endtime;

	/* shortcut */
	if (dive->cns)
		return dive->cns;
	/*
	 * Do we start with a cns loading from a privious dive?
	 * Check if we did a dive 12 hours prior, and what cns we had from that.
	 * Then apply ha 90min halftime to see whats left.
	 */
	divenr = get_divenr(dive);
	if (divenr) {
		prev_dive = get_dive(divenr -1 );
		endtime = prev_dive->when + prev_dive->duration.seconds;
		if (prev_dive && dive->when < (endtime + 3600 * 12)) {
			cns = calculate_cns(prev_dive);
			cns = cns * 1/pow(2, (dive->when - endtime) / (90.0 * 60.0));
		}
	}
	/* Caclulate the cns for each sample in this dive and sum them */
	for (i = 1; i < dc->samples; i++) {
		int t;
		int po2;
		struct sample *sample = dc->sample + i;
		struct sample *psample = sample - 1;
		t = sample->time.seconds - psample->time.seconds;
		if (sample->po2) {
			po2 = sample->po2;
		} else {
			int o2 = active_o2(dive, dc, sample->time);
			po2 = o2 / 1000.0 * depth_to_mbar(sample->depth.mm, dive);
		}
		/* Find what table-row we should calculate % for */
		for (j = 1; j < sizeof(cns_table)/(sizeof(int) * 3); j++)
			if (po2 > cns_table[j][0])
				break;
		j--;
		cns += ((double)t)/((double)cns_table[j][1]) * 100;
	}
	/* save calculated cns in dive struct */
	dive->cns = cns;
	return dive->cns;
}
/*
 * Return air usage (in liters).
 */
static double calculate_airuse(struct dive *dive)
{
	int airuse = 0;
	int i;

	for (i = 0; i < MAX_CYLINDERS; i++) {
		pressure_t start, end;
		cylinder_t *cyl = dive->cylinder + i;

		start = cyl->start.mbar ? cyl->start : cyl->sample_start;
		end = cyl->end.mbar ? cyl->end : cyl->sample_end;

		airuse += gas_volume(cyl, start) - gas_volume(cyl, end);
	}
	return airuse / 1000.0;
}

/* this only uses the first divecomputer to calculate the SAC rate */
static int calculate_sac(struct dive *dive)
{
	struct divecomputer *dc = &dive->dc;
	double airuse, pressure, sac;
	int duration, meandepth;

	airuse = calculate_airuse(dive);
	if (!airuse)
		return 0;

	duration = dc->duration.seconds;
	if (!duration)
		return 0;

	meandepth = dc->meandepth.mm;
	if (!meandepth)
		return 0;

	/* Mean pressure in ATM (SAC calculations are in atm*l/min) */
	pressure = (double) depth_to_mbar(meandepth, dive) / SURFACE_PRESSURE;
	sac = airuse / pressure * 60 / duration;

	/* milliliters per minute.. */
	return sac * 1000;
}

/* for now we do this based on the first divecomputer */
static void add_dive_to_deco(struct dive *dive)
{
	struct divecomputer *dc = &dive->dc;
	int i;

	if (!dc)
		return;
	for (i = 1; i < dc->samples; i++) {
		struct sample *psample = dc->sample + i - 1;
		struct sample *sample = dc->sample + i;
		int t0 = psample->time.seconds;
		int t1 = sample->time.seconds;
		int j;

		for (j = t0; j < t1; j++) {
			int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0);
			(void) add_segment(depth_to_mbar(depth, dive) / 1000.0,
					   &dive->cylinder[sample->sensor].gasmix, 1, sample->po2, dive);
		}
	}
}

int get_divenr(struct dive *dive)
{
	int divenr = -1;
	while (++divenr < dive_table.nr && get_dive(divenr) != dive)
		;
	return divenr;
}

static struct gasmix air = { .o2.permille = O2_IN_AIR };

/* take into account previous dives until there is a 48h gap between dives */
double init_decompression(struct dive *dive)
{
	int i, divenr = -1;
	unsigned int surface_time;
	timestamp_t when, lasttime = 0;
	gboolean deco_init = FALSE;
	double tissue_tolerance, surface_pressure;

	if (!dive)
		return 0.0;
	divenr = get_divenr(dive);
	when = dive->when;
	i = divenr;
	while (i && --i) {
		struct dive* pdive = get_dive(i);
		/* we don't want to mix dives from different trips as we keep looking
		 * for how far back we need to go */
		if (dive->divetrip && pdive->divetrip != dive->divetrip)
			continue;
		if (!pdive || pdive->when > when || pdive->when + pdive->duration.seconds + 48 * 60 * 60 < when)
			break;
		when = pdive->when;
		lasttime = when + pdive->duration.seconds;
	}
	while (++i < divenr) {
		struct dive* pdive = get_dive(i);
		/* again skip dives from different trips */
		if (dive->divetrip && dive->divetrip != pdive->divetrip)
			continue;
		surface_pressure = get_surface_pressure_in_mbar(pdive, TRUE) / 1000.0;
		if (!deco_init) {
			clear_deco(surface_pressure);
			deco_init = TRUE;
#if DECO_CALC_DEBUG & 2
			dump_tissues();
#endif
		}
		add_dive_to_deco(pdive);
#if DECO_CALC_DEBUG & 2
		printf("added dive #%d\n", pdive->number);
		dump_tissues();
#endif
		if (pdive->when > lasttime) {
			surface_time = pdive->when - lasttime;
			lasttime = pdive->when + pdive->duration.seconds;
			tissue_tolerance = add_segment(surface_pressure, &air, surface_time, 0, dive);
#if DECO_CALC_DEBUG & 2
			printf("after surface intervall of %d:%02u\n", FRACTION(surface_time,60));
			dump_tissues();
#endif
		}
	}
	/* add the final surface time */
	if (lasttime && dive->when > lasttime) {
		surface_time = dive->when - lasttime;
		surface_pressure = get_surface_pressure_in_mbar(dive, TRUE) / 1000.0;
		tissue_tolerance = add_segment(surface_pressure, &air, surface_time, 0, dive);
#if DECO_CALC_DEBUG & 2
		printf("after surface intervall of %d:%02u\n", FRACTION(surface_time,60));
		dump_tissues();
#endif
	}
	if (!deco_init) {
		double surface_pressure = get_surface_pressure_in_mbar(dive, TRUE) / 1000.0;
		clear_deco(surface_pressure);
#if DECO_CALC_DEBUG & 2
		printf("no previous dive\n");
		dump_tissues();
#endif
	}
	return tissue_tolerance;
}

void update_cylinder_related_info(struct dive *dive)
{
	if (dive != NULL) {
		dive->sac = calculate_sac(dive);
		dive->otu = calculate_otu(dive);
		if (dive->maxcns == 0)
			dive->maxcns = calculate_cns(dive);
	}
}

static void get_string(char **str, const char *s)
{
	int len;
	char *n;

	if (!s)
		s = "";
	len = g_utf8_strlen(s, -1);
	if (len > 60)
		len = 60;
	n = malloc(len * sizeof(gunichar) + 1);
	g_utf8_strncpy(n, s, len);
	*str = n;
}

void get_location(struct dive *dive, char **str)
{
	get_string(str, dive->location);
}

void get_cylinder(struct dive *dive, char **str)
{
	get_string(str, dive->cylinder[0].type.description);
}

void get_suit(struct dive *dive, char **str)
{
	get_string(str, dive->suit);
}

#define MAX_DATE_STRING 256
/* caller needs to free the string */
char *get_dive_date_string(struct tm *tm) {
	char *buffer = malloc(MAX_DATE_STRING);
	if (buffer)
		snprintf(buffer, MAX_DATE_STRING,
			/*++GETTEXT 60 char buffer weekday, monthname, day of month, year, hour:min */
			_("%1$s, %2$s %3$d, %4$d %5$02d:%6$02d"),
			weekday(tm->tm_wday),
			monthname(tm->tm_mon),
			tm->tm_mday, tm->tm_year + 1900,
			tm->tm_hour, tm->tm_min);
	return buffer;
}

/* caller needs to free the string */
char *get_trip_date_string(struct tm *tm, int nr) {
	char *buffer = malloc(MAX_DATE_STRING);
	if (buffer)
		snprintf(buffer, MAX_DATE_STRING,
			/*++GETTEXT 60 char buffer weekday, monthname, day of month, year, nr dives */
			ngettext("Trip %1$s, %2$s %3$d, %4$d (%5$d dive)",
				"Trip %1$s, %2$s %3$d, %4$d (%5$d dives)", nr),
			weekday(tm->tm_wday),
			monthname(tm->tm_mon),
			tm->tm_mday, tm->tm_year + 1900,
			nr);
	return buffer;
}

/*
 * helper functions for dive_trip handling
 */

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

	for (trip = dive_trip_list; trip; trip = trip->next) {
		struct tm tm;
		utc_mkdate(trip->when, &tm);
		if (trip->when < last_time)
			printf("\n\ndive_trip_list 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, trip->location,
			tm.tm_year + 1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
			trip->nrdives, trip);
		last_time = trip->when;
	}
	printf("-----\n");
}
#endif

/* this finds the last trip that at or before the time given */
dive_trip_t *find_matching_trip(timestamp_t when)
{
	dive_trip_t *trip = dive_trip_list;

	if (!trip || trip->when > when) {
#ifdef DEBUG_TRIP
		printf("no matching trip\n");
#endif
		return NULL;
	}
	while (trip->next && trip->next->when <= when)
		trip = trip->next;
#ifdef DEBUG_TRIP
	{
		struct tm tm;
		utc_mkdate(trip->when, &tm);
		printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n",
			trip,
			tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
			tm.tm_hour, tm.tm_min, tm.tm_sec);
	}
#endif
	return trip;
}

/* insert the trip into the dive_trip_list - but ensure you don't have
 * two trips for the same date; but if you have, make sure you don't
 * keep the one with less information */
void insert_trip(dive_trip_t **dive_trip_p)
{
	dive_trip_t *dive_trip = *dive_trip_p;
	dive_trip_t **p = &dive_trip_list;
	dive_trip_t *trip;
	struct dive *divep;

	/* Walk the dive trip list looking for the right location.. */
	while ((trip = *p) != NULL && trip->when < dive_trip->when)
		p = &trip->next;

	if (trip && trip->when == dive_trip->when) {
		if (! trip->location)
			trip->location = dive_trip->location;
		if (! trip->notes)
			trip->notes = dive_trip->notes;
		divep = dive_trip->dives;
		while (divep) {
			add_dive_to_trip(divep, trip);
			divep = divep->next;
		}
		*dive_trip_p = trip;
	} else {
		dive_trip->next = trip;
		*p = dive_trip;
	}
#ifdef DEBUG_TRIP
	dump_trip_list();
#endif
}

static void delete_trip(dive_trip_t *trip)
{
	dive_trip_t **p, *tmp;

	assert(!trip->dives);

	/* Remove the trip from the list of trips */
	p = &dive_trip_list;
	while ((tmp = *p) != NULL) {
		if (tmp == trip) {
			*p = trip->next;
			break;
		}
		p = &tmp->next;
	}

	/* .. and free it */
	if (trip->location)
		free(trip->location);
	if (trip->notes)
		free(trip->notes);
	free(trip);
}

static void find_new_trip_start_time(dive_trip_t *trip)
{
	struct dive *dive = trip->dives;
	timestamp_t when = dive->when;

	while ((dive = dive->next) != NULL) {
		if (dive->when < when)
			when = dive->when;
	}
	trip->when = when;
}

void remove_dive_from_trip(struct dive *dive)
{
	struct dive *next, **pprev;
	dive_trip_t *trip = dive->divetrip;

	if (!trip)
		return;

	/* Remove the dive from the trip's list of dives */
	next = dive->next;
	pprev = dive->pprev;
	*pprev = next;
	if (next)
		next->pprev = pprev;

	dive->divetrip = NULL;
	dive->tripflag = NO_TRIP;
	assert(trip->nrdives > 0);
	if (!--trip->nrdives)
		delete_trip(trip);
	else if (trip->when == dive->when)
		find_new_trip_start_time(trip);
}

void add_dive_to_trip(struct dive *dive, dive_trip_t *trip)
{
	if (dive->divetrip == trip)
		return;
	assert(trip->when);
	remove_dive_from_trip(dive);
	trip->nrdives++;
	dive->divetrip = trip;
	dive->tripflag = ASSIGNED_TRIP;

	/* Add it to the trip's list of dives*/
	dive->next = trip->dives;
	if (dive->next)
		dive->next->pprev = &dive->next;
	trip->dives = dive;
	dive->pprev = &trip->dives;

	if (dive->when && trip->when > dive->when)
		trip->when = dive->when;
}

dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive)
{
	dive_trip_t *dive_trip = calloc(sizeof(dive_trip_t),1);
	dive_trip->when = dive->when;
	if (dive->location)
		dive_trip->location = strdup(dive->location);
	insert_trip(&dive_trip);

	dive->tripflag = IN_TRIP;
	add_dive_to_trip(dive, dive_trip);
	return dive_trip;
}

/*
 * Walk the dives from the oldest dive, and see if we can autogroup them
 */
void autogroup_dives(void)
{
	int i;
	struct dive *dive, *lastdive = NULL;

	for_each_dive(i, dive) {
		dive_trip_t *trip;

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

		if (!DIVE_NEEDS_TRIP(dive)) {
			lastdive = NULL;
			continue;
		}

		/* Do we have a trip we can combine this into? */
		if (lastdive && dive->when < lastdive->when + TRIP_THRESHOLD) {
			dive_trip_t *trip = lastdive->divetrip;
			add_dive_to_trip(dive, trip);
			if (dive->location && !trip->location)
				trip->location = strdup(dive->location);
			lastdive = dive;
			continue;
		}

		lastdive = dive;
		trip = create_and_hookup_trip_from_dive(dive);
		trip->autogen = 1;
	}

#ifdef DEBUG_TRIP
	dump_trip_list();
#endif
}

void clear_trip_indexes(void)
{
	dive_trip_t *trip;

	for (trip = dive_trip_list; trip != NULL; trip = trip->next)
		trip->index = 0;
}

/* this implements the mechanics of removing the dive from the table,
 * but doesn't deal with updating dive trips, etc */
void delete_single_dive(int idx)
{
	int i;
	struct dive *dive = get_dive(idx);
	if (!dive)
		return; /* this should never happen */
	remove_dive_from_trip(dive);
	for (i = idx; i < dive_table.nr - 1; i++)
		dive_table.dives[i] = dive_table.dives[i+1];
	dive_table.dives[--dive_table.nr] = NULL;
	if (dive->selected)
		amount_selected--;
	/* free all allocations */
	free(dive->dc.sample);
	if (dive->location)
		free((void *)dive->location);
	if (dive->notes)
		free((void *)dive->notes);
	if (dive->divemaster)
		free((void *)dive->divemaster);
	if (dive->buddy)
		free((void *)dive->buddy);
	if (dive->suit)
		free((void *)dive->suit);
	free(dive);
}

void add_single_dive(int idx, struct dive *dive)
{
	int i;
	dive_table.nr++;
	if (dive->selected)
		amount_selected++;
	for (i = idx; i < dive_table.nr ; i++) {
		struct dive *tmp = dive_table.dives[i];
		dive_table.dives[i] = dive;
		dive = tmp;
	}
}

void merge_dive_index(int i, struct dive *a)
{
	struct dive *b = get_dive(i+1);
	struct dive *res;

	res = merge_dives(a, b, b->when - a->when, FALSE);
	if (!res)
		return;

	add_single_dive(i, res);
	delete_single_dive(i+1);
	delete_single_dive(i+1);

	dive_list_update_dives();
	mark_divelist_changed(TRUE);
}

void select_dive(int idx)
{
	struct dive *dive = get_dive(idx);
	if (dive && !dive->selected) {
		/* never select an invalid dive that isn't displayed */
		if (dive->dive_tags & DTAG_INVALID && !prefs.display_invalid_dives)
			return;
		dive->selected = 1;
		amount_selected++;
		selected_dive = idx;
	}
}

void deselect_dive(int idx)
{
	struct dive *dive = get_dive(idx);
	if (dive && dive->selected) {
		dive->selected = 0;
		amount_selected--;
		if (selected_dive == idx && amount_selected > 0) {
			/* pick a different dive as selected */
			while (--selected_dive >= 0) {
				dive = get_dive(selected_dive);
				if (dive && dive->selected)
					return;
			}
			selected_dive = idx;
			while (++selected_dive < dive_table.nr) {
				dive = get_dive(selected_dive);
				if (dive && dive->selected)
					return;
			}
		}
		if (amount_selected == 0)
			selected_dive = -1;
	}
}

void mark_divelist_changed(int changed)
{
	dive_list_changed = changed;
}

int unsaved_changes()
{
	return dive_list_changed;
}

void remove_autogen_trips()
{
	int i;
	struct dive *dive;

	for_each_dive(i, dive) {
		dive_trip_t *trip = dive->divetrip;

		if (trip && trip->autogen)
			remove_dive_from_trip(dive);
	}
}