/******************************************************************************
 * $Id: pj_init.c,v 1.19 2007/11/26 00:21:59 fwarmerdam Exp $
 *
 * Project:  PROJ.4
 * Purpose:  Initialize projection object from string definition.  Includes
 *           pj_init(), pj_init_plus() and pj_free() function.
 * Author:   Gerald Evenden, Frank Warmerdam <warmerdam@pobox.com>
 *
 ******************************************************************************
 * Copyright (c) 1995, Gerald Evenden
 * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 *
 * $Log: pj_init.c,v $
 * Revision 1.19  2007/11/26 00:21:59  fwarmerdam
 * Modified PJ structure to hold a_orig, es_orig, ellipsoid definition before
 * adjustment for spherical projections.
 * Modified pj_datum_transform() to use the original ellipsoid parameters,
 * not the ones adjusted for spherical projections.
 * Modified pj_datum_transform() to not attempt any datum shift via
 * geocentric coordinates if the source *or* destination are raw ellipsoids
 * (ie. PJD_UNKNOWN).  All per PROJ bug #1602, GDAL bug #2025.
 *
 * Revision 1.18  2006/10/12 21:04:39  fwarmerdam
 * Added experimental +lon_wrap argument to set a "center point" for
 * longitude wrapping of longitude values coming out of pj_transform().
 *
 * Revision 1.17  2006/09/22 23:06:24  fwarmerdam
 * remote static start variable in pj_init (bug 1283)
 *
 * Revision 1.16  2004/09/08 15:23:37  warmerda
 * added new error for unknown prime meridians
 *
 * Revision 1.15  2004/05/05 01:45:41  warmerda
 * Made sword even longer.
 *
 * Revision 1.14  2004/05/05 01:45:00  warmerda
 * Make sword buffer larger so long +towgs84 parameters don't get split.
 *
 * Revision 1.13  2003/09/16 03:46:21  warmerda
 * dont use default ellps if any earth model info is set: bug 386
 *
 * Revision 1.12  2003/08/21 02:15:59  warmerda
 * improve MAX_ARG checking
 *
 * Revision 1.11  2003/06/09 21:23:16  warmerda
 * ensure start is initialized at very beginning of pj_init()
 *
 * Revision 1.10  2003/03/16 16:38:24  warmerda
 * Modified get_opt() to terminate reading the definition when a new
 * definition (a word starting with '<') is encountered, in addition to when
 * the definition terminator '<>' is encountered, so that unterminated
 * definitions like those in the distributed esri file will work properly.
 * http://bugzilla.remotesensing.org/show_bug.cgi?id=302
 *
 * Revision 1.9  2002/12/14 20:15:02  warmerda
 * added geocentric support, updated headers
 *
 */

#define PJ_LIB__
#include <projects.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

PJ_CVSID("$Id: pj_init.c,v 1.19 2007/11/26 00:21:59 fwarmerdam Exp $");

extern FILE *pj_open_lib(char *, char *);

/************************************************************************/
/*                              get_opt()                               */
/************************************************************************/
static paralist *
get_opt(paralist **start, FILE *fid, char *name, paralist *next) {
    char sword[302], *word = sword+1;
    int first = 1, len, c;

    len = strlen(name);
    *sword = 't';
    while (fscanf(fid, "%300s", word) == 1) {
        if (*word == '#') /* skip comments */
            while((c = fgetc(fid)) != EOF && c != '\n') ;
        else if (*word == '<') { /* control name */
            if (first && !strncmp(name, word + 1, len)
                && word[len + 1] == '>')
                first = 0;
            else if (!first && *word == '<') {
                while((c = fgetc(fid)) != EOF && c != '\n') ;
                break;
            }
        } else if (!first && !pj_param(*start, sword).i) {
            /* don't default ellipse if datum, ellps or any earth model
               information is set. */
            if( strncmp(word,"ellps=",6) != 0 
                || (!pj_param(*start, "tdatum").i 
                    && !pj_param(*start, "tellps").i 
                    && !pj_param(*start, "ta").i 
                    && !pj_param(*start, "tb").i 
                    && !pj_param(*start, "trf").i 
                    && !pj_param(*start, "tf").i) )
            {
                next = next->next = pj_mkparam(word);
            }
        }
    }

    if (errno == 25)
        errno = 0;
    return next;
}

/************************************************************************/
/*                            get_defaults()                            */
/************************************************************************/
static paralist *
get_defaults(paralist **start, paralist *next, char *name) {
	FILE *fid;

	if (fid = pj_open_lib("proj_def.dat", "rt")) {
		next = get_opt(start, fid, "general", next);
		rewind(fid);
		next = get_opt(start, fid, name, next);
		(void)fclose(fid);
	}
	if (errno)
		errno = 0; /* don't care if can't open file */
	return next;
}

/************************************************************************/
/*                              get_init()                              */
/************************************************************************/
static paralist *
get_init(paralist **start, paralist *next, char *name) {
	char fname[MAX_PATH_FILENAME+ID_TAG_MAX+3], *opt;
	FILE *fid;

	(void)strncpy(fname, name, MAX_PATH_FILENAME + ID_TAG_MAX + 1);
	if (opt = strrchr(fname, ':'))
		*opt++ = '\0';
	else { pj_errno = -3; return(0); }
	if (fid = pj_open_lib(fname, "rt"))
		next = get_opt(start, fid, opt, next);
	else
		return(0);
	(void)fclose(fid);
	if (errno == 25)
		errno = 0; /* unknown problem with some sys errno<-25 */
	return next;
}

/************************************************************************/
/*                            pj_init_plus()                            */
/*                                                                      */
/*      Same as pj_init() except it takes one argument string with      */
/*      individual arguments preceeded by '+', such as "+proj=utm       */
/*      +zone=11 +ellps=WGS84".                                         */
/************************************************************************/

PJ *
pj_init_plus( const char *definition )

{
#define MAX_ARG 200
    char	*argv[MAX_ARG];
    char	*defn_copy;
    int		argc = 0, i;
    PJ	        *result;
    
    /* make a copy that we can manipulate */
    defn_copy = (char *) pj_malloc( strlen(definition)+1 );
    strcpy( defn_copy, definition );

    /* split into arguments based on '+' and trim white space */

    for( i = 0; defn_copy[i] != '\0'; i++ )
    {
        switch( defn_copy[i] )
        {
          case '+':
            if( i == 0 || defn_copy[i-1] == '\0' )
            {
                if( argc+1 == MAX_ARG )
                {
                    pj_errno = -44;
                    return NULL;
                }
                
                argv[argc++] = defn_copy + i + 1;
            }
            break;

          case ' ':
          case '\t':
          case '\n':
            defn_copy[i] = '\0';
            break;

          default:
            /* do nothing */;
        }
    }

    /* perform actual initialization */
    result = pj_init( argc, argv );

    pj_dalloc( defn_copy );

    return result;
}

/************************************************************************/
/*                              pj_init()                               */
/*                                                                      */
/*      Main entry point for initialing a PJ projections                */
/*      definition.  Note that the projection specific function is      */
/*      called to do the initial allocation so it can be created        */
/*      large enough to hold projection specific parameters.            */
/************************************************************************/

PJ *
pj_init(int argc, char **argv) {
	char *s, *name;
        paralist *start = NULL;
	PJ *(*proj)(PJ *);
	paralist *curr;
	int i;
	PJ *PIN = 0;

	errno = pj_errno = 0;
        start = NULL;

	/* put arguments into internal linked list */
	if (argc <= 0) { pj_errno = -1; goto bum_call; }
	for (i = 0; i < argc; ++i)
		if (i)
			curr = curr->next = pj_mkparam(argv[i]);
		else
			start = curr = pj_mkparam(argv[i]);
	if (pj_errno) goto bum_call;

	/* check if +init present */
	if (pj_param(start, "tinit").i) {
		paralist *last = curr;

		if (!(curr = get_init(&start, curr, pj_param(start, "sinit").s)))
			goto bum_call;
		if (curr == last) { pj_errno = -2; goto bum_call; }
	}

	/* find projection selection */
	if (!(name = pj_param(start, "sproj").s))
		{ pj_errno = -4; goto bum_call; }
	for (i = 0; (s = pj_list[i].id) && strcmp(name, s) ; ++i) ;
	if (!s) { pj_errno = -5; goto bum_call; }

	/* set defaults, unless inhibited */
	if (!pj_param(start, "bno_defs").i)
		curr = get_defaults(&start, curr, name);
	proj = (PJ *(*)(PJ *)) pj_list[i].proj;

	/* allocate projection structure */
	if (!(PIN = (*proj)(0))) goto bum_call;
	PIN->params = start;
        PIN->is_latlong = 0;
        PIN->is_geocent = 0;
        PIN->long_wrap_center = 0.0;

        /* set datum parameters */
        if (pj_datum_set(start, PIN)) goto bum_call;

	/* set ellipsoid/sphere parameters */
	if (pj_ell_set(start, &PIN->a, &PIN->es)) goto bum_call;

        PIN->a_orig = PIN->a;
        PIN->es_orig = PIN->es;

	PIN->e = sqrt(PIN->es);
	PIN->ra = 1. / PIN->a;
	PIN->one_es = 1. - PIN->es;
	if (PIN->one_es == 0.) { pj_errno = -6; goto bum_call; }
	PIN->rone_es = 1./PIN->one_es;

        /* Now that we have ellipse information check for WGS84 datum */
        if( PIN->datum_type == PJD_3PARAM 
            && PIN->datum_params[0] == 0.0
            && PIN->datum_params[1] == 0.0
            && PIN->datum_params[2] == 0.0
            && PIN->a == 6378137.0
            && ABS(PIN->es - 0.006694379990) < 0.000000000050 )/*WGS84/GRS80*/
        {
            PIN->datum_type = PJD_WGS84;
        }
        
	/* set PIN->geoc coordinate system */
	PIN->geoc = (PIN->es && pj_param(start, "bgeoc").i);

	/* over-ranging flag */
	PIN->over = pj_param(start, "bover").i;

	/* longitude center for wrapping */
	PIN->long_wrap_center = pj_param(start, "rlon_wrap").f;

	/* central meridian */
	PIN->lam0=pj_param(start, "rlon_0").f;

	/* central latitude */
	PIN->phi0 = pj_param(start, "rlat_0").f;

	/* false easting and northing */
	PIN->x0 = pj_param(start, "dx_0").f;
	PIN->y0 = pj_param(start, "dy_0").f;

	/* general scaling factor */
	if (pj_param(start, "tk_0").i)
		PIN->k0 = pj_param(start, "dk_0").f;
	else if (pj_param(start, "tk").i)
		PIN->k0 = pj_param(start, "dk").f;
	else
		PIN->k0 = 1.;
	if (PIN->k0 <= 0.) {
		pj_errno = -31;
		goto bum_call;
	}

	/* set units */
	s = 0;
	if (name = pj_param(start, "sunits").s) { 
		for (i = 0; (s = pj_units[i].id) && strcmp(name, s) ; ++i) ;
		if (!s) { pj_errno = -7; goto bum_call; }
		s = pj_units[i].to_meter;
	}
	if (s || (s = pj_param(start, "sto_meter").s)) {
		PIN->to_meter = strtod(s, &s);
		if (*s == '/') /* ratio number */
			PIN->to_meter /= strtod(++s, 0);
		PIN->fr_meter = 1. / PIN->to_meter;
	} else
		PIN->to_meter = PIN->fr_meter = 1.;

	/* prime meridian */
	s = 0;
	if (name = pj_param(start, "spm").s) { 
            const char *value = NULL;
            char *next_str = NULL;

            for (i = 0; pj_prime_meridians[i].id != NULL; ++i )
            {
                if( strcmp(name,pj_prime_meridians[i].id) == 0 )
                {
                    value = pj_prime_meridians[i].defn;
                    break;
                }
            }
            
            if( value == NULL 
                && (dmstor(name,&next_str) != 0.0  || *name == '0')
                && *next_str == '\0' )
                value = name;

            if (!value) { pj_errno = -46; goto bum_call; }
            PIN->from_greenwich = dmstor(value,NULL);
	}
        else
            PIN->from_greenwich = 0.0;

	/* projection specific initialization */
	if (!(PIN = (*proj)(PIN)) || errno || pj_errno) {
bum_call: /* cleanup error return */
		if (!pj_errno)
			pj_errno = errno;
		if (PIN)
			pj_free(PIN);
		else
			for ( ; start; start = curr) {
				curr = start->next;
				pj_dalloc(start);
			}
		PIN = 0;
	}
	return PIN;
}

/************************************************************************/
/*                              pj_free()                               */
/*                                                                      */
/*      This is the application callable entry point for destroying     */
/*      a projection definition.  It does work generic to all           */
/*      projection types, and then calls the projection specific        */
/*      free function (P->pfree()) to do local work.  This maps to      */
/*      the FREEUP code in the individual projection source files.      */
/************************************************************************/

void
pj_free(PJ *P) {
	if (P) {
		paralist *t = P->params, *n;

		/* free parameter list elements */
		for (t = P->params; t; t = n) {
			n = t->next;
			pj_dalloc(t);
		}

		/* free projection parameters */
		P->pfree(P);
	}
}


