/* functions that support interpolation in latitude-longitude projections */

#include <stdlib.h>
#include <grass/gis.h>
#include "main.h"

/************************************************************************/
/*      This function initializes the search for nearest neighbors      */
/*      by locating the two data closest to the specified column in     */
/*      a linked list of row data                                       */

int first_west_LL (EW *ewptr, SHORT col)
{
    if (ewptr->start == NULL)           /* no data in this row */
        ewptr->walive = ewptr->ealive = FALSE;
    else if (ewptr->start == ewptr->start->prior) {	/* single datum */
	ewptr->west = ewptr->east = ewptr->start;
	ewptr->walive = FALSE;
	ewptr->ealive = TRUE;
	}
    else {	/* two or more data in this row */
        while (col > ewptr->start->x &&
	 ewptr->start->x < ewptr->start->next->x) /* start is west of col */
            ewptr->start = ewptr->start->next;
        ewptr->east = ewptr->start;
        ewptr->west = ewptr->start->prior;
	ewptr->walive = ewptr->ealive = TRUE;
        }    

	return 0;
}

 
double 
offset_distance_LL (SHORT offset)
{
    extern double	*lat_diff;
       
    return (*(lat_diff + abs ((int) offset)));
}


/************************************************************************/
/*      Return TRUE if search is exhausted both west and east in a row  */
 
int 
completed_row_LL (EW *ewptr)
{
        return (!ewptr->walive && !ewptr->ealive);
}


int find_neighbors_LL (
    EW *ewptr, NEIGHBOR *nbr_head, SHORT row, SHORT col,
    int npoints, SHORT *neighbors)
{
    MELEMENT    **Mptr;		/* double indirection !! */ 
    int 	westward = 1;       /* 1 if west of interpolation point */
    double      distance;
    short	*active;	/* TRUE if active search in this direction */

    active = &ewptr->walive;	/* TRUE if searching west in this row */
    Mptr = &ewptr->west;	/* process search west first, then east */
    do {
    	if (*active) {
	    distance = distance_LL (row, col, *Mptr);

            if (*neighbors < npoints) 
            	add_neighbor (Mptr, nbr_head, distance, ++(*neighbors));
            else if (!replace_neighbor (Mptr, nbr_head, distance)) 
            	*active = FALSE;       /* curtail search in this direction */
            
	    if (*active)
        	if (westward) 
		    extend_west (ewptr);
        	else
		    extend_east (ewptr);
            }
	
	active = &ewptr->ealive;
	Mptr = &ewptr->east;
	} while (westward--);	/* repeat loop for east and quit */

	return 0;
}


/************************************************************************/
/*      This function exhausts all possible nearest neighhbors          */
/*      within the row indexed by the ew search pointer                 */

int 
exhaust_search_LL (EW *ewptr, NEIGHBOR *nbr_head, SHORT row, SHORT col)
{
    double      distance;

    while (ewptr->walive) {             /* search active */
	distance = distance_LL (row, col, ewptr->west);

        if (!replace_neighbor (&ewptr->west, nbr_head, distance))
            break; /* curtail search in this direction */
        else
	    extend_west (ewptr);
        }

    while (ewptr->ealive) {		/* search active */               
	distance = distance_LL (row, col, ewptr->east);

        if (!replace_neighbor (&ewptr->east, nbr_head, distance))
            break; /* curtail search in this direction */
        else
	    extend_east (ewptr);
        }

	return 0;
}


int 
extend_west (EW *ewptr)
{
    if (ewptr->west->prior != ewptr->east)
        ewptr->west = ewptr->west->prior;
    else
        ewptr->walive = FALSE;

	return 0;
}


int 
extend_east (EW *ewptr)
{
    if (ewptr->east->next != ewptr->west)
	ewptr->east = ewptr->east->next;
    else
	ewptr->ealive = FALSE;

	return 0;
}


double 
distance_LL (SHORT row, SHORT col, MELEMENT *Mptr)
{
    extern double       *rowlook, *collook;

    /* use lookup tables to increase distance calculation efficiency */
    LL_set_geodesic_distance (rowlook, row, Mptr->y);
    return (LL_geodesic_distance (*(collook + abs ((int) (col - Mptr->x)))));
}


/************************************************************************/
/*      Lookup tables storing pre-processed latitude and longitude data */
/*      are created for later use in selecting nearest neighbors        */

int 
LL_lookup_tables (SHORT nrows, SHORT ncols)
{
    extern double               *rowlook, *collook, *lat_diff;
    extern struct Cell_head     window;
    double                      *nextrow, *nextcol, *next_diff,
				lon = 0.,
				lat = window.north - (0.5 * window.ns_res);
    SHORT                       i;

    nextrow = rowlook = (double *) G_calloc (nrows, sizeof (double));
    for (i = 0; i < nrows; i++, nextrow++, lat -= window.ns_res)
        *nextrow = LL_set_geodesic_distance_lat (lat);

    nextcol = collook = (double *) G_calloc (ncols, sizeof (double));
    for (i = 0; i < ncols; i++, nextcol++, lon += window.ew_res)
        *nextcol = set_sdlmr (lon);

    /* compute distance between latitudes at same longitude */
    next_diff = lat_diff = (double *) G_calloc (nrows, sizeof (double));
    for (i = 0; i < nrows; i++, next_diff++) {
	LL_set_geodesic_distance (rowlook, 0, i);
	*next_diff = LL_geodesic_distance (0.);
	}

	return 0;
}
