/* ===================================================
 *  top level data manager
 * ---------------------------------------------------
 *
 * See the NOTICE file distributed with this work regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * More Information on ASAM OpenCRG can be found here:
 * https://www.asam.net/standards/detail/opencrg/
 *
 */
/* ====== INCLUSIONS ====== */
#include "crgBaseLibPrivate.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* ====== LOCAL VARIABLES ====== */
static CrgDataStruct** sDataSetList = NULL;
static int             sNoDataSets  = 0;

/* ====== LOCAL METHODS ====== */
/**
* scale the data of a given channel
* @param channel    pointer to the channel
* @param factor     scale factor
* @param dataOnly   flag to scale the data entries only, not the info values
*/
static void crgDataScaleChannel( CrgChannelBaseStruct* channel, double factor, int dataOnly );

/**
* offset the data of a given channel
* @param channel    pointer to the channel
* @param offset     offset value
*/
static void crgDataOffsetChannel( CrgChannelStruct* channel, double offset );

/**
* apply any transformation specified by modifiers
* @param crgData    pointer to data set which is to be modified
*/
static void crgDataApplyTransformations( CrgDataStruct *crgData );

/**
* rotate a given point around a given center for a given angle
* @param x      pointer to point's x co-ordinate           [m]
* @param y      pointer to point's u co-ordinate           [m]
* @param ctrX   x co-ordinate of rotation center           [m]
* @param ctrY   y co-ordinate of rotation center           [m]
* @param angle  the rotation angle                       [rad]
*/
static void rotatePoint( double* x, double* y, double ctrX, double ctrY, double angle );

/* ====== IMPLEMENTATION ====== */
int
crgDataSetRelease( int dataSet )
{
    size_t i;

    CrgDataStruct* crgData = crgDataSetAccess( dataSet );

    if ( !crgData )
        return 0;

    /* --- release all contact points referring to this data set --- */
    crgContactPointDeleteAll( dataSet );

    /* --- release all dynamically allocated data of the data set --- */
    for( i = 0; i < crgData->channelV.info.size; i++ )
        if ( crgData->channelZ[i].data )
            crgFree( crgData->channelZ[i].data );

    crgFree( crgData->channelZ );

    if ( crgData->channelX.data )
        crgFree( crgData->channelX.data );

    if ( crgData->channelY.data )
        crgFree( crgData->channelY.data );

    if ( crgData->channelPhi.data )
        crgFree( crgData->channelPhi.data );

    if ( crgData->channelV.data )
        crgFree( crgData->channelV.data );

    if ( crgData->channelSlope.data )
        crgFree( crgData->channelSlope.data );

    if ( crgData->channelBank.data )
        crgFree( crgData->channelBank.data );

    if ( crgData->channelRefZ.data )
        crgFree( crgData->channelRefZ.data );

    /* --- get rid of modifiers and options --- */
    if ( crgData->modifiers.entry )
        crgFree( crgData->modifiers.entry );

    if ( crgData->options.entry )
        crgFree( crgData->options.entry );

    /* --- invalidate the data set in the master list --- */
    for ( i = 0; i < (size_t)sNoDataSets; i++ )
        if ( sDataSetList[i] == crgData )
        {
            sDataSetList[i] = NULL;
            break;
        }

   /* --- finally: free the crgData struct --- */
   crgFree( crgData );

   crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetRelease: released data set no. %d\n", dataSet );


    return 1;
}

CrgDataStruct*
crgDataSetCreate( void )
{
    int i;
    int maxId = 0;
    int tgtId = -1;

    /* --- get the maximum ID of existing data sets --- */
    for ( i = 0; i < sNoDataSets; i++ )
    {
        /* --- found some free space in list of data sets? --- */
        if ( !sDataSetList[i] )
            tgtId = i;
        else if ( sDataSetList[i]->admin.id > maxId )
            maxId = sDataSetList[i]->admin.id;
    }

    /* --- next available ID is maximum ID plus one --- */
    maxId++;

    crgMsgPrint( dCrgMsgLevelInfo, "crgDataSetCreate: creating data set with id %d\n", maxId );

    /* --- any unused ID available or do we need to exend the data management list? --- */
    if ( tgtId < 0 )
    {
        sDataSetList = ( CrgDataStruct** ) crgRealloc( sDataSetList, ( sNoDataSets + 1 ) * sizeof( CrgDataStruct* ) );
        tgtId        = sNoDataSets;
        sNoDataSets++;
    }

    /* --- now allocate the space for the dataset itself --- */
    sDataSetList[tgtId]           = ( CrgDataStruct* ) crgCalloc( 1, sizeof( CrgDataStruct ) );
    sDataSetList[tgtId]->admin.id = maxId;

    /* --- allocate the memory for the options and modifiers --- */
    crgOptionCreateList( &( sDataSetList[tgtId]->options   ) );
    crgOptionCreateList( &( sDataSetList[tgtId]->modifiers ) );

    /* --- set the default options of the data set --- */
    crgDataSetModifierSetDefault( maxId );
    crgDataSetOptionSetDefault( maxId );

    return sDataSetList[tgtId];
}

CrgDataStruct*
crgDataSetAccess( int id )
{
    int i;

    for ( i = 0; i < sNoDataSets; i++ )
    {
        if( sDataSetList[i] )
        {
            if ( sDataSetList[i]->admin.id == id )
                return sDataSetList[i];
        }
    }

    return NULL;
}

void
crgDataPrintHeader( int dataSetId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
        return;

    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: uMin     = %10.4f\n", crgData->channelU.info.first   );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: xBegin   = %10.4f\n", crgData->channelX.info.first   );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: yBegin   = %10.4f\n", crgData->channelY.info.first   );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: phiBegin = %10.4f\n", crgData->channelPhi.info.first );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: uMax     = %10.4f\n", crgData->channelU.info.last    );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: xEnd     = %10.4f\n", crgData->channelX.info.last    );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: yEnd     = %10.4f\n", crgData->channelY.info.last    );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: phiEnd   = %10.4f\n", crgData->channelPhi.info.last  );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: uInc     = %10.4f\n", crgData->channelU.info.inc     );

    if ( !crgData->channelV.data )
    {
        crgMsgPrint( dCrgMsgLevelFatal, "crgDataPrintHeader: no v channel definition available.\n" );
        return;
    }

    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: vMin     = %10.4f\n", crgData->channelV.info.first   );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: vMax     = %10.4f\n", crgData->channelV.info.last    );
    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintHeader: vInc     = %10.4f\n", crgData->channelV.info.inc     );
}

void
crgDataPrintChannelInfo( int dataSetId )
{
    size_t i;
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
        return;

    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintChannelInfo: overview of channel information\n"    );
    crgMsgPrint( dCrgMsgLevelNotice, "    total number of channels  = %4zu\n", crgData->noChannels    );
    crgMsgPrint( dCrgMsgLevelNotice, "    number of v channels      = %4ld\n", crgData->channelV.info.size );

    for ( i = 0; i < crgData->channelV.info.size; ++i )
        crgMsgPrint( dCrgMsgLevelNotice, "    v channel[%4ld].pos   = %.4f\n", i, crgData->channelV.data[i] );

    crgMsgPrint( dCrgMsgLevelNotice, "    number of z channels      = %4ld\n", crgData->channelV.info.size );

    for ( i = 0; i < crgData->channelV.info.size; ++i )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "    z channel[%4ld].info.index = %4zu\n",  i, crgData->channelZ[i].info.index );
        crgMsgPrint( dCrgMsgLevelDebug,  "    z channel[%4ld].info.size  = %4ld\n",  i, crgData->channelZ[i].info.size );
    }

    crgMsgPrint( dCrgMsgLevelNotice, "additional channels:\n" );

    if ( crgData->channelX.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelX.info.index   = %zu\n", crgData->channelX.info.index );
    if ( crgData->channelX.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelX.info.size    = %ld\n", crgData->channelX.info.size  );

    if ( crgData->channelY.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelY.info.index   = %zu\n", crgData->channelY.info.index );
    if ( crgData->channelY.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelY.info.size    = %ld\n", crgData->channelY.info.size  );

    if ( crgData->channelU.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelU.info.index   = %zu\n", crgData->channelU.info.index );
    if ( crgData->channelU.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelU.info.size    = %ld\n", crgData->channelU.info.size  );

    if ( crgData->channelPhi.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelPhi.info.index = %zu\n", crgData->channelPhi.info.index );
    if ( crgData->channelPhi.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelPhi.info.size  = %ld\n", crgData->channelPhi.info.size  );

    if ( crgData->channelSlope.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelSlope.info.index = %zu\n", crgData->channelSlope.info.index );
    if ( crgData->channelSlope.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelSlope.info.size  = %ld\n", crgData->channelSlope.info.size  );

    if ( crgData->channelBank.info.defined )
        crgMsgPrint( dCrgMsgLevelNotice, "    channelBank.info.index = %zu\n", crgData->channelBank.info.index );
    if ( crgData->channelBank.info.valid )
        crgMsgPrint( dCrgMsgLevelDebug,  "    channelBank.info.size  = %ld\n", crgData->channelBank.info.size  );

    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintChannelInfo: end of channel overview\n" );
}

void
crgDataPrintRoadInfo( int dataSetId )
{
    size_t i;
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
        return;

    crgMsgPrint( dCrgMsgLevelNotice, "crgDataPrintRoadInfo: Summary of crg basic information\n" );
    crgMsgPrint( dCrgMsgLevelNotice, "                      length of road [m]:          %10.4f\n", crgData->channelU.info.last - crgData->channelU.info.first );
    crgMsgPrint( dCrgMsgLevelNotice, "                      cross section increment [m]: %10.4f\n", crgData->channelU.info.inc );
    crgMsgPrint( dCrgMsgLevelNotice, "                      number of cross sections:       %ld\n",  crgData->channelU.info.size );

    crgMsgPrint( dCrgMsgLevelNotice, "                      width of crg road[m]:        %10.4f\n", crgData->channelV.info.last - crgData->channelV.info.first );
    crgMsgPrint( dCrgMsgLevelNotice, "                      width right of refline [m]:  %10.4f\n", crgData->channelV.info.first < 0.0 ?  crgData->channelV.info.first : 0.0 );
    crgMsgPrint( dCrgMsgLevelNotice, "                      width left of refline [m]:   %10.4f\n", crgData->channelV.info.last > 0.0 ?  crgData->channelV.info.last : 0.0 );

    for ( i = 0; i < crgData->channelV.info.size; ++i )
        crgMsgPrint( dCrgMsgLevelNotice, "                      long section at [m]:         %10.4f\n", crgData->channelV.data[i]   );

    crgMsgPrint( dCrgMsgLevelNotice, "                      number of v channels:         %4ld\n", crgData->channelV.info.size );
}

int
crgDataSetHistory( int dataSetId, int histSize )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetHistory: unknown data set %d\n", dataSetId );
        return 0;
    }

    if ( histSize < 0 )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetHistory: invalid history size = %d\n", histSize );
        return 0;
    }

    /* --- set the history size of all contact points referring to the given data set --- */
    return crgContactPointSetHistoryForDataSet( crgData, histSize );
}

int
crgDataSetGetURange( int dataSetId, double *uMin, double *uMax )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetGetURange: unknown data set %d\n", dataSetId );
        return 0;
    }

    *uMin = crgData->channelU.info.first;
    *uMax = crgData->channelU.info.last;

    return 1;
}

int
crgDataSetGetVRange( int dataSetId, double *vMin, double *vMax )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetGetVRange: unknown data set %d\n", dataSetId );
        return 0;
    }

    *vMin = crgData->channelV.data[0];
    *vMax = crgData->channelV.data[crgData->channelV.info.size-1];

    return 1;
}

int
crgDataSetGetIncrements( int dataSetId, double *uInc, double *vInc )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetGetIncrements: unknown data set %d\n", dataSetId );
        return 0;
    }

    *uInc = crgData->channelU.info.inc;
    *vInc = crgData->channelV.info.inc;

    return 1;
}

int
crgDataSetGetUtilityDataClosedTrack( const int dataSetId, int *uIsClosed, double *uCloseMin, double *uCloseMax )
{
    const CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelNotice, "crgDataSetGetUtilityDataClosedTrack: unknown data set %d\n", dataSetId );
        return 0;
    }

    *uIsClosed = crgData->util.uIsClosed;

    if( uIsClosed )
    {
        *uCloseMax = crgData->util.uCloseMax;
        *uCloseMin = crgData->util.uCloseMin;
    }
    else
    {
        *uCloseMax = nan("");
        *uCloseMin = nan("");
    }

    return 1;
}

int
crgDataSetModifierSetInt( int dataSetId, unsigned int optionId, int optionValue )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierSetInt: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionSetInt( &( crgData->modifiers ), optionId, optionValue );
}

int
crgDataSetModifierSetDouble( int dataSetId, unsigned int optionId, double optionValue )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierSetDouble: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionSetDouble( &( crgData->modifiers ), optionId, optionValue );
}

int
crgDataSetModifierGetInt( int dataSetId, unsigned int optionId, int *optionValue )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierGetInt: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionGetInt( &( crgData->modifiers ), optionId, optionValue );
}

int
crgDataSetModifierGetDouble( int dataSetId, unsigned int optionId, double *optionValue )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierGetDouble: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionGetDouble( &( crgData->modifiers ), optionId, optionValue );
}

int
crgDataSetModifierRemove( int dataSetId, unsigned int optionId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierRemove: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionRemove( &( crgData->modifiers ), optionId );
}

int
crgDataSetModifierRemoveAll( int dataSetId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierRemoveAll: invalid data set id <%d>.\n", dataSetId );
        return 0;
    }

    return crgOptionRemoveAll( &( crgData->modifiers ) );
}

void
crgDataSetModifiersPrint( int dataSetId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifiersPrint: invalid data set id <%d>.\n", dataSetId );
        return;
    }

    crgOptionsPrint( &( crgData->modifiers ), "modifier" );
}

void
crgDataSetModifiersApply( int dataSetId )
{
    double dValue;
    int    iValue;
    size_t i;
    int    needPrepare = 0; /* per default, data doesn't have to be re-prepared */

    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifiersApply: invalid data set id <%d>.\n", dataSetId );
        return;
    }

    /* --- is z scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleZ, &dValue ) )
    {
        needPrepare = 1;

        for ( i = 0; i < crgData->channelV.info.size; i++ )
            crgDataScaleChannel( ( CrgChannelBaseStruct* ) &( crgData->channelZ[i] ), dValue, 0 );
    }

    /* --- is slope scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleSlope, &dValue ) )
    {
        needPrepare = 1;

        crgDataScaleChannel( ( CrgChannelBaseStruct* ) &( crgData->channelSlope ), dValue, 0 );

        /* --- z end value of header is no longer valid! --- */
        crgData->admin.defMask &= ~dCrgDataDefZEnd;
    }

    /* --- is bank scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleBank, &dValue ) )
    {
        /* --- re-preparation is not required --- */
        crgDataScaleChannel( ( CrgChannelBaseStruct* ) &( crgData->channelBank ), dValue, 0 );

        /* --- bank end value of header is no longer valid! --- */
        crgData->admin.defMask &= ~dCrgDataDefBankEnd;
    }

    /* --- is length scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleLength, &dValue ) )
    {
        double uRange = crgData->channelU.info.last - crgData->channelU.info.first;

        needPrepare = 1;

        crgDataScaleChannel( ( CrgChannelBaseStruct* ) &( crgData->channelU ), dValue, 1 );

        crgData->channelU.info.last = crgData->channelU.info.first + dValue * uRange;
        crgData->channelU.info.inc *=dValue;

        /* --- x and y end values of header are no longer valid! --- */
        crgData->admin.defMask &= ~dCrgDataDefXEnd;
        crgData->admin.defMask &= ~dCrgDataDefYEnd;

        /* --- z end value of header is no longer valid! --- */
        crgData->admin.defMask &= ~dCrgDataDefZEnd;
    }

    /* --- is width scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleWidth, &dValue ) )
    {
        needPrepare = 1;

        crgDataScaleChannel( ( CrgChannelBaseStruct* ) &( crgData->channelV ), dValue, 0 );
    }

    /* --- is curvature scaling defined? --- */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModScaleCurvature, &dValue ) )
    {
        needPrepare = 1;
        {
            size_t i;

            for ( i = 1; i < crgData->channelPhi.info.size; i++ )
                crgData->channelPhi.data[i] = crgData->channelPhi.info.first + dValue * ( crgData->channelPhi.data[i] - crgData->channelPhi.info.first );

            if ( crgData->channelPhi.info.size )
                crgData->channelPhi.info.last = crgData->channelPhi.data[crgData->channelPhi.info.size-1];
            else
                crgData->channelPhi.info.last = crgData->channelPhi.info.first;

            /* --- re-compute some utility data --- */
            crgData->util.phiLastSin = sin( crgData->channelPhi.info.last );
            crgData->util.phiLastCos = cos( crgData->channelPhi.info.last );
        }

        /* --- x and y end values of header are no longer valid! --- */
        crgData->admin.defMask &= ~dCrgDataDefXEnd;
        crgData->admin.defMask &= ~dCrgDataDefYEnd;
    }

    /* --- treat NaNs in grid  --- */
    if ( crgOptionGetInt( &( crgData->modifiers ), dCrgModGridNaNMode, &iValue ) )
    {
        needPrepare = 1;

        dValue = 0.0;

        /* --- any offset defined? --- */
        if ( iValue != dCrgGridNaNKeep )
            crgOptionGetDouble( &( crgData->modifiers ), dCrgModGridNaNOffset, &dValue );

        crgLoaderHandleNaNs( crgData, iValue, dValue );
    }

    /* --- does the data prepare stage need to be re-called? --- */
    if ( needPrepare )
        crgLoaderPrepareData( crgData );


    /* --- transform data to a different location? --- */
    crgDataApplyTransformations( crgData );
}

static void
crgDataScaleChannel( CrgChannelBaseStruct* channel, double factor, int valuesOnly )
{
    size_t i;

    if ( !channel )
        return;

    if ( channel->data )
    {
        if ( channel->info.singlePrec )
        {
            float* data = ( float* ) ( channel->data );
            for ( i = 0; i < channel->info.size; i++ )
                if ( !isnan( data[i] ) )
                    data[i] *= ( float ) factor;
        }
        else
        {
            double* data = ( double* ) ( channel->data );
            for ( i = 0; i < channel->info.size; i++ )
                if ( !isnan(data[i] ) )
                    data[i] *= factor;
        }
    }

    if ( valuesOnly )
        return;

    channel->info.first *= factor;
    channel->info.last  *= factor;
    channel->info.inc   *= factor;
    channel->info.mean  *= factor;
}

static void
crgDataOffsetChannel( CrgChannelStruct* channel, double offset )
{
    size_t i;

    if ( !channel )
        return;

    for ( i = 0; i < channel->info.size; i++ )
        channel->data[i] += offset;

    channel->info.first += offset;
    channel->info.last  += offset;
}

void
crgDataSetModifierSetDefault( int dataSetId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetModifierSetDefault: invalid data set id <%d>.\n", dataSetId );
        return;
    }

    crgOptionSetDefaultModifiers( &( crgData->modifiers ) );
}

void
crgDataSetOptionSetDefault( int dataSetId )
{
    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetOptionSetDefault: invalid data set id <%d>.\n", dataSetId );
        return;
    }

    crgOptionSetDefaultOptions( &( crgData->options ) );
}

void
crgMemRelease( void )
{
    int i;

    /* --- delete all data sets --- */
    for ( i = 0; i < sNoDataSets; i++ )
    {
        if ( sDataSetList[i] )
            crgDataSetRelease( sDataSetList[i]->admin.id );
    }

    /* --- finally: release the list holding all data sets --- */
    crgFree( sDataSetList );

    sDataSetList = NULL;
    sNoDataSets  = 0;
}

const char*
crgGetReleaseInfo( void )
{
    return "ASAM OpenCRG C-API release 2.0.0, February 02, 2026";
}

void
crgDataSetBuildVTable( int dataSetId )
{
    int i;

    CrgDataStruct *crgData = crgDataSetAccess( dataSetId );

    if ( !crgData )
    {
        crgMsgPrint( dCrgMsgLevelWarn, "crgDataSetBuildVTable: invalid data set id <%d>.\n", dataSetId );
        return;
    }
    if ( !crgData->channelV.info.size )
    {
         crgMsgPrint( dCrgMsgLevelFatal, "crgDataSetBuildVTable: no cross section for data set id <%d>.\n", dataSetId );
         return;
    }

    crgData->indexTableV.minVal = crgData->channelV.info.first;
    crgData->indexTableV.maxVal = crgData->channelV.info.last;
    crgData->indexTableV.range  = crgData->indexTableV.maxVal - crgData->indexTableV.minVal;

    if ( fabs( crgData->indexTableV.range ) == 0.0 )
    {
        crgData->indexTableV.size  = 0;
        crgData->indexTableV.valid = 0;
        return;
    }

    crgData->indexTableV.size   = dCrgVTableStdSize;
    crgData->indexTableV.valid  = 1;

    for ( i = 0; i < dCrgVTableStdSize; i++ )
    {
        double vPos = crgData->indexTableV.minVal + i * crgData->indexTableV.range / ( dCrgVTableStdSize - 1 );

        size_t    indexV = 0;
        size_t    indexCtr;
        size_t    index0 = crgData->channelV.info.size - 1;

        while ( 1 )
        {
            indexCtr = ( index0 + indexV ) / 2;

            if ( indexCtr <= indexV )
                break;

            if ( vPos < crgData->channelV.data[indexCtr] )
                index0 = indexCtr;
            else
                indexV = indexCtr;

        }

        crgData->indexTableV.refIdx[i] = indexV;
    }
}

static void
crgDataApplyTransformations( CrgDataStruct *crgData )
{
    /* --- store the transformation in the following variables --- */
    double fromXYZ[3]    = { 0.0, 0.0, 0.0 };
    double toXYZ[3]      = { 0.0, 0.0, 0.0 };
    double rotCenter[2]  = { 0.0, 0.0 };
    double rotAngle      = 0.0;
    double fromPhi       = 0.0;
    double fromCurv      = 0.0;
    int    applyXform    = 0;

    double uPos   = 0.0;
    double vPos   = 0.0;
    double offset = 0.0;
    double dValue = 0.0;

    if ( !crgData )
        return;

    /* --- find the "from" point --- */

    /* --- default reference point is always the begin of the line --- */
    uPos = crgData->channelU.info.first;
    vPos = 0.0;

    /* --- transform data to a different locaction using an arbitrary reference point ? --- */
    /* make this code a bit more robust in terms of optimization */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointX, &dValue ) )
    {
        toXYZ[0]  = dValue;
        applyXform = 1;
    }

    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointY, &dValue ) )
    {
        toXYZ[1]  = dValue;
        applyXform = 1;
    }

    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointZ, &dValue ) )
    {
        toXYZ[2]  = dValue;
        applyXform = 1;
    }

    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointPhi, &dValue) )
    {
        rotAngle  = dValue;
        applyXform = 1;
    }

    /* u by absolute position? */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointU, &uPos ) )
        applyXform = 1;

    /* u by relative position? */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointUFrac, &uPos ) )
    {
        uPos = crgData->channelU.info.first + uPos * ( crgData->channelU.info.last - crgData->channelU.info.first );

        /* any offset defined? */
        crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointUOffset, &offset );

        uPos += offset;

        applyXform = 1;
    }

    /* v by absolute position? */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointV, &vPos ) )
        applyXform = 1;

        /* v by relative position? */
    if ( crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointVFrac, &vPos ) )
    {
        vPos = crgData->channelV.info.first + vPos * ( crgData->channelV.info.last - crgData->channelV.info.first );

        /* any offset defined? */
        offset = 0.0;

        crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefPointVOffset, &offset );

        vPos += offset;

        applyXform = 1;
    }

    if ( applyXform )
    {
        /* --- compute FROM point --- */
        crgDataEvaluv2xy( crgData, &( crgData->options ), uPos, vPos, &( fromXYZ[0] ), &( fromXYZ[1] ) );
        crgDataEvaluv2z( crgData, &( crgData->options ), uPos, vPos, &( fromXYZ[2] ) );
        crgDataEvaluv2pk( crgData, &( crgData->options ), uPos, vPos, &( fromPhi ), &( fromCurv ) );

        /* correct rotation angle */
        rotAngle -= fromPhi;

        rotCenter[0] = fromXYZ[0];
        rotCenter[1] = fromXYZ[1];
    }

    /* --- if transformation is not defined by reference point, then check for relative offsets --- */
    if ( !applyXform )
    {
        int transform = 0;


        /* --- per default, transformation point is begin of reference line --- */
        fromXYZ[0] = crgData->channelX.info.first;
        fromXYZ[1] = crgData->channelY.info.first;

        /* --- default: rotate around xbeg, ybeg */
        rotCenter[0] = fromXYZ[0];
        rotCenter[1] = fromXYZ[1];

        crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineRotCenterX, &( rotCenter[0] ) );
        crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineRotCenterY, &( rotCenter[1] ) );

        /* --- transform data to a different location using a plain offset? --- */
        /* make this code a bit more robust in terms of optimization */
        transform  = crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineOffsetX,    &( toXYZ[0] ) );
        transform |= crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineOffsetY,    &( toXYZ[1] ) );
        transform |= crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineOffsetZ,    &( toXYZ[2] ) );
        transform |= crgOptionGetDouble( &( crgData->modifiers ), dCrgModRefLineOffsetPhi,  &rotAngle );

        crgDataEvaluv2z( crgData, NULL, 0.0, 0.0, &( fromXYZ[2] ) );

        toXYZ[0] += fromXYZ[0];
        toXYZ[1] += fromXYZ[1];
        toXYZ[2] += fromXYZ[2];

        /* reset the temporarily used values */
        fromPhi  = 0.0;
        fromCurv = 0.0;

        /* --- is there already a need for applying a transformation? --- */
        applyXform = transform;
    }

   if ( applyXform )   /* temporarily disabled, for debugging only */
        crgMsgPrint( dCrgMsgLevelDebug, "crgDataApplyTransformations: rotCx / rotCy / dphi / dx / dy / dz  = %.6f %.6f %.6f / %.6f / %.6f / %.6f\n",
                     rotCenter[0], rotCenter[1] ,rotAngle, toXYZ[0] - fromXYZ[0], toXYZ[1] - fromXYZ[1], toXYZ[2] - fromXYZ[2] );

    if ( applyXform )
    {
        size_t i;

        /* --- first rotate --- */
        /* phi on center line */

        crgMsgPrint( dCrgMsgLevelDebug, "crgDataApplyTransformations: rotating data by %.3lf deg around %.3f / %.3f fromPhi = %.3lf \n",
                rotAngle * 180 / 3.14159265, rotCenter[0], rotCenter[1], fromPhi * 180 / 3.14159265 );

        crgDataOffsetChannel( &( crgData->channelPhi ), rotAngle );

        /* --- compute sine and cosine of direction at either end of reference line */
        crgData->util.phiFirstSin = sin( crgData->channelPhi.info.first );
        crgData->util.phiFirstCos = cos( crgData->channelPhi.info.first );
        crgData->util.phiLastSin  = sin( crgData->channelPhi.info.last  );
        crgData->util.phiLastCos  = cos( crgData->channelPhi.info.last  );

        /* x,y data of center line */
        rotatePoint( &( crgData->channelX.info.first ), &( crgData->channelY.info.first ), rotCenter[0], rotCenter[1], rotAngle );
        rotatePoint( &( crgData->channelX.info.last ),  &( crgData->channelY.info.last ),  rotCenter [0], rotCenter [1], rotAngle );

        for ( i = 0; i < crgData->channelX.info.size; i++ )
            rotatePoint( &( crgData->channelX.data[i] ), &( crgData->channelY.data[i] ), rotCenter [0], rotCenter [1], rotAngle );

        /* --- translation --- */
        crgDataOffsetChannel( &( crgData->channelX ), toXYZ[0] - fromXYZ[0] );
        crgDataOffsetChannel( &( crgData->channelY ), toXYZ[1] - fromXYZ[1] );

        if ( crgData->channelRefZ.info.valid )
        {
            for ( i = 0; i < crgData->channelRefZ.info.size; i++ )
                crgData->channelRefZ.data[i] += toXYZ[2] - fromXYZ[2];

            crgData->channelRefZ.info.first += toXYZ[2] - fromXYZ[2];
            crgData->channelRefZ.info.last  += toXYZ[2] - fromXYZ[2];
        }
        else if ( crgData->admin.defMask & dCrgDataDefZStart )
        {
            crgData->channelRefZ.info.first += toXYZ[2] - fromXYZ[2];
            crgData->channelRefZ.info.last  += toXYZ[2] - fromXYZ[2];
        }
        else
        {
            for ( i = 0; i < crgData->channelV.info.size; i++ )
            {
                crgData->channelZ[i].info.mean  += toXYZ[2] - fromXYZ[2];
                crgData->channelZ[i].info.first += toXYZ[2] - fromXYZ[2];
                crgData->channelZ[i].info.last  += toXYZ[2] - fromXYZ[2];
            }
        }
    }
}

static void
rotatePoint( double* x, double* y, double ctrX, double ctrY, double rotAngle )
{
    double dx;
    double dy;
    double angle;
    double dist;

    /* x,y data of center line */
    dx = *x - ctrX;
    dy = *y - ctrY;

    angle  = atan2( dy, dx );
    angle += rotAngle;
    dist  = sqrt( dx * dx + dy * dy );

    *x = ctrX + dist * cos( angle );
    *y = ctrY + dist * sin( angle );
}
