/******************************************************************************
 * Project:  PROJ.4
 * Purpose:  Load datum shift files into memory.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Frank Warmerdam
 *
 * 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.
 *****************************************************************************/

#define PJ_LIB__

#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "proj.h"
#include "proj_internal.h"

/************************************************************************/
/*                             swap_words()                             */
/*                                                                      */
/*      Convert the byte order of the given word(s) in place.           */
/************************************************************************/

static const int  byte_order_test = 1;
#define IS_LSB	(((const unsigned char *) (&byte_order_test))[0] == 1)

static void swap_words( void *data_in, int word_size, int word_count )

{
    int	word;
    unsigned char *data = (unsigned char *) data_in;

    for( word = 0; word < word_count; word++ )
    {
        int	i;
        
        for( i = 0; i < word_size/2; i++ )
        {
            unsigned char	t;
            
            t = data[i];
            data[i] = data[word_size-i-1];
            data[word_size-i-1] = t;
        }
        
        data += word_size;
    }
}

/************************************************************************/
/*                          nad_ctable_load()                           */
/*                                                                      */
/*      Load the data portion of a ctable formatted grid.               */
/************************************************************************/

int nad_ctable_load( projCtx ctx, struct CTABLE *ct, struct projFileAPI_t* fileapi )

{
    PAFile fid = (PAFile)fileapi;
    size_t a_size;

    pj_ctx_fseek( ctx, fid, sizeof(struct CTABLE), SEEK_SET );

    /* read all the actual shift values */
    a_size = ct->lim.lam * ct->lim.phi;
    ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size);
    if( ct->cvs == nullptr 
        || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size )
    {
        pj_dalloc( ct->cvs );
        ct->cvs = nullptr;

        pj_log( ctx, PJ_LOG_ERROR, 
                "ctable loading failed on fread() - binary incompatible?" );
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        return 0;
    }

    return 1;
} 

/************************************************************************/
/*                          nad_ctable_init()                           */
/*                                                                      */
/*      Read the header portion of a "ctable" format grid.              */
/************************************************************************/

struct CTABLE *nad_ctable_init( projCtx ctx, struct projFileAPI_t* fileapi )
{
    PAFile fid = (PAFile)fileapi;
    struct CTABLE *ct;
    int		id_end;

    /* read the table header */
    ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
    if( ct == nullptr 
        || pj_ctx_fread( ctx, ct, sizeof(struct CTABLE), 1, fid ) != 1 )
    {
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        pj_dalloc( ct );
        return nullptr;
    }

    /* do some minimal validation to ensure the structure isn't corrupt */
    if( ct->lim.lam < 1 || ct->lim.lam > 100000 
        || ct->lim.phi < 1 || ct->lim.phi > 100000 )
    {
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        pj_dalloc( ct );
        return nullptr;
    }
    
    /* trim white space and newlines off id */
    for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- )
    {
        if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' )
            ct->id[id_end] = '\0';
        else
            break;
    }

    ct->cvs = nullptr;

    return ct;
}

/************************************************************************/
/*                          nad_ctable2_load()                          */
/*                                                                      */
/*      Load the data portion of a ctable2 formatted grid.              */
/************************************************************************/

int nad_ctable2_load( projCtx ctx, struct CTABLE *ct, struct projFileAPI_t* fileapi )

{
    PAFile fid = (PAFile)fileapi;
    size_t a_size;

    pj_ctx_fseek( ctx, fid, 160, SEEK_SET );

    /* read all the actual shift values */
    a_size = ct->lim.lam * ct->lim.phi;
    ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size);
    if( ct->cvs == nullptr 
        || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size )
    {
        pj_dalloc( ct->cvs );
        ct->cvs = nullptr;

        if( getenv("PROJ_DEBUG") != nullptr )
        {
            fprintf( stderr,
            "ctable2 loading failed on fread() - binary incompatible?\n" );
        }

        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        return 0;
    }

    if( !IS_LSB )
    {
        swap_words( ct->cvs, 4, (int)a_size * 2 );
    }

    return 1;
} 

/************************************************************************/
/*                          nad_ctable2_init()                          */
/*                                                                      */
/*      Read the header portion of a "ctable2" format grid.             */
/************************************************************************/

struct CTABLE *nad_ctable2_init( projCtx ctx, struct projFileAPI_t* fileapi )
{
    PAFile fid = (PAFile)fileapi;
    struct CTABLE *ct;
    int		id_end;
    char        header[160];

    if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
    {
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        return nullptr;
    }

    if( !IS_LSB )
    {
        swap_words( header +  96, 8, 4 );
        swap_words( header + 128, 4, 2 );
    }

    if( strncmp(header,"CTABLE V2",9) != 0 )
    {
        pj_log( ctx, PJ_LOG_ERROR, "ctable2 - wrong header!" );
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        return nullptr;
    }

    /* read the table header */
    ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
    if( ct == nullptr )
    {
        pj_ctx_set_errno( ctx, ENOMEM );
        return nullptr;
    }

    memcpy( ct->id,       header +  16, 80 );
    memcpy( &ct->ll.lam,  header +  96, 8 );
    memcpy( &ct->ll.phi,  header + 104, 8 );
    memcpy( &ct->del.lam, header + 112, 8 );
    memcpy( &ct->del.phi, header + 120, 8 );
    memcpy( &ct->lim.lam, header + 128, 4 );
    memcpy( &ct->lim.phi, header + 132, 4 );

    /* do some minimal validation to ensure the structure isn't corrupt */
    if( ct->lim.lam < 1 || ct->lim.lam > 100000 
        || ct->lim.phi < 1 || ct->lim.phi > 100000 )
    {
        pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
        pj_dalloc( ct );
        return nullptr;
    }
    
    /* trim white space and newlines off id */
    for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- )
    {
        if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' )
            ct->id[id_end] = '\0';
        else
            break;
    }

    ct->cvs = nullptr;

    return ct;
}

/************************************************************************/
/*                              nad_init()                              */
/*                                                                      */
/*      Read a datum shift file in any of the supported binary formats. */
/************************************************************************/

struct CTABLE *nad_init(projCtx ctx, char *name) 
{
    struct CTABLE *ct;
    PAFile      fid;

    ctx->last_errno = 0;

/* -------------------------------------------------------------------- */
/*      Open the file using the usual search rules.                     */
/* -------------------------------------------------------------------- */
    if (!(fid = pj_open_lib(ctx, name, "rb"))) {
        return nullptr;
    }

    ct = nad_ctable_init( ctx, (struct projFileAPI_t*)fid );
    if( ct != nullptr )
    {
        if( !nad_ctable_load( ctx, ct, (struct projFileAPI_t*)fid ) )
        {
            nad_free( ct );
            ct = nullptr;
        }
    }

    pj_ctx_fclose(ctx, fid);
    return ct;
}

/************************************************************************/
/*                              nad_free()                              */
/*                                                                      */
/*      Free a CTABLE grid shift structure produced by nad_init().      */
/************************************************************************/

void nad_free(struct CTABLE *ct) 
{
    if (ct) {
        if( ct->cvs != nullptr )
            pj_dalloc(ct->cvs);

        pj_dalloc(ct);
    }
}
