/**********************************************************************
 *
 *  geo_write.c  -- Public routines for GEOTIFF GeoKey access.
 *
 *    Written By: Niles D. Ritter.
 *
 *  copyright (c) 1995   Niles D. Ritter
 *
 *  Permission granted to use this software, so long as this copyright
 *  notice accompanies any source code derived therefrom.
 *
 **********************************************************************/

#include "geotiffio.h"   /* public interface        */
#include "geo_tiffp.h" /* external TIFF interface */
#include "geo_keyp.h"  /* private interface       */

static int WriteKey(GTIF* gt, TempKeyData* tempData,
                    KeyEntry* entptr, GeoKey* keyptr);
static int SortKeys(GTIF* gt,int *sortkeys);


/**
This function flushes all the GeoTIFF keys that have been set with the
GTIFKeySet() function into the associated
TIFF file.

@param gt The GeoTIFF handle returned by GTIFNew.

GTIFWriteKeys() should be called before
GTIFFree() is used to deallocate a GeoTIFF access handle.
 */

int GTIFWriteKeys(GTIF *gt)
{

    if (!(gt->gt_flags & FLAG_FILE_MODIFIED)) return 1;

    if( gt->gt_tif == NULL )
        return 0;

    TempKeyData tempData;
    tempData.tk_asciiParams = 0;
    tempData.tk_asciiParamsLength = 0;
    tempData.tk_asciiParamsOffset = 0;

    /*  Sort the Keys into numerical order */
    int sortkeys[MAX_KEYS];
    if (!SortKeys(gt,sortkeys))
    {
        /* XXX error: a key was not recognized */
    }

    /* Set up header of ProjectionInfo tag */
    KeyHeader *header = (KeyHeader *)gt->gt_short;
    header->hdr_num_keys = (pinfo_t) gt->gt_num_keys;
    header->hdr_version  = gt->gt_version;
    header->hdr_rev_major  = gt->gt_rev_major;
    header->hdr_rev_minor  = gt->gt_rev_minor;

    /* Sum up the ASCII tag lengths */
    for (int i = 0; i < gt->gt_num_keys; i++)
    {
        GeoKey *keyptr = gt->gt_keys + sortkeys[i];
        if (keyptr->gk_type == TYPE_ASCII)
        {
            tempData.tk_asciiParamsLength += keyptr->gk_count;
        }
    }
    if (tempData.tk_asciiParamsLength > 0)
    {
        tempData.tk_asciiParams =
            (char *)_GTIFcalloc(tempData.tk_asciiParamsLength + 1);
        if( tempData.tk_asciiParams == NULL )
            return 0;
        tempData.tk_asciiParams[tempData.tk_asciiParamsLength] = '\0';
    }

    /* Set up the rest of SHORT array properly */
    GeoKey *keyptr = gt->gt_keys;
    KeyEntry *entptr = (KeyEntry*)(gt->gt_short + 4);
    for (int i=0; i< gt->gt_num_keys; i++,entptr++)
    {
        if (!WriteKey(gt,&tempData,entptr,keyptr+sortkeys[i]))
        {
            if (tempData.tk_asciiParamsLength > 0)
            {
                _GTIFFree (tempData.tk_asciiParams);
            }
            return 0;
        }
    }

    /* Write out the Key Directory */
    (gt->gt_methods.set)(gt->gt_tif, GTIFF_GEOKEYDIRECTORY, gt->gt_nshorts, gt->gt_short );

    /* Write out the params directories */
    if (gt->gt_ndoubles)
        (gt->gt_methods.set)(gt->gt_tif, GTIFF_DOUBLEPARAMS, gt->gt_ndoubles, gt->gt_double );
    if (tempData.tk_asciiParamsLength > 0)
    {
        /* just to be safe */
        tempData.tk_asciiParams[tempData.tk_asciiParamsLength] = '\0';
        (gt->gt_methods.set)(gt->gt_tif,
                             GTIFF_ASCIIPARAMS, 0, tempData.tk_asciiParams);
    }

    gt->gt_flags &= ~FLAG_FILE_MODIFIED;

    if (tempData.tk_asciiParamsLength > 0)
    {
        _GTIFFree (tempData.tk_asciiParams);
    }
    return 1;
}

/**********************************************************************
 *
 *                        Private Routines
 *
 **********************************************************************/

/*
 * Given GeoKey, write out the KeyEntry entries, returning 0 if failure.
 *  This is the exact complement of ReadKey().
 */

static int WriteKey(GTIF* gt, TempKeyData* tempData,
                    KeyEntry* entptr, GeoKey* keyptr)
{
    entptr->ent_key = (pinfo_t) keyptr->gk_key;
    entptr->ent_count = (pinfo_t) keyptr->gk_count;
    const int count = entptr->ent_count;

    if (count==1 && keyptr->gk_type==TYPE_SHORT)
    {
        entptr->ent_location = GTIFF_LOCAL;
        memcpy(&(entptr->ent_val_offset), &keyptr->gk_data, sizeof(pinfo_t));
        return 1;
    }

    switch (keyptr->gk_type)
    {
      case TYPE_SHORT:
        entptr->ent_location = GTIFF_GEOKEYDIRECTORY;
        entptr->ent_val_offset = (pinfo_t)
            ((pinfo_t*)keyptr->gk_data - gt->gt_short);
        break;
      case TYPE_DOUBLE:
        entptr->ent_location = GTIFF_DOUBLEPARAMS;
        entptr->ent_val_offset = (pinfo_t)
            ((double*)keyptr->gk_data - gt->gt_double);
        break;
      case TYPE_ASCII:
        if( tempData->tk_asciiParams == NULL )
            return 0;
        entptr->ent_location = GTIFF_ASCIIPARAMS;
        entptr->ent_val_offset = (pinfo_t) tempData->tk_asciiParamsOffset;
        _GTIFmemcpy (tempData->tk_asciiParams + tempData->tk_asciiParamsOffset
                     , keyptr->gk_data, keyptr->gk_count);
        tempData->tk_asciiParams[tempData->tk_asciiParamsOffset+keyptr->gk_count-1] = '|';
        tempData->tk_asciiParamsOffset += keyptr->gk_count;
        break;
      default:
        return 0; /* failure */
    }

    return 1; /* success */
}


/*
 * Numerically sort the GeoKeys.
 * We just do a linear search through
 * the list and pull out the keys that were set.
 */

static int SortKeys(GTIF* gt,int *sortkeys)
{
    /* A bit convoluted to make Clang Static Analyzer happy */
    if( gt->gt_num_keys <= 0 )
        return 1;

    sortkeys[0] = 1;
    for( int i = 1; i < gt->gt_num_keys; i++ )
        sortkeys[i] = i+1;

    int did_work;
    do {  /* simple bubble sort */
        did_work = 0;
        for( int i = 0; i < gt->gt_num_keys-1; i++ )
        {
            if( gt->gt_keys[sortkeys[i]].gk_key
                > gt->gt_keys[sortkeys[i+1]].gk_key )
            {
                /* swap keys in sort list */
                int j = sortkeys[i];
                sortkeys[i] = sortkeys[i+1];
                sortkeys[i+1] = j;

                did_work = 1;
            }
        }
    } while( did_work );

    return 1;
}
