/* $Id: casu_getstds.c,v 1.7 2015/11/27 12:06:41 jim Exp $
 *
 * This file is part of the CASU Pipeline utilities
 * Copyright (C) 2015 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jim $
 * $Date: 2015/11/27 12:06:41 $
 * $Revision: 1.7 $
 * $Name:  $
 */

/* Includes */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500 /* posix 2001, mkstemp */
#endif

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>

#include <unistd.h>
#include <string.h>
#include <cpl.h>

#include <math.h>

#include "casu_mods.h"
#include "casu_utils.h"
#include "casu_wcsutils.h"

#define CACHEDIR ".catcache"
#define CACHEIND ".catcache/index"
#define SZBUF 1024

#define CDS   "cdsarc.u-strasbg.fr"
#define BUFMAX 32768
#define PORT 80

static cpl_table *casu_2mass_extract(char *path, float ramin, float ramax,
                                     float decmin, float decmax);
static cpl_table *casu_ppmxl_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax);
static cpl_table *casu_apass_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax);
static cpl_table *casu_local_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax);
static cpl_table *check_cache(char *catname, float ra1_im, float ra2_im, 
                              float dec1_im, float dec2_im, char *cacheloc);
static void addto_cache(cpl_table *stds, char *catname, float ramin, 
                        float ramax, float decmin, float decmax, 
                        char *cacheloc);
static char *form_request(float ra, float dec, float dra, float ddec, 
                          float equinox, const char *catid);
static char *url_encode(char *instring);
static int connect_site(const char *site, int *sock);
static int send_request(int sock, char *req_string);
static int get_response(int soc, cpl_table **outtab);

/**@{*/

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Get a table of standard stars that appear on an image from
    a catalogue.

    \par Name:
        casu_getstds
    \par Purpose:
        Get a table of standard stars that appear on an image from a
        catalogue.
    \par Description:
        The propertylist of an image is given. The WCS from that propertylist
        is extracted and the region in equatorial coordinates covered by
        the image is calculated. From that information standard stars are
        extracted from the requested source catalogues which reside either in
        a location specified by a given path or at the CDS. For each object 
        in the table an x,y pixel coordinate pair is calculated from the 
        WCS and added to the table.
    \par Language:
        C
    \param plist 
        The input propertylist for the image in question
    \param cache 
        If set, then we can cache results which should result in
        faster access for several observations of the same region
    \param path 
        The full path to the catalogue FITS files if they are stored locally
    \param catname
        The name of the input catalogue. This is just used for labelling
        purposes
    \param cdssearch
        If this is not zero, then it refers to a catalogue to be searched
        at the CDS (this takes precedence over local catalogues if both
        cdssearch and a catpath are specfied). Current values are:
        - 1: 2MASS PSC
        - 2: USNOB
        - 3: PPMXL
        - 4: Landolt
        - 5: WISE
        - 6: APASS
    \param cacheloc
        A directory where we want to write the cache directory
    \param stds 
        The output table of standards
    \param status 
        An input/output status that is the same as the returned values below.
    \retval CASU_OK 
        if everything is ok
    \retval CASU_WARN 
        if the output standards table has no rows in it
    \retval CASU_FATAL 
        if a failure occurs in either accessing or extracting the 
        standard data.
    \par QC headers:
        None
    \par DRS headers:
        None
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_getstds(cpl_propertylist *plist, int cache, char *path,
                        char *catname, int cdssearch, char *cacheloc,
                        cpl_table **stds, int *status) {
    const char *fctid = "casu_getstds";
    double xx1,xx2,yy1,yy2,r,d,xx,yy,*rad,*decd,dra,ddec;
    float ramin,ramax,decmin,decmax,*ra,*dec,*x,*y,ramid,decmid;
    cpl_wcs *wcs;
    int n,i,sock;
    cpl_propertylist *p;
    char *req_string,catname2[64],*cdscatname,*cdscatid,*path2;

    /* Inherited status */

    *stds = NULL;
    if (*status != CASU_OK)
        return(*status);
    if (plist == NULL)
        FATAL_ERROR

    /* Get the coverage of the input WCS */

    (void)casu_coverage(plist,0,&xx1,&xx2,&yy1,&yy2,status);
    ramin = (float)xx1;
    ramax = (float)xx2;
    decmin = (float)yy1;
    decmax = (float)yy2;
    dra = ramax - ramin;
    ddec = decmax - decmin;
    ramid = 0.5*(ramax+ramin);
    decmid = 0.5*(decmax+decmin);

    /* If using the cache, then check if for a previously used table */

    cdscatid = NULL;
    if (cdssearch > 0) {
        if (casu_getstds_cdslist(cdssearch,&cdscatname,&cdscatid,status) != 
            CASU_OK) 
            return(*status);
        (void)strcpy(catname2,cdscatname);
        freespace(cdscatname);
    } else {
        (void)strcpy(catname2,catname);
    }
    *stds = NULL;
    if (cache)
        *stds = check_cache(catname2,ramin,ramax,decmin,decmax,cacheloc);

    /* If there was nothing in the cache (or you didn't use it) then search
       the standard catalogues */

    if (*stds == NULL) {        

        /* Read the standards from local catalogues */

        if (cdssearch == 0) {
            path2 = cpl_strdup(path);
            if (! strcmp(catname2,"casu_2mass")) {
                *stds = casu_2mass_extract(dirname(path2),ramin,ramax,decmin,
                                           decmax);
            } else if (! strcmp(catname2,"casu_ppmxl")) {
                *stds = casu_ppmxl_extract(dirname(path2),ramin,ramax,decmin,
                                           decmax);
            } else if (! strcmp(catname2,"casu_apass")) {
                *stds = casu_apass_extract(dirname(path2),ramin,ramax,decmin,
                                           decmax);
            } else {
                *stds = casu_local_extract(path2,ramin,ramax,decmin,decmax);
            }
            cpl_free(path2);
            if (*stds == NULL) {
                cpl_msg_error(fctid,"Unable to extract data in %s",path);
                FATAL_ERROR
            }

        /* Read the standards from CDS */

        } else {
            req_string = form_request(ramid,decmid,dra,ddec,2000.0,
                                      cdscatid);
            freespace(cdscatid);

            /* Now make the connection to the site */

            if (connect_site(CDS,&sock) != CASU_OK) {
                cpl_msg_warning(fctid,"Couldn't connect to CDS");
                FATAL_ERROR
            }
            
            /* Send the request to the site */

            if (send_request(sock,req_string) != CASU_OK) {
                cpl_msg_warning(fctid,"Couldn't send request to site CDS");
                FATAL_ERROR
            }

            /* Get the response from the the server and write it to the 
               output table */

            if (get_response(sock,stds) != CASU_OK) {       
                cpl_msg_warning(fctid,"Error receiving info from site CDS");
                FATAL_ERROR
            }

            /* Close up the connection */
            
            close(sock);

            /* Do some modifications to the table */

            cpl_table_name_column(*stds,"RAJ2000","RA");
            cpl_table_name_column(*stds,"DEJ2000","Dec");
        }
            
        /* Add this table to the cache if you want to */

        if (cache)
            addto_cache(*stds,catname2,ramin,ramax,decmin,decmax,cacheloc);
    } else {
        freespace(cdscatid);
    }

    /* If there are no rows in the table, this may be a cause for concern.
       So add the columns into the table and then set a warning return 
       status */

    n = (int)cpl_table_get_nrow(*stds);
    if (n == 0) {
        cpl_table_new_column(*stds,"xpredict",CPL_TYPE_FLOAT);
        cpl_table_new_column(*stds,"ypredict",CPL_TYPE_FLOAT);
        WARN_RETURN
    }

    /* Now fill the coordinates in */

    wcs = cpl_wcs_new_from_propertylist((const cpl_propertylist *)plist);
    if (cpl_table_get_column_type(*stds,"RA") == CPL_TYPE_FLOAT) {
        ra = cpl_table_get_data_float(*stds,"RA");
        dec = cpl_table_get_data_float(*stds,"Dec");
        x = cpl_malloc(n*sizeof(*x));
        y = cpl_malloc(n*sizeof(*y));
        for (i = 0; i < n; i++) {
            r = (double)ra[i];
            d = (double)dec[i];
            casu_radectoxy(wcs,r,d,&xx,&yy);
            x[i] = (float)xx;
            y[i] = (float)yy;
        }
    } else {
        rad = cpl_table_get_data_double(*stds,"RA");
        decd = cpl_table_get_data_double(*stds,"Dec");
        x = cpl_malloc(n*sizeof(*x));
        y = cpl_malloc(n*sizeof(*y));
        for (i = 0; i < n; i++) {
            r = rad[i];
            d = decd[i];
            casu_radectoxy(wcs,r,d,&xx,&yy);
            x[i] = (float)xx;
            y[i] = (float)yy;
        }
    }
    cpl_wcs_delete(wcs);
    
    /* Add the predicted x,y coordinates columns to the table */

    cpl_table_wrap_float(*stds,x,"xpredict");
    cpl_table_set_column_unit(*stds,"xpredict","pixels");
    cpl_table_wrap_float(*stds,y,"ypredict");
    cpl_table_set_column_unit(*stds,"ypredict","pixels");

    /* Finally sort this by ypredict */

    p = cpl_propertylist_new();
    cpl_propertylist_append_bool(p,"ypredict",0);
    cpl_table_sort(*stds,p);
    cpl_propertylist_delete(p);

    /* Get out of here */

    GOOD_STATUS
}

/*---------------------------------------------------------------------------*/
/**
    \ingroup casu_modules
    \brief Get the designations of CDS catalogues served.


    \par Name:
        casu_getstds_cdslist
    \par Purpose:
        Get the designations of CDS catalogues served through the 
        casu_getstds interface
    \par Description:
        An integer number representing a choice of CDS catalogue is
        given. Currently these are (1) 2MASS PSC, (2) USNOB, (3) PPMXL,
        (4) Landolt, (5) WISE. Output is the CDS designation for the
        catalogue and an ID that can be used to match up with the
        photometric colour equation calibration file. 
    \par Language:
        C
    \param cdschoice
        The input integer for the catalogue choice
    \param cdscatname
        The output CDS designation for the catalogue
    \param cdscatid
        The output label for the catalogue
    \param status 
        An input/output status that is the same as the returned values below.
    \retval CASU_OK 
        if everything is ok
    \retval CASU_FATAL 
        if the number is out of the current range of options.
    \par QC headers:
        None
    \par DRS headers:
        None
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

extern int casu_getstds_cdslist(int cdschoice, char **cdscatname,
                                char **cdscatid, int *status) {
    const char *cdscats[7] = {"","II/246","I/284","I/317","II/183A","II/311",
                              "II/336"};
    const char *cdscatnames[7] = {"","2mass","usnob","ppmxl","landolt","wise",
                                  "apass"};
    const int maxcds = 6;
    const char *fctid = "casu_getstds_cdslist";

    if (*status != CASU_OK) 
        return(*status);

    /* Check that the choice is within the bounds of possibility */

    *cdscatname = NULL;
    *cdscatid = NULL;
    if (cdschoice < 0 || cdschoice > maxcds) {
        cpl_msg_error(fctid,"CDS catalogue choice must be >= 0 && <= %d",
            maxcds);
        return(CASU_FATAL);
    }
    *cdscatname = cpl_strdup(cdscatnames[cdschoice]);
    *cdscatid = cpl_strdup(cdscats[cdschoice]);
    GOOD_STATUS
}
        

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_2mass_extract
    \par Purpose:
        Extract standards from the local 2mass catalogue fits tables
    \par Description:
        The local FITS tables containing the 2mass psc catalogue are searched
        to find all of the objects within an input equatorial area. Deals
        with the sigularity at the equinox, but not at the poles.
    \par Language:
        C
    \param path 
       The full path to the catalogue FITS files and index.
    \param ramin1 
       The minimum RA, this can be negative in the case the area wraps around 
       the equinox.
    \param ramax1 
        The maximum RA
    \param decmin 
        The minimum Declination
    \param decmax 
        The maximum Declination
    \return   
        A table structure with the extracted catalogue objects
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *casu_2mass_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax) {
    cpl_table *t,*s,*o;
    int i,nrows,start,finish,first_index,last_index,irow,init,j;
    int first_index_ra,last_index_ra,wrap,iwrap;
    float dectest,ratest,ramin,ramax;
    char fullname[SZBUF];
    cpl_array *a;
    const char *deccol = "Dec";
    cpl_propertylist *p;

    /* Create an output table */

    o = cpl_table_new(0);
    init = 1;

    /* Create a cpl array */

    a = cpl_array_wrap_string((char **)&deccol,1);

    /* Is there a wrap around problem? */

    wrap = (ramin1 < 0.0 && ramax1 > 0.0) ? 2 : 1;

    /* Loop for each query. If there is a wrap around problem then we need 2
       queries. If not, then we only need 1 */

    for (iwrap = 0; iwrap < wrap; iwrap++) {
        if (wrap == 2) {
            if (iwrap == 0) {
                ramin = ramin1 + 360.0;
                ramax = 360.0;
            } else {
                ramin = 0.000001;
                ramax = ramax1;
            }
        } else {
            ramin = ramin1;
            ramax = ramax1;
        }

        /* Find out where in the index to look */

        first_index_ra = (int)ramin;
        last_index_ra = min((int)ramax,359);
        
        /* Look at the min and max RA and decide which files need to be 
           opened. */

        for (i = first_index_ra; i <= last_index_ra; i++) {

            /* Ok, we've found one that needs opening. Read the file with 
               the relevant CPL call */

            (void)snprintf(fullname,SZBUF,"%s/npsc%03d.fits",path,i);

            /* Read the propertylist so that you know how many rows there
               are in the table */

            p = cpl_propertylist_load(fullname,1);
            if (p == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            nrows = cpl_propertylist_get_int(p,"NAXIS2");
            cpl_propertylist_delete(p);

            /* Load various rows until you find the Dec range that you 
               have specified. First the minimum Dec */

            start = 0;
            finish = nrows;
            first_index = nrows/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)first_index,
                                          1);
                dectest = cpl_table_get_double(t,"Dec",0,NULL);
                cpl_table_delete(t);
                if (dectest < decmin) {
                    start = first_index;
                    first_index = (first_index + finish)/2;
                } else {
                    finish = first_index;
                    first_index = (first_index + start)/2;
                }
            }

            /* Load various rows until you find the Dec range that you 
               have specified. Now the maximum Dec */

            start = first_index;
            finish = nrows;
            last_index = start + (finish - start)/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)last_index,
                                          1);
                dectest = cpl_table_get_double(t,"Dec",0,NULL);
                cpl_table_delete(t);
                if (dectest < decmax) {
                    start = last_index;
                    last_index = (last_index + finish)/2;
                } else {
                    finish = last_index;
                    last_index = (last_index + start)/2;
                }
            }    
            if (last_index < first_index)
                last_index = first_index;

            /* Ok now now load all the rows in the relevant dec limits */

            nrows = last_index - first_index + 1;
            if ((t = cpl_table_load_window(fullname,1,1,NULL,
                                           (cpl_size)first_index,
                                           (cpl_size)nrows)) == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            cpl_table_unselect_all(t);

            /* Right, we now know what range of rows to search. Go through 
               these and pick the ones that are in the correct range of RA.
               If a row qualifies, then 'select' it. */

            for (j = 0; j < nrows; j++) {
                ratest = cpl_table_get_double(t,"RA",(cpl_size)j,NULL);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_table_delete(t);
                    cpl_array_unwrap(a);
                    freetable(o);
                    return(NULL);
                }
                if (ratest >= ramin && ratest <= ramax)
                    cpl_table_select_row(t,(cpl_size)j);
            }

            /* Extract the rows that have been selected now and append them
               onto the output table */

            s = cpl_table_extract_selected(t);
            if (init == 1) {
                cpl_table_copy_structure(o,t);
                init = 0;
            }
            irow = (int)cpl_table_get_nrow(o) + 1;
            cpl_table_insert(o,s,(cpl_size)irow);

            /* Tidy up */

            cpl_table_delete(t);
            cpl_table_delete(s);
        }
    }

    /* Ok, now just return the table and get out of here */

    cpl_array_unwrap(a);
    return(o);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_ppmxl_extract
    \par Purpose:
        Extract standards from the local ppmxl catalogue fits tables
    \par Description:
        The FITS tables containing the ppmxl catalogue are searched
        to find all of the objects within an input equatorial area. Deals
        with the sigularity at the equinox, but not at the poles.
    \par Language:
        C
    \param path 
       The full path to the catalogue FITS files and index.
    \param ramin1 
       The minimum RA, this can be negative in the case the area wraps around 
       the equinox.
    \param ramax1 
        The maximum RA
    \param decmin 
        The minimum Declination
    \param decmax 
        The maximum Declination
    \return   
        A table structure with the extracted catalogue objects
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *casu_ppmxl_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax) {
    cpl_table *t,*s,*o;
    int i,nrows,start,finish,first_index,last_index,irow,init,j;
    int first_index_ra,last_index_ra,wrap,iwrap;
    float dectest,ratest,ramin,ramax;
    char fullname[SZBUF];
    cpl_array *a;
    const char *deccol = "Dec";
    cpl_propertylist *p;

    /* Create an output table */

    o = cpl_table_new(0);
    init = 1;

    /* Create a cpl array */

    a = cpl_array_wrap_string((char **)&deccol,1);

    /* Is there a wrap around problem? */

    wrap = (ramin1 < 0.0 && ramax1 > 0.0) ? 2 : 1;

    /* Loop for each query. If there is a wrap around problem then we need 2
       queries. If not, then we only need 1 */

    for (iwrap = 0; iwrap < wrap; iwrap++) {
        if (wrap == 2) {
            if (iwrap == 0) {
                ramin = ramin1 + 360.0;
                ramax = 360.0;
            } else {
                ramin = 0.000001;
                ramax = ramax1;
            }
        } else {
            ramin = ramin1;
            ramax = ramax1;
        }

        /* Find out where in the index to look */

        first_index_ra = (int)ramin;
        last_index_ra = min((int)ramax,359);
        
        /* Look at the min and max RA and decide which files need to be 
           opened. */

        for (i = first_index_ra; i <= last_index_ra; i++) {

            /* Ok, we've found one that needs opening. Read the file with 
               the relevant CPL call */

            (void)snprintf(fullname,SZBUF,"%s/nppmxl%03d.fits",path,i);

            /* Read the propertylist so that you know how many rows there
               are in the table */

            p = cpl_propertylist_load(fullname,1);
            if (p == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            nrows = cpl_propertylist_get_int(p,"NAXIS2");
            cpl_propertylist_delete(p);

            /* Load various rows until you find the Dec range that you 
               have specified. First the minimum Dec */

            start = 0;
            finish = nrows;
            first_index = nrows/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)first_index,
                                          1);
                dectest = (float)cpl_table_get_double(t,"Dec",0,NULL);
                cpl_table_delete(t);
                if (dectest < decmin) {
                    start = first_index;
                    first_index = (first_index + finish)/2;
                } else {
                    finish = first_index;
                    first_index = (first_index + start)/2;
                }
            }

            /* Load various rows until you find the Dec range that you 
               have specified. Now the maximum Dec */

            start = first_index;
            finish = nrows;
            last_index = start + (finish - start)/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)last_index,
                                          1);
                dectest = (float)cpl_table_get_double(t,"Dec",0,NULL);
                cpl_table_delete(t);
                if (dectest < decmax) {
                    start = last_index;
                    last_index = (last_index + finish)/2;
                } else {
                    finish = last_index;
                    last_index = (last_index + start)/2;
                }
            }    
            if (last_index < first_index)
                last_index = first_index;

            /* Ok now now load all the rows in the relevant dec limits */

            nrows = last_index - first_index + 1;
            if ((t = cpl_table_load_window(fullname,1,1,NULL,
                                           (cpl_size)first_index,
                                           (cpl_size)nrows)) == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            cpl_table_unselect_all(t);

            /* Right, we now know what range of rows to search. Go through 
               these and pick the ones that are in the correct range of RA.
               If a row qualifies, then 'select' it. */

            for (j = 0; j < nrows; j++) {
                ratest = (float)cpl_table_get_double(t,"RA",(cpl_size)j,NULL);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_table_delete(t);
                    cpl_array_unwrap(a);
                    freetable(o);
                    return(NULL);
                }
                if (ratest >= ramin && ratest <= ramax)
                    cpl_table_select_row(t,(cpl_size)j);
            }

            /* Extract the rows that have been selected now and append them
               onto the output table */

            s = cpl_table_extract_selected(t);
            if (init == 1) {
                cpl_table_copy_structure(o,t);
                init = 0;
            }
            irow = (int)cpl_table_get_nrow(o) + 1;
            cpl_table_insert(o,s,(cpl_size)irow);

            /* Tidy up */

            cpl_table_delete(t);
            cpl_table_delete(s);
        }
    }

    /* Ok, now just return the table and get out of here */

    cpl_array_unwrap(a);
    return(o);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_apass_extract
    \par Purpose:
        Extract standards from the local apass catalogue FITS tables
    \par Description:
        The FITS tables containing the apass catalogue are searched
        to find all of the objects within an input equatorial area. Deals
        with the sigularity at the equinox, but not at the poles.
    \par Language:
        C
    \param path 
       The full path to the catalogue FITS files and index.
    \param ramin1 
       The minimum RA, this can be negative in the case the area wraps around 
       the equinox.
    \param ramax1 
        The maximum RA
    \param decmin 
        The minimum Declination
    \param decmax 
        The maximum Declination
    \return   
        A table structure with the extracted catalogue objects
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *casu_apass_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax) {
    cpl_table *t,*s,*o;
    int i,nrows,start,finish,first_index,last_index,irow,init,j;
    int first_index_ra,last_index_ra,wrap,iwrap;
    float dectest,ratest,ramin,ramax;
    char fullname[SZBUF];
    cpl_array *a;
    const char *racol = "RA";
    cpl_propertylist *p;

    /* Create an output table */

    o = cpl_table_new(0);
    init = 1;

    /* Create a cpl array */

    a = cpl_array_wrap_string((char **)&racol,1);

    /* Is there a wrap around problem? */

    wrap = (ramin1 < 0.0 && ramax1 > 0.0) ? 2 : 1;

    /* Loop for each query. If there is a wrap around problem then we need 2
       queries. If not, then we only need 1 */

    for (iwrap = 0; iwrap < wrap; iwrap++) {
        if (wrap == 2) {
            if (iwrap == 0) {
                ramin = ramin1 + 360.0;
                ramax = 360.0;
            } else {
                ramin = 0.000001;
                ramax = ramax1;
            }
        } else {
            ramin = ramin1;
            ramax = ramax1;
        }

        /* Find out where in the index to look */

        first_index_ra = (int)ramin;
        last_index_ra = min((int)ramax,359);
        
        /* Look at the min and max RA and decide which files need to be 
           opened. */

        for (i = first_index_ra; i <= last_index_ra; i++) {

            /* Ok, we've found one that needs opening. Read the file with 
               the relevant CPL call */

            (void)snprintf(fullname,SZBUF,"%s/casuapass_%03d.fits",path,i);

            /* Read the propertylist so that you know how many rows there
               are in the table */

            p = cpl_propertylist_load(fullname,1);
            if (p == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            nrows = cpl_propertylist_get_int(p,"NAXIS2");
            cpl_propertylist_delete(p);

            /* Load various rows until you find the RA range that you 
               have specified. First the minimum RA */

            start = 0;
            finish = nrows;
            first_index = nrows/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)first_index,
                                          1);
                ratest = (float)cpl_table_get_double(t,"RA",0,NULL);
                cpl_table_delete(t);
                if (ratest < ramin) {
                    start = first_index;
                    first_index = (first_index + finish)/2;
                } else {
                    finish = first_index;
                    first_index = (first_index + start)/2;
                }
            }

            /* Load various rows until you find the RA range that you 
               have specified. Now the maximum RA */

            start = first_index;
            finish = nrows;
            last_index = start + (finish - start)/2;
            while (finish - start >= 2) {
                t = cpl_table_load_window(fullname,1,1,a,(cpl_size)last_index,
                                          1);
                ratest = (float)cpl_table_get_double(t,"RA",0,NULL);
                cpl_table_delete(t);
                if (ratest < ramax) {
                    start = last_index;
                    last_index = (last_index + finish)/2;
                } else {
                    finish = last_index;
                    last_index = (last_index + start)/2;
                }
            }    
            if (last_index < first_index)
                last_index = first_index;

            /* Ok now now load all the rows in the relevant RA limits */

            nrows = last_index - first_index + 1;
            if ((t = cpl_table_load_window(fullname,1,1,NULL,
                                           (cpl_size)first_index,
                                           (cpl_size)nrows)) == NULL) {
                freetable(o);
                cpl_array_unwrap(a);
                return(NULL);
            }
            cpl_table_unselect_all(t);

            /* Right, we now know what range of rows to search. Go through 
               these and pick the ones that are in the correct range of RA.
               If a row qualifies, then 'select' it. */

            for (j = 0; j < nrows; j++) {
                dectest = (float)cpl_table_get_double(t,"Dec",(cpl_size)j,NULL);
                if (cpl_error_get_code() != CPL_ERROR_NONE) {
                    cpl_table_delete(t);
                    cpl_array_unwrap(a);
                    freetable(o);
                    return(NULL);
                }
                if (dectest >= decmin && dectest <= decmax)
                    cpl_table_select_row(t,(cpl_size)j);
            }

            /* Extract the rows that have been selected now and append them
               onto the output table */

            s = cpl_table_extract_selected(t);
            if (init == 1) {
                cpl_table_copy_structure(o,t);
                init = 0;
            }
            irow = (int)cpl_table_get_nrow(o) + 1;
            cpl_table_insert(o,s,(cpl_size)irow);

            /* Tidy up */

            cpl_table_delete(t);
            cpl_table_delete(s);
        }
    }

    /* Ok, now just return the table and get out of here */

    cpl_array_unwrap(a);
    return(o);
}
        
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        casu_local_extract
    \par Purpose:
        Extract standards from a local FITS table
    \par Description:
        A FITS tables containing standards set up by the user is read
        and stars within an input equatorial area are extracted.  Deals
        with the sigularity at the equinox, but not at the poles. The 
        input table should all be in the first extension.
    \par Language:
        C
    \param path 
       The full path to the catalogue FITS file.
    \param ramin1 
       The minimum RA, this can be negative in the case the area wraps around 
       the equinox.
    \param ramax1 
        The maximum RA
    \param decmin 
        The minimum Declination
    \param decmax 
        The maximum Declination
    \return   
        A table structure with the extracted catalogue objects
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *casu_local_extract(char *path, float ramin1, float ramax1, 
                                     float decmin, float decmax) {
    cpl_table *whole,*out,*out2;
    int wrap,iwrap,nrows;
    float ramin,ramax;
    cpl_propertylist *p;

    /* First read the input file. It's assumed this isn't so big that it's
       going to take very long. */

    whole = cpl_table_load((const char *)path,1,0);

    /* Is there a wrap around problem? */

    wrap = (ramin1 < 0.0 && ramax1 > 0.0) ? 2 : 1;

    /* Loop for each query. If there is a wrap around problem then we need 2
       queries. If not, then we only need 1 */

    out = NULL;
    for (iwrap = 0; iwrap < wrap; iwrap++) {
        if (wrap == 2) {
            if (iwrap == 0) {
                ramin = ramin1 + 360.0;
                ramax = 360.0;
            } else {
                ramin = 0.000001;
                ramax = ramax1;
            }
        } else {
            ramin = ramin1;
            ramax = ramax1;
        }
        cpl_table_unselect_all(whole);

        /* Select on RA and Dec */

        cpl_table_or_selected_double(whole,"RA",CPL_NOT_LESS_THAN,
                                     (double)ramin);
        cpl_table_and_selected_double(whole,"RA",CPL_NOT_GREATER_THAN,
                                      (double)ramax);
        cpl_table_and_selected_double(whole,"Dec",CPL_NOT_LESS_THAN,
                                      (double)decmin);
        cpl_table_and_selected_double(whole,"Dec",CPL_NOT_GREATER_THAN,
                                      (double)decmax);
        
        /* If this is the first time through, then create the output table */

        if (out == NULL) {
            out = cpl_table_extract_selected(whole);
        } else {
            out2 = cpl_table_extract_selected(whole);
            nrows = cpl_table_get_nrow(out);
            cpl_table_insert(out,out2,nrows+1);
            cpl_table_delete(out2);
        }
    }

    /* Sort the table by Dec and get out of here*/

    p = cpl_propertylist_new();
    cpl_propertylist_append_bool(p,"Dec",0);
    cpl_table_sort(out,p);
    cpl_propertylist_delete(p);
    return(out);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        check_cache
    \par Purpose:
        Read standard info from a local cache
    \par Description:
        Check the local cache to see if the region of interest has been
        extracted before. If it has, then pass back a table.
    \par Language:
        C
    \param catname
        The name of the catalogue being used
    \param ra1_im 
        The minimum RA, this can be negative in the case the area wraps around 
        the equinox.
    \param ra2_im 
        The maximum RA
    \param dec1_im 
        The minimum Declination
    \param dec2_im 
        The maximum Declination
    \param cacheloc
        The location of the cache directory
    \return
        A table structure with the extracted catalogue objects as chosen
        from the local cache
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static cpl_table *check_cache(char *catname, float ra1_im, float ra2_im, 
                              float dec1_im, float dec2_im, char *cacheloc) {
    int wrap1,wrap2;
    FILE *fd;
    char fname[BUFSIZ],catname2[SZBUF];
	char * cat_cache = NULL;
    float best,ra1_cat,ra2_cat,dec1_cat,dec2_cat,d1,d2,fra,fdec,ftot;
    cpl_table *out_cat;

    /* Open the index file.  NB the path and file name are hardcoded */

    (void)snprintf(fname,BUFSIZ,"%s/%s",cacheloc,CACHEIND);
    fd = fopen(fname,"r");
    if (fd == NULL)
        return(NULL);

    /* Check to see if there is wrap around in the coordinates */

    wrap1 = (ra1_im < 0.0 ? 1 : 0);

    /* Now see if you have any matching entries */

    best = 0.0;
    // Todo: the hard coded numbers should be changed to use BUFSIZ and SZBUF
    while (fscanf(fd,"%8191s %1023s %g %g %g %g",
                  fname,catname2,&ra1_cat,&ra2_cat,
                  &dec1_cat,&dec2_cat) != EOF) {
        wrap2 = (ra1_cat < 0.0 ? 1 : 0);
        if (wrap1 != wrap2)
            continue;
        if (strcmp(catname,catname2))
            continue;
        
        /* Check to see if there is at least some overlap */

        if (!(((ra1_im >= ra1_cat && ra1_im <= ra2_cat) ||
             (ra2_im >= ra1_cat && ra2_im <= ra2_cat)) &&
            ((dec1_im >= dec1_cat && dec1_im <= dec2_cat) ||
             (dec2_im >= dec1_cat && dec2_im <= dec2_cat))))
            continue;

        /* Work out exactly how much there is in each coordinate */

        d1 = max(0.0,ra1_cat-ra1_im);
        d2 = max(0.0,ra2_im-ra2_cat);
        fra = 1.0 - (d1 + d2)/(ra2_im - ra1_im);
        d1 = max(0.0,dec1_cat-dec1_im);
        d2 = max(0.0,dec2_im-dec2_cat);
        fdec = 1.0 - (d1 + d2)/(dec2_im - dec1_im);
        ftot = fra*fdec;

        /* Keep track of which is the best one */

        if (ftot > best) {
        	cpl_free(cat_cache); /* As we are in a loop free old allocation */
        	cat_cache = cpl_sprintf("%s/%s/%s",cacheloc,CACHEDIR,fname);
            best = ftot;
        }
    }
    fclose(fd);

    /* Return a bad status if there isn't sufficient overlap */

    if (best < 0.9)
        return(NULL);

    /* If there is enough overlap, then try and read the FITS table. If it
       reads successfully, then return the table pointer */

    out_cat = cpl_table_load(cat_cache,1,1);
    cpl_free(cat_cache);
    return(out_cat);
}
    
/*---------------------------------------------------------------------------*/
/**
    \par Name:
        addto_cache
    \par Purpose:
        Add a table to the local cache
    \par Description:
        Add an existing table to the local cache.
    \par Language:
        C
    \param stds 
        The table to be written.
    \param catname
        The name of the catalogue being used.
    \param ramin 
        The minimum RA, this can be negative in the case the area wraps 
        around the equinox.
    \param ramax 
        The maximum RA
    \param decmin 
        The minimum Declination
    \param decmax 
        The maximum Declination
    \param cacheloc
        The location of the cache
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static void addto_cache(cpl_table *stds, char *catname, float ramin, 
                        float ramax, float decmin, float decmax, 
                        char *cacheloc) {
    FILE *fd;
    char newname[SZBUF];
    int i;

    /* Check to see if the cache directory exists.  If it doesn't, then create
       it. */

    (void)snprintf(newname,SZBUF,"%s/%s",cacheloc,CACHEDIR);
    if (access(newname,0) != 0)
        mkdir(newname,0755);

    /* Open the index file with 'append' access */

    (void)snprintf(newname,SZBUF,"%s/%s",cacheloc,CACHEIND);
    fd = fopen(newname,"a");

    /* Check the files in the directory to get a number that isn't already
       being used */

    i = 0;
    while (i >= 0) {
        i++;
        snprintf(newname,SZBUF,"%s/%s/cch_%08d",cacheloc,CACHEDIR,i);
        if (access(newname,F_OK) != 0) 
            break;
    }

    /* Now write the current entry and make a copy of the file into the 
       directory */

    snprintf(newname,SZBUF,"%s/%s/cch_%08d",cacheloc,CACHEDIR,i);
    cpl_table_save(stds,NULL,NULL,newname,CPL_IO_DEFAULT);
    snprintf(newname,SZBUF,"cch_%08d",i);
    if (cpl_error_get_code() == CPL_ERROR_NONE)
        fprintf(fd, "%s %s %g %g %g %g\n",newname,catname,ramin-0.0005,
                ramax+0.0005,decmin-0.0005,decmax+0.0005);
    fclose(fd);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        get_response
    \par Purpose:
        Read the information from the socket and form a table from it
    \par Description:
        The response to a socket query is read. If it's all kosher, then
        it takes the information and forms a table structure which is
        returned.
    \par Language:
        C
    \param sock
        The input socket number
    \param outtab
        The output table object
    \retval CASU_OK
        if all went well
    \retval CASU_FATAL
        if the socket read failed or the table has no rows.
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static int get_response(int sock, cpl_table **outtab) {
    int nnewline,i,nv,irem,fd;
    long nrows;
    char buf[BUFMAX],outfile[32];
    const char *fctid = "get_response";

    /* Read from the socket until we find the double newline */

    nnewline = 0;
    while (1) {
        nv = recv(sock,buf,sizeof(buf),0);
        if (nv <= 0) {
            cpl_msg_warning(fctid,"Unable to find double newline");
            return(CASU_FATAL);
        }
        for (i = 0; i < nv; i++) {
            if (buf[i] == '\n') {
                nnewline++;
                if (nnewline == 2) 
                    break;
            } else {
                if (nnewline && buf[i] != '\r')
                    nnewline = 0;
            }
        }
        if (nnewline == 2) {
            irem = i + 1;
            break;
        }
    }

    /* Shift the buffer */

    for (i = irem; i < nv; i++)
        buf[i-irem] = buf[i];
    nv -= irem;

    /* Open the output file */

    (void)snprintf(outfile,32,"stdsXXXXXX");
    fd = mkstemp(outfile);

    /* Now write the data to the output file */

    while (1) {
        ssize_t written = write(fd,buf,nv);
        if(written != nv) {
            cpl_msg_info(cpl_func, "Not all bytes could be written - "
                            "check diskspace");
        }
        nv = recv(sock,buf,sizeof(buf),0);
        if (nv == 0) 
            break;
        else if (nv < 0) {
            cpl_msg_warning(fctid,"Read from socket failed");
            close(fd);
            remove(outfile);
            return(CASU_FATAL);
        }
    }

    /* Close the output file. Check and see if a table was created and whether
       or not it contains any rows... */

    close(fd);
    *outtab = cpl_table_load(outfile,1,0);
    if (cpl_error_get_code() != CPL_ERROR_NONE) {
        cpl_msg_warning(fctid,"No standards were found");
        cpl_error_reset();
        remove(outfile);
        *outtab = NULL;
        return(CASU_FATAL);
    } else {
        nrows = (int)cpl_table_get_nrow(*outtab);
        if (nrows <= 0) {
            cpl_msg_warning(fctid,"No standards table had no rows");
            remove(outfile);
            freetable(*outtab);
            return(CASU_FATAL);
        }
    }
    
    /* Get out of here */

    remove(outfile);
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        send_request
    \par Purpose:
        Send the request line through the socket to CDS
    \par Description:
        A given request string is sent through to the CDS through
        a previously opened socket.
    \par Language:
        C
    \param sock
        The input socket number
    \param req_string
        The request string
    \retval CASU_OK
        if all went well
    \retval CASU_FATAL
        if the send operation failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
        
static int send_request(int sock, char *req_string) {
    char buf[BUFMAX];
    const char *fctid = "send_request";

    /* Form the full request contents and send it. */

    sprintf(buf,"GET /viz-bin/asu-fits?%s HTTP/1.0\r\n\r\n",req_string);
    if (send(sock,buf,strlen(buf),0) < 0) {
        cpl_msg_warning(fctid,"Attempt to send message failed, error: %d\n",
                        errno);
        return(CASU_FATAL);
    }
    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        connect_site
    \par Purpose:
        Connect to the CDS and return the socket number
    \par Description:
        Given a web address for the CDS, make a connection to the
        site and return the socket number
    \par Language:
        C
    \param site
        The site URL
    \param sock
        The output socket number
    \retval CASU_OK
        if all went well
    \retval CASU_FATAL
        if the host information was unavailable or the socket connection
        failed
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/
        
static int connect_site(const char *site, int *sock) {
    struct hostent *hp;
    struct sockaddr_in ssin;
    const char *fctid = "connect_site";

    /* Get host pointer */

    hp = gethostbyname(site);
    if (hp == NULL) {
        cpl_msg_warning(fctid,"Unable to get host information for %s\n",site);
        return(CASU_FATAL);
    }
    ssin.sin_family = hp->h_addrtype;
    memcpy(&ssin.sin_addr,hp->h_addr_list[0],hp->h_length);
    ssin.sin_port = htons(PORT);

    /*  Create an IP-family socket on which to make the connection */

    *sock = socket(hp->h_addrtype,SOCK_STREAM,0);
    if (*sock < 0) {
        cpl_msg_warning(fctid,"Unable to create socket descriptor for %s\n",
                        site);
        return (CASU_FATAL);
    }

    /* Connect to that address...*/

    if (connect(*sock,(struct sockaddr*)&ssin,sizeof(ssin)) < 0) {
        cpl_msg_warning(fctid,"Unable to connect to site: %s\n",site);
        return(CASU_FATAL);
    }

    /* Otherwise send back a good status */

    return(CASU_OK);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        form_request
    \par Purpose:
        Form a request line for the CDS query
    \par Description:
        Given the equatorial coordinate limits and a catalogue id, 
        form the query line
    \par Language:
        C
    \param ra
        The central RA of the region (degrees)
    \param dec
        The central declination of the region (degrees)
    \param dra
        The size of the search box in RA (degrees)
    \param ddec
        The size of the search box in Dec (degrees)
    \param equinox
        The equinox of the coordinates
    \param catid
        The CDS catalogue ID
    \returns
        a string with the request line
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static char *form_request(float ra, float dec, float dra, float ddec,
                          float equinox, const char *catid) {
    static char buf[2*BUFSIZ];
    char buf2[BUFSIZ],equi[1];

    /* Format each of the fields and then add them to the buffer that will
       become the query string.  URLencode them before appending. First
       the coordinates */

    (void)snprintf(buf2,BUFSIZ,"-c=%8.3f %8.3f",ra,dec);
    (void)strncpy(buf,url_encode(buf2),BUFSIZ);

    /* Now the box size */

    (void)snprintf(buf2,BUFSIZ,"-c.bd=%g/%g",dra,ddec);
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);

    /* The catalogue name */

    (void)snprintf(buf2,BUFSIZ,"-source=%s",catid);
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);

    /* The equinox */

    equi[0] = (equinox == 1950.0 ? 'B' : 'J');
    (void)snprintf(buf2,BUFSIZ,"-c.eq=%c%g",equi[0],equinox);
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);

    /* Finally make sure that we don't truncate the result and that we
       sort by RA */

    (void)snprintf(buf2,BUFSIZ,"-out.max=unlimited");
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);
    (void)snprintf(buf2,BUFSIZ,"-sort=_RA*-c.eq");
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);
    (void)snprintf(buf2,BUFSIZ,"-out.add=_RA*-c.eq");
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);
    (void)snprintf(buf2,BUFSIZ,"-out.add=_DEC*-c.eq");
    (void)strncat(buf,"&",BUFSIZ);
    (void)strncat(buf,url_encode(buf2),BUFSIZ);

    /* Right, get out of here */

    return(buf);
}

/*---------------------------------------------------------------------------*/
/**
    \par Name:
        url_encode
    \par Purpose:
        URL encode a string
    \par Description:
        Replace characters in a string that need escaping if that
        string is to be used as a URL
    \par Language:
        C
    \param instring
        The input string
    \returns
        a string with the dubious characters escaped
    \author
        Jim Lewis, CASU
 */
/*---------------------------------------------------------------------------*/

static char *url_encode(char *instring) {
    static char buf[BUFSIZ];
    int i,j,k,len;

    /* First copy over everything before the equal sign */

    k = 0;
    do {
        buf[k] = instring[k];
    } while (instring[k++] != '=');
    len = strlen(instring);

    /* Copy over the string, substituting the things that we don't want in 
       a URL */

    j = k;
    for (i = k; i < len; i++) {
        assert(j < (int)sizeof(buf));
        if (instring[i] == ' ') 
            buf[j++] = '+';
        else if (isalnum((unsigned char)instring[i]))
            buf[j++] = instring[i];
        else {
            sprintf(buf+j,"%%%2x",(unsigned char)instring[i]);
            j += 3;
        }
    }
    buf[j] = '\0';
    return(buf);
}

/**@}*/


/* 

$Log: casu_getstds.c,v $
Revision 1.7  2015/11/27 12:06:41  jim
Detabbed a few things

Revision 1.6  2015/11/25 13:50:03  jim
cacheloc parameter added

Revision 1.5  2015/11/25 10:24:49  jim
Changed so that catpath always contains the full path of a file and the
file name is stripped off in the event that you're using local
2mass, apass or ppmxl catalogues

Revision 1.4  2015/10/04 18:43:14  jim
fixed small bug when no standards are returned

Revision 1.3  2015/08/07 13:06:54  jim
Fixed copyright to ESO

Revision 1.2  2015/08/06 05:34:02  jim
Fixes to get rid of compiler moans

Revision 1.1.1.1  2015/06/12 10:44:32  jim
Initial import

Revision 1.15  2015/05/20 11:58:00  jim
Changed URL for CDS

Revision 1.14  2015/05/14 11:46:33  jim
All RA and Dec columns are officially double

Revision 1.13  2015/05/13 11:42:27  jim
superficial changes

Revision 1.12  2015/03/12 09:16:51  jim
Modified to remove some compiler moans

Revision 1.11  2015/03/09 19:51:58  jim
Fixed undefined value

Revision 1.10  2015/03/03 10:48:11  jim
Fixed some memory leaks

Revision 1.9  2015/01/29 11:48:15  jim
modified comments

Revision 1.8  2015/01/09 12:13:15  jim
*** empty log message ***

Revision 1.7  2014/12/11 12:23:33  jim
new version

Revision 1.6  2014/04/23 05:39:41  jim
Now reads local standard tables

Revision 1.5  2014/04/09 11:26:36  jim
Order of catalogues for CDS was wrong

Revision 1.4  2014/04/09 11:08:21  jim
Get rid of a couple of compiler moans

Revision 1.3  2014/04/09 09:09:51  jim
Detabbed

Revision 1.2  2013/11/21 09:38:13  jim
detabbed

Revision 1.1.1.1  2013-08-27 12:07:48  jim
Imported


*/
