/* TA-LIB Copyright (c) 1999-2025, Mario Fortier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * - Neither name of author nor the names of its contributors
 *   may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* List of contributors:
 *
 *  Initial  Name/description
 *  -------------------------------------------------------------------
 *  MF       Mario Fortier
 *
 *
 * Change history:
 *
 *  MMDDYY BY   Description
 *  -------------------------------------------------------------------
 *  112400 MF   First version.
 *
 */

/* Description:
 *     Provide utility function internally used in ta_regtest only.
 */

/**** Headers ****/
#ifdef WIN32
   #include "windows.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "ta_test_priv.h"
#include "ta_utility.h"
#include "ta_memory.h"


/**** External functions declarations. ****/
/* None */

/**** External variables declarations. ****/
extern int nbProfiledCall;
extern double timeInProfiledCall;
extern double worstProfiledCall;
extern int insufficientClockPrecision;

/**** Global variables definitions.    ****/

/* Global temporary buffers used while testing. */
#define RESV_PATTERN_MEMGUARD_1   (2.4789205E-150)
#define RESV_PATTERN_MEMGUARD_2   (4.2302468E-165)

#define RESV_PATTERN_PREFIX       (9.1349043E-200)
#define RESV_PATTERN_SUFFIX       (8.1489031E-158)
#define RESV_PATTERN_IMPROBABLE   (-2.849284E-199)

#define RESV_PATTERN_PREFIX_INT     (TA_INTEGER_DEFAULT)
#define RESV_PATTERN_SUFFIX_INT     (TA_INTEGER_DEFAULT)
#define RESV_PATTERN_IMPROBABLE_INT (TA_INTEGER_DEFAULT)

#define TA_BUF_PREFIX 100
#define TA_BUF_SUFFIX 100
#define TA_BUF_SIZE   (TA_BUF_PREFIX+MAX_NB_TEST_ELEMENT+TA_BUF_SUFFIX)

#define TA_NB_OUT 3
#define TA_NB_IN  1
#define TA_NB_OUT_IN (TA_NB_OUT+TA_NB_IN)

TA_Real memoryGuard1 = RESV_PATTERN_MEMGUARD_1; /* Magic number to detect problem. */
TA_Real buf[NB_GLOBAL_BUFFER][TA_NB_OUT_IN][TA_BUF_SIZE];     /* The global buffers. */
TA_Real memoryGuard2 = RESV_PATTERN_MEMGUARD_2; /* Magic number to detect problem. */

#define NB_TOTAL_ELEMENTS (sizeof(buf)/sizeof(TA_Real))

TestBuffer gBuffer[5]; /* See initGlobalBuffer. */

/**** Local declarations.              ****/
/* None */

/**** Local functions declarations.    ****/
static ErrorNumber doRangeTestFixSize( RangeTestFunction testFunction,
                                       void *opaqueData,
                                       TA_Integer refOutBeg,
                                       TA_Integer refOutNbElement,
                                       TA_Integer refLookback,
                                       const TA_Real    *refBuffer,
                                       const TA_Integer *refBufferInt,
                                       TA_FuncUnstId unstId,
                                       TA_Integer fixSize,
                                       unsigned int outputNb,
                                       unsigned int integerTolerance );

static int dataWithinReasonableRange( TA_Real val1, TA_Real val2,
                                      unsigned int outputPosition,
                                      TA_FuncUnstId unstId,
                                      unsigned int integerTolerance );

static ErrorNumber doRangeTestForOneOutput( RangeTestFunction testFunction,
                                            TA_FuncUnstId unstId,
                                            void *opaqueData,
                                            unsigned int outputNb,
                                            unsigned int integerTolerance );

static TA_RetCode CallTestFunction( RangeTestFunction testFunction,
                                    TA_Integer    startIdx,
                                    TA_Integer    endIdx,
                                    TA_Real      *outputBuffer,
                                    TA_Integer   *outputBufferInt,
                                    TA_Integer   *outBegIdx,
                                    TA_Integer   *outNbElement,
                                    TA_Integer   *lookback,
                                    void         *opaqueData,
                                    unsigned int  outputNb,
                                    unsigned int *isOutputInteger );

/**** Local variables definitions.     ****/
/* None */

/**** Global functions definitions.   ****/
static int ta_g_val = 0;
static const char *ta_g_wheel = "-\\|/";
void showFeedback()
{
   if( ta_g_wheel[ta_g_val] == '\0' )
      ta_g_val = 0;
   putchar('\b');
   putchar(ta_g_wheel[ta_g_val]);
   fflush(stdout);
   ta_g_val++;
}

void hideFeedback()
{
   putchar('\b');
   fflush(stdout);
   ta_g_val = 0;
}

ErrorNumber allocLib()
{
   TA_RetCode retCode;

   /* Initialize the library. */
   retCode = TA_Initialize();
   if( retCode != TA_SUCCESS )
   {
      printf( "TA_Initialize failed [%d]\n", retCode );
      return TA_TESTUTIL_INIT_FAILED;
   }

   return TA_TEST_PASS;
}

ErrorNumber freeLib()
{
   TA_RetCode retCode;

   /* For testing purpose */
   /* TA_FATAL_RET( "Test again", 100, 200, 0 ); */

   retCode = TA_Shutdown();
   if( retCode != TA_SUCCESS )
   {
      printf( "TA_Shutdown failed [%d]\n", retCode );
      return TA_TESTUTIL_SHUTDOWN_FAILED;
   }

   return TA_TEST_PASS;
}

void reportError( const char *str, TA_RetCode retCode )
{
   TA_RetCodeInfo retCodeInfo;

   TA_SetRetCodeInfo( retCode, &retCodeInfo );

   printf( "%s,%d==%s\n", str, retCode, retCodeInfo.enumStr );
   printf( "[%s]\n", retCodeInfo.infoStr );
}

/* Need to be called only once. */
void initGlobalBuffer( void )
{
   gBuffer[0].in   = &buf[0][0][TA_BUF_PREFIX];
   gBuffer[0].out0 = &buf[0][1][TA_BUF_PREFIX];
   gBuffer[0].out1 = &buf[0][2][TA_BUF_PREFIX];
   gBuffer[0].out2 = &buf[0][3][TA_BUF_PREFIX];

   gBuffer[1].in   = &buf[1][0][TA_BUF_PREFIX];
   gBuffer[1].out0 = &buf[1][1][TA_BUF_PREFIX];
   gBuffer[1].out1 = &buf[1][2][TA_BUF_PREFIX];
   gBuffer[1].out2 = &buf[1][3][TA_BUF_PREFIX];

   gBuffer[2].in   = &buf[2][0][TA_BUF_PREFIX];
   gBuffer[2].out0 = &buf[2][1][TA_BUF_PREFIX];
   gBuffer[2].out1 = &buf[2][2][TA_BUF_PREFIX];
   gBuffer[2].out2 = &buf[2][3][TA_BUF_PREFIX];

   gBuffer[3].in   = &buf[3][0][TA_BUF_PREFIX];
   gBuffer[3].out0 = &buf[3][1][TA_BUF_PREFIX];
   gBuffer[3].out1 = &buf[3][2][TA_BUF_PREFIX];
   gBuffer[3].out2 = &buf[3][3][TA_BUF_PREFIX];

   gBuffer[4].in   = &buf[4][0][TA_BUF_PREFIX];
   gBuffer[4].out0 = &buf[4][1][TA_BUF_PREFIX];
   gBuffer[4].out1 = &buf[4][2][TA_BUF_PREFIX];
   gBuffer[4].out2 = &buf[4][3][TA_BUF_PREFIX];
}

/* Will set some values in the buffers allowing
 * to detect later if the function is writing
 * out-of-bound (and to make sure the
 * function is writing exactly the number
 * of values it pretends to do).
 */
void clearAllBuffers( void )
{
   unsigned int i,j,k;

   for( i=0; i < NB_GLOBAL_BUFFER; i++ )
   {
      for( j=0; j < TA_NB_OUT_IN; j++ )
      {
         for( k=0; k < TA_BUF_PREFIX; k++ )
            buf[i][j][k] = RESV_PATTERN_PREFIX;
         for( ; k < TA_BUF_SIZE; k++ )
            buf[i][j][k] = RESV_PATTERN_SUFFIX;
      }
   }
}

void setInputBuffer( unsigned int i, const TA_Real *data, unsigned int nbElement )
{
   unsigned int j;
   for( j=0; j < nbElement; j++ )
      buf[i][0][j+TA_BUF_PREFIX] = data[j];
}

void setInputBufferValue( unsigned int i, const TA_Real data, unsigned int nbElement )
{
   unsigned int j;
   for( j=0; j < nbElement; j++ )
      buf[i][0][j+TA_BUF_PREFIX] = data;

}

/* Check that a buffer (within a TestBuffer) is not containing
 * NAN (or any reserved "impossible" value) within the specified
 * range (it also checks that all out-of-bound values are untouch).
 *
 * Return 1 on success.
 */
ErrorNumber checkForNAN( const TA_Real *buffer,
                         unsigned int nbElement )
{
   unsigned int i;
   unsigned int idx;

   const TA_Real *theBuffer;
   theBuffer = buffer - TA_BUF_PREFIX;

   /* Check that the prefix are all still untouch. */
   for( idx=0; idx < TA_BUF_PREFIX; idx++ )
   {
      if( theBuffer[idx] != RESV_PATTERN_PREFIX )
      {
         printf( "Fail: Out of range writing in prefix buffer (%d,%f)\n", idx, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_0;
      }
   }

   if( nbElement > MAX_NB_TEST_ELEMENT )
   {
       printf( "Fail: outNbElement is out of range 0 (%d)\n", nbElement );
       return TA_TEST_TFRR_NB_ELEMENT_OUT_OF_RANGE;
   }

   /* Check that no NAN (or reserved "impossible" value) exist
    * in the specified range.
    */
   for( i=0; i < nbElement; i++,idx++ )
   {
      /* TODO Add back some nan/inf checking
      if( trio_isnan(theBuffer[idx]) )
      {
         printf( "Fail: Not a number find within the data (%d,%f)\n", i, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_1;
      }

      if( trio_isinf(theBuffer[idx]) )
      {
         printf( "Fail: Not a number find within the data (%d,%f)\n", i, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_2;
      }*/

      if( theBuffer[idx] == RESV_PATTERN_PREFIX )
      {
         printf( "Fail: Not a number find within the data (%d,%f)\n", i, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_3;
      }

      if( theBuffer[idx] == RESV_PATTERN_SUFFIX )
      {
         printf( "Fail: Not a number find within the data (%d,%f)\n", i, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_4;
      }
   }

   /* Make sure that the remaining of the buffer is untouch. */
   for( ; idx < TA_BUF_SIZE; idx++ )
   {
      if( theBuffer[idx] != RESV_PATTERN_SUFFIX )
      {
         printf( "Fail: Out of range writing in suffix buffer (%d,%f)\n", idx, theBuffer[idx] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_5;
      }

      idx++;
   }

   /* Make sure the global memory guard are untouch. */
   if( memoryGuard1 != RESV_PATTERN_MEMGUARD_1 )
   {
      printf( "Fail: MemoryGuard1 have been modified (%f,%f)\n", memoryGuard1, RESV_PATTERN_MEMGUARD_1 );
      return TA_TEST_TFRR_OVERLAP_OR_NAN_6;
   }

   if( memoryGuard2 != RESV_PATTERN_MEMGUARD_2 )
   {
      printf( "Fail: MemoryGuard2 have been modified (%f,%f)\n", memoryGuard2, RESV_PATTERN_MEMGUARD_2 );
      return TA_TEST_TFRR_OVERLAP_OR_NAN_7;
   }

   /* Everything looks good! */
   return TA_TEST_PASS;
}

/* Return 1 on success */
ErrorNumber checkSameContent( TA_Real *buffer1,
                              TA_Real *buffer2 )
{
   const TA_Real *theBuffer1;
   const TA_Real *theBuffer2;

   unsigned int i;

   theBuffer1 = buffer1 - TA_BUF_PREFIX;
   theBuffer2 = buffer2 - TA_BUF_PREFIX;

   for( i=0; i < TA_BUF_SIZE; i++ )
   {
        /* TODO Add back nan/inf checking
          (!trio_isnan(theBuffer1[i])) &&
          (!trio_isinf(theBuffer1[i])) &&
         */

      if( (theBuffer1[i] != RESV_PATTERN_SUFFIX) &&
          (theBuffer1[i] != RESV_PATTERN_PREFIX) )
      {

         if(!TA_REAL_EQ( theBuffer1[i], theBuffer2[i], 0.000001))
         {
            printf( "Fail: Large difference found between two value expected identical (%f,%f,%d)\n",
                     theBuffer1[i], theBuffer2[i], i );
            return TA_TEST_TFRR_CHECK_SAME_CONTENT;
         }
      }
   }

   return TA_TEST_PASS;
}

ErrorNumber checkDataSame( const TA_Real *data,
                           const TA_Real *originalInput,
                           unsigned int nbElement )
{
   unsigned int i;
   ErrorNumber errNb;

   errNb = checkForNAN( data, nbElement );

   if( errNb != TA_TEST_PASS )
       return errNb;

   if( nbElement > MAX_NB_TEST_ELEMENT )
   {
       printf( "Fail: outNbElement is out of range 1 (%d)\n", nbElement );
       return TA_TEST_TFRR_NB_ELEMENT_OUT_OF_RANGE;
   }

   for( i=0; i < nbElement; i++ )
   {
      if( originalInput[i] != data[i] )
      {
         printf( "Fail: Data was wrongly modified (%f,%f,%d)\n",
                 originalInput[i],
                 data[i], i );
         return TA_TEST_TFRR_INPUT_HAS_BEEN_MODIFIED;
      }
   }

   return TA_TEST_PASS;
}

ErrorNumber checkExpectedValue( const TA_Real *data,
                                TA_RetCode retCode, TA_RetCode expectedRetCode,
                                unsigned int outBegIdx, unsigned int expectedBegIdx,
                                unsigned int outNbElement, unsigned int expectedNbElement,
                                TA_Real oneOfTheExpectedOutReal,
                                unsigned int oneOfTheExpectedOutRealIndex )
{
   if( retCode != expectedRetCode )
   {
      printf( "Fail: RetCode %d different than expected %d\n", retCode, expectedRetCode );
      return TA_TESTUTIL_TFRR_BAD_RETCODE;
   }

   if( retCode != TA_SUCCESS )
   {
      /* An error did occured, but it
       * was expected. No need to go
       * further.
       */
      return TA_TEST_PASS;
   }

   if( outNbElement > MAX_NB_TEST_ELEMENT )
   {
       printf( "Fail: outNbElement is out of range 2 (%d)\n", outNbElement );
       return TA_TEST_TFRR_NB_ELEMENT_OUT_OF_RANGE;
   }


   /* Make sure the range of output does not contains NAN. */
   /* TODO Add back nan/inf checking
   for( i=0; i < outNbElement; i++ )
   {
      if( trio_isnan(data[i]) )
      {
         printf( "Fail: Not a number find within the data (%d,%f)\n", i, data[i] );
         return TA_TEST_TFRR_OVERLAP_OR_NAN_3;
      }
   }*/

   /* Verify that the expected output is there. */

   if( outNbElement != expectedNbElement )
   {
      printf( "Fail: outNbElement expected %d but got %d\n",
              expectedNbElement, outNbElement );
      return TA_TESTUTIL_TFRR_BAD_OUTNBELEMENT;
   }

   if( expectedNbElement > 0 )
   {
      if( !TA_REAL_EQ( oneOfTheExpectedOutReal, data[oneOfTheExpectedOutRealIndex], 0.01 ) )
      {
         printf( "Fail: For index %d, Expected value = %f but calculate value is %f\n",
                 oneOfTheExpectedOutRealIndex,
                 oneOfTheExpectedOutReal,
                 data[oneOfTheExpectedOutRealIndex] );
         return TA_TESTUTIL_TFRR_BAD_CALCULATION;
      }

      if( expectedBegIdx != outBegIdx )
      {
         printf( "Fail: outBegIdx expected %d but got %d\n", expectedBegIdx, outBegIdx );
         return TA_TESTUTIL_TFRR_BAD_BEGIDX;
      }
   }

   /* Succeed. */
   return TA_TEST_PASS;
}


ErrorNumber doRangeTest( RangeTestFunction testFunction,
                         TA_FuncUnstId unstId,
                         void *opaqueData,
                         unsigned int nbOutput,
                         unsigned int integerTolerance )
{
   unsigned int outputNb;
   ErrorNumber errNb;

   /* Test all the outputs individually. */
   for( outputNb=0; outputNb < nbOutput; outputNb++ )
   {
      errNb = doRangeTestForOneOutput( testFunction,
                                       unstId,
                                       opaqueData,
                                       outputNb,
                                       integerTolerance );
      if( errNb != TA_TEST_PASS )
      {
         printf( "Failed: For output #%d of %d\n", outputNb+1, nbOutput );
         return errNb;
      }
   }

   return TA_TEST_PASS;
}

void printRetCode( TA_RetCode retCode )
{
   TA_RetCodeInfo retCodeInfo;

   TA_SetRetCodeInfo( retCode, &retCodeInfo );
   printf( "\nFailed: ErrorCode %d=%s:[%s]\n", retCode,
           retCodeInfo.enumStr,
           retCodeInfo.infoStr );
}



/**** Local functions definitions.     ****/
static ErrorNumber doRangeTestForOneOutput( RangeTestFunction testFunction,
                                            TA_FuncUnstId unstId,
                                            void *opaqueData,
                                            unsigned int outputNb,
                                            unsigned int integerTolerance )
{
   TA_RetCode retCode;
   TA_Integer refOutBeg, refOutNbElement, refLookback;
   TA_Integer fixSize;
   TA_Real *refBuffer;
   TA_Integer *refBufferInt;
   ErrorNumber errNb;
   TA_Integer unstablePeriod, temp;
   unsigned int outputIsInteger;

   showFeedback();

   /* Caculate the whole range. This is going
    * to be the reference for all subsequent test.
    */
   refBuffer = (TA_Real *)TA_Malloc( MAX_RANGE_SIZE * sizeof( TA_Real ) );

   if( !refBuffer )
      return TA_TESTUTIL_DRT_ALLOC_ERR;

   refBufferInt = (TA_Integer *)TA_Malloc( MAX_RANGE_SIZE * sizeof( TA_Integer ) );

   if( !refBufferInt )
   {
      TA_Free( refBuffer );
      return TA_TESTUTIL_DRT_ALLOC_ERR;
   }

   if( unstId != TA_FUNC_UNST_NONE )
   {
      /* Caller wish to test for a range of unstable
       * period values. But the reference is calculated
       * on the whole range by keeping that unstable period
       * to zero.
       */
      TA_SetUnstablePeriod( unstId, 0 );
   }

   outputIsInteger = 0;
   retCode = CallTestFunction( testFunction, 0, MAX_RANGE_END, refBuffer, refBufferInt,
                           &refOutBeg, &refOutNbElement, &refLookback,
                           opaqueData, outputNb, &outputIsInteger );

   if( retCode != TA_SUCCESS )
   {
      printf( "Fail: doRangeTest whole range failed (%d)\n", retCode );
      TA_Free( refBuffer );
      TA_Free( refBufferInt );
      return TA_TESTUTIL_DRT_REF_FAILED;
   }

   /* When calculating for the whole range, the lookback and the
    * refOutBeg are supppose to be equal.
    */
   if( refLookback != refOutBeg )
   {
      printf( "Fail: doRangeTest refLookback != refOutBeg (%d != %d)\n", refLookback, refOutBeg );
      TA_Free( refBuffer );
      TA_Free( refBufferInt );
      return TA_TESTUTIL_DRT_LOOKBACK_INCORRECT;
   }

   temp = MAX_RANGE_SIZE-refLookback;
   if( temp != refOutNbElement )
   {
      printf( "Fail: doRangeTest either refOutNbElement or refLookback bad (%d,%d)\n", temp, refOutNbElement );
      TA_Free( refBuffer );
      TA_Free( refBufferInt );
      return TA_TESTUTIL_DRT_REF_OUTPUT_INCORRECT;
   }

   /* Calculate each value ONE by ONE and make sure it is identical
    * to the reference.
    *
    * Then repeat the test but calculate TWO by TWO and so on...
    */
   for( fixSize=1; fixSize <= MAX_RANGE_SIZE; fixSize++ )
   {
      /* When a function has an unstable period, verify some
       * unstable period between 0 and MAX_RANGE_SIZE.
       */
      if( unstId == TA_FUNC_UNST_NONE )
      {
         errNb = doRangeTestFixSize( testFunction, opaqueData,
                                     refOutBeg, refOutNbElement, refLookback,
                                     refBuffer, refBufferInt,
                                     unstId, fixSize, outputNb, integerTolerance );
         if( errNb != TA_TEST_PASS)
         {
            TA_Free( refBuffer );
            TA_Free( refBufferInt );
            return errNb;
         }
      }
      else
      {
         for( unstablePeriod=0; unstablePeriod <= MAX_RANGE_SIZE; unstablePeriod++ )
         {
            TA_SetUnstablePeriod( unstId, unstablePeriod );

            errNb = doRangeTestFixSize( testFunction, opaqueData,
                                        refOutBeg, refOutNbElement, refLookback,
                                        refBuffer, refBufferInt,
                                        unstId, fixSize, outputNb, integerTolerance );
            if( errNb != TA_TEST_PASS)
            {
               printf( "Fail: Using unstable period %d\n", unstablePeriod );
               TA_Free( refBuffer );
               TA_Free( refBufferInt );
               return errNb;
            }

            /* Randomly skip the test of some unstable period (limit case are
             * always tested though).
             */
            if( (unstablePeriod > 5) && (unstablePeriod < 240) )
            {
               /* Randomly skips from 0 to 239 tests. Never
                * make unstablePeriod exceed 240.
                */
               temp = (rand() % 240);
               unstablePeriod += temp;
               if( unstablePeriod > 240 )
                  unstablePeriod = 240;
            }
         }

         /* Because the tests with an unstable period are very intensive
          * and kinda repetitive, skip the test of some fixSize (limit
          * case are always tested though).
          */
         if( (fixSize > 5) && (fixSize < 240) )
         {
            /* Randomly skips from 0 to 239 tests. Never
             * make fixSize exceed 240.
             */
            temp = (rand() % 239);
            fixSize += temp;
            if( fixSize > 240 )
               fixSize = 240;
         }
      }
   }

   TA_Free( refBuffer );
   TA_Free( refBufferInt );
   return TA_TEST_PASS;
}

static ErrorNumber doRangeTestFixSize( RangeTestFunction testFunction,
                                       void *opaqueData,
                                       TA_Integer refOutBeg,
                                       TA_Integer refOutNbElement,
                                       TA_Integer refLookback,
                                       const TA_Real *refBuffer,
                                       const TA_Integer *refBufferInt,
                                       TA_FuncUnstId unstId,
                                       TA_Integer fixSize,
                                       unsigned int outputNb,
                                       unsigned int integerTolerance )
{
   TA_RetCode retCode;
   TA_Real *outputBuffer;
   TA_Real val1, val2;
   TA_Integer i, temp;
   TA_Integer outputBegIdx, outputNbElement, lookback;
   TA_Integer startIdx, endIdx, relativeIdx, outputSizeByOptimalLogic;
   TA_Integer *outputBufferInt;
   unsigned int outputIsInteger;

   (void)refLookback;

   /* Allocate the output buffer (+prefix and suffix memory guard). */
   outputBuffer = (TA_Real *)TA_Malloc( (fixSize+2) * sizeof( TA_Real ) );

   if( !outputBuffer )
      return TA_TESTUTIL_DRT_ALLOC_ERR;

   outputBufferInt = (TA_Integer *)TA_Malloc( (fixSize+2) * sizeof( TA_Integer ) );

   if( !refBufferInt )
   {
      TA_Free( outputBuffer );
      return TA_TESTUTIL_DRT_ALLOC_ERR;
   }

   outputBuffer[0] = RESV_PATTERN_PREFIX;
   outputBuffer[fixSize+1] = RESV_PATTERN_SUFFIX;

   outputBufferInt[0] = RESV_PATTERN_PREFIX_INT;
   outputBufferInt[fixSize+1] = RESV_PATTERN_SUFFIX_INT;

   /* Initialize the outputs with improbable values. */
   for( i=1; i <= fixSize; i++ )
   {
      outputBuffer[i] = RESV_PATTERN_IMPROBABLE;
      outputBufferInt[i] = RESV_PATTERN_IMPROBABLE_INT;
   }

   /* Test for a large number of possible startIdx */
   for( startIdx=0; startIdx <= (MAX_RANGE_SIZE-fixSize); startIdx++ )
   {
      /* Call the TA function. */
      endIdx = startIdx+fixSize-1;
      retCode = CallTestFunction( testFunction, startIdx, endIdx,
                              &outputBuffer[1], &outputBufferInt[1],
                              &outputBegIdx, &outputNbElement, &lookback,
                              opaqueData, outputNb, &outputIsInteger );

      if( retCode != TA_SUCCESS )
      {
          /* No call shall never fail here. When the range
           * is "out-of-range" the function shall still return
           * TA_SUCCESS with the outNbElement equal to zero.
           */
         printf( "Fail: doRangeTestFixSize testFunction return error=(%d) (%d,%d)\n", retCode, fixSize, startIdx );
         TA_Free( outputBuffer );
         TA_Free( outputBufferInt );
         return TA_TESTUTIL_DRT_RETCODE_ERR;
      }
      else
      {
         /* Possible startIdx gap of the output shall be always the
          * same regardless of the range.
          */
         if( outputNbElement == 0 )
         {
            /* Trap cases where there is no output. */
            if( (startIdx > lookback) || (endIdx > lookback) )
            {
               /* Whenever startIdx is greater than lookback, some data
                * shall be return. Same idea with endIdx.
                *
                * Note:
                *  some output will never start at the startIdx, particularly
                *  when a TA function have multiple output. Usually, the first output
                *  will be between startIdx/endIdx and other outputs may have a "gap"
                *  from the startIdx.
                *
                * Example:
                *    Stochastic %K is between startIdx/endIdx, but %D output will
                *    have less data because it is a MA of %K. A gap will then
                *    exist for the %D output.
                */
               printf( "Fail: doRangeTestFixSize data missing (%d,%d,%d)\n", startIdx, endIdx, lookback );

               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_MISSING_DATA;
            }
         }
         else
         {
            /* Some output was returned. Are the returned index correct? */
            if( (outputBegIdx < startIdx) || (outputBegIdx > endIdx) || (outputBegIdx < refOutBeg))
            {
               printf( "Fail: doRangeTestFixSize bad outBegIdx\n" );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_BAD_OUTBEGIDX;
            }

            if( (outputNbElement > fixSize) || (outputNbElement > refOutNbElement) )
            {
               printf( "Fail: doRangeTestFixSize Incorrect outputNbElement\n" );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free(  outputBuffer );
               return TA_TESTUTIL_DRT_BAD_OUTNBLEMENT;
            }

            /* Is the calculated lookback too high? */
            if( outputBegIdx < lookback )
            {
               printf( "Fail: doRangeTestFixSize Lookback calculation too high? (%d)\n", lookback );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_LOOKBACK_TOO_HIGH;
            }

            /* Is the output identical to the reference? */
            relativeIdx = outputBegIdx-refOutBeg;
            for( i=0; i < outputNbElement; i++ )
            {
               if( outputIsInteger )
               {
                  if( outputBufferInt[1+i] != refBufferInt[relativeIdx+i] )
                  {
                     printf( "Fail: doRangeTestFixSize diff data for idx=%d (%d,%d)\n", i,
                              outputBufferInt[1+i], refBufferInt[relativeIdx+i] );
                     printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
                     printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
                     TA_Free( outputBuffer );
                     TA_Free( outputBufferInt );
                     return TA_TESTUTIL_DRT_DATA_DIFF_INT;
                  }
               }
               else
               {
                  val1 = outputBuffer[1+i];
                  val2 = refBuffer[relativeIdx+i];
                  if( !dataWithinReasonableRange( val1, val2, i, unstId, integerTolerance ) )
                  {
                     printf( "Fail: doRangeTestFixSize diff data for idx=%d (%e,%e)\n", i, val1, val2 );
                     printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
                     printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
                     if( val1 != 0.0 )
                        printf( "Fail: Diff %g %%\n", ((val2-val1)/val1)*100.0 );
                     TA_Free( outputBuffer );
                     TA_Free( outputBufferInt );
                     return TA_TESTUTIL_DRT_DATA_DIFF;
                  }
               }

               /* Randomly skip the verification of some value. Limit
                * cases are always checked though.
                */
               if( outputNbElement > 30 )
               {
                  temp = outputNbElement-20;
                  if( (i > 20) && (i < temp) )
                  {
                     /* Randomly skips from 0 to 200 verification.
                      * Never make it skip the last 20 values.
                      */
                     i += (rand() % 200);
                     if( i > temp )
                        i = temp;
                  }
               }
            }

            /* Verify out-of-bound writing in the output buffer. */
            outputSizeByOptimalLogic = max(lookback,startIdx);
            if( outputSizeByOptimalLogic > endIdx )
               outputSizeByOptimalLogic = 0;
            else
               outputSizeByOptimalLogic = endIdx-outputSizeByOptimalLogic+1;

            if( (fixSize != outputNbElement) && (outputBuffer[1+outputSizeByOptimalLogic] != RESV_PATTERN_IMPROBABLE) )
            {
               printf( "Fail: doRangeTestFixSize out-of-bound output (%e)\n", outputBuffer[1+outputSizeByOptimalLogic] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_OUT_OF_BOUND_OUT;
            }

            if( (fixSize != outputNbElement) && (outputBufferInt[1+outputSizeByOptimalLogic] != RESV_PATTERN_IMPROBABLE_INT) )
            {
               printf( "Fail: doRangeTestFixSize out-of-bound output  (%d)\n", outputBufferInt[1+outputSizeByOptimalLogic] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_OUT_OF_BOUND_OUT_INT;
            }

            /* Verify that the memory guard were preserved. */
            if( outputBuffer[0] != RESV_PATTERN_PREFIX )
            {
               printf( "Fail: doRangeTestFixSize bad RESV_PATTERN_PREFIX (%e)\n", outputBuffer[0] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_BAD_PREFIX;
            }

            if( outputBufferInt[0] != RESV_PATTERN_PREFIX_INT )
            {
               printf( "Fail: doRangeTestFixSize bad RESV_PATTERN_PREFIX_INT (%d)\n", outputBufferInt[0] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_BAD_PREFIX;
            }

            if( outputBuffer[fixSize+1] != RESV_PATTERN_SUFFIX )
            {
               printf( "Fail: doRangeTestFixSize bad RESV_PATTERN_SUFFIX (%e)\n", outputBuffer[fixSize+1] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_BAD_SUFFIX;
            }

            if( outputBufferInt[fixSize+1] != RESV_PATTERN_SUFFIX_INT )
            {
               printf( "Fail: doRangeTestFixSize bad RESV_PATTERN_SUFFIX_INT (%d)\n", outputBufferInt[fixSize+1] );
               printf( "Fail: doRangeTestFixSize (%d,%d,%d,%d,%d)\n", startIdx, endIdx, outputBegIdx, outputNbElement, fixSize );
               printf( "Fail: doRangeTestFixSize refOutBeg,refOutNbElement (%d,%d)\n", refOutBeg, refOutNbElement );
               TA_Free( outputBuffer );
               TA_Free( outputBufferInt );
               return TA_TESTUTIL_DRT_BAD_SUFFIX;
            }

            /* Clean-up for next test. */
            if( outputIsInteger )
            {
               for( i=1; i <= fixSize; i++ )
                  outputBufferInt[i] = RESV_PATTERN_IMPROBABLE_INT;
            }
            else
            {
               for( i=1; i <= fixSize; i++ )
                  outputBuffer[i] = RESV_PATTERN_IMPROBABLE;
            }
         }

         /* Skip some startIdx at random. Limit case are still
          * tested though.
          */
         if( (startIdx > 30) && ((startIdx+100) <= (MAX_RANGE_SIZE-fixSize)) )
         {
            /* Randomly skips from 40 to 100 tests. */
            temp = (rand() % 100)+40;
            startIdx += temp;
         }
      }

      /* Loop and move forward for the next startIdx to test. */
   }

   TA_Free( outputBuffer );
   TA_Free( outputBufferInt );
   return TA_TEST_PASS;
}

/* This function compares two value.
 * The value is determined to be equal
 * if it is within a certain error range.
 */
static int dataWithinReasonableRange( TA_Real val1, TA_Real val2,
                                      unsigned int outputPosition,
                                      TA_FuncUnstId unstId,
                                      unsigned int integerTolerance )
{
   TA_Real difference, tolerance, temp;
   unsigned int val1_int, val2_int, tempInt, periodToIgnore;

   if( integerTolerance == TA_DO_NOT_COMPARE )
      return 1; /* Don't compare, says that everything is fine */

   /* If the function does not have an unstable period,
    * the compared value shall be identical.
    *
    * Because the algo may vary slightly allow for
    * a small epsilon error because of the nature
    * of floating point operations.
    */
   if( unstId == TA_FUNC_UNST_NONE )
      return TA_REAL_EQ( val1, val2, 0.000000001 );

   /* In the context of the TA functions, all value
    * below 0.00001 are considered equal to zero and
    * are considered to be equal within a reasonable range.
    * (the percentage difference might be large, but
    *  unsignificant at that level, so no tolerance
    *  check is being done).
    */
    if( (val1 < 0.00001) && (val2 < 0.00001) )
      return 1;

   /* When the function is unstable, the comparison
    * tolerate at first a large difference.
    *
    * As the number of "outputPosition" is higher
    * the tolerance is reduced.
    *
    * In the same way, as the unstable period
    * increase, the tolerance is reduced (that's
    * what the unstable period is for... reducing
    * difference).
    *
    * When dealing with an unstable period, the
    * first 100 values are ignored.
    *
    * Following 100, the tolerance is
    * progressively reduced as follow:
    *
    *   1   == 0.5/1   == 50  %
    *   2   == 0.5/2   == 25  %
    *   ...
    *   100 == 0.5/100 == 0.005 %
    *   ...
    *
    * Overall, the following is a fair estimation:
    *  When using a unstable period of 200, you
    *  can expect the output to not vary more
    *  than 0.005 %
    *
    * The logic is sligthly different if the
    * output are rounded integer, but it is
    * the same idea.
    *
    * The following describe the special meaning of
    * the integerTolerance:
    *
    * Value 10      -> A tolerance of 1/10  is used.
    *
    * Value 100     -> A tolerance of 1/100 is used.
    *
    * Value 1000    -> A tolerance of 1/1000 is used.
    *
    * Value 360     -> Useful when the output are
    *                  degrees. In that case, a fix
    *                  tolerance of 1 degree is used.
    *
    * Value TA_DO_NOT_COMPARE ->
    *                  Indicate that NO COMPARISON take
    *                  place. This is useful for functions
    *                  that cannot be compare when changing
    *                  the range (like the accumulative
    *                  algorithm used for TA_AD and TA_ADOSC).
    */


   /* Some functions requires a longer unstable period.
    * These are trap here.
    */
   switch( unstId )
   {
   case TA_FUNC_UNST_T3:
      periodToIgnore = 200;
      break;
   default:
      periodToIgnore = 100;
      break;
   }

   if( integerTolerance == 1000 )
   {
      /* Check for no difference of more
       * than 1/1000
       */
      if( val1 > val2 )
         difference = (val1-val2);
      else
         difference = (val2-val1);

      difference *= 1000.0;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else if( (int)difference > 1 )
      {
         printf( "\nFail: Value diffferent by more than 1/1000 (%f)\n", difference );
         return 0;
      }
   }
   else if( integerTolerance == 100 )
   {
      /* Check for no difference of more
       * than 1/1000
       */
      if( val1 > val2 )
         difference = (val1-val2);
      else
         difference = (val2-val1);

      difference *= 100.0;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else if( (int)difference > 1 )
      {
         printf( "\nFail: Value diffferent by more than 1/100 (%f)\n", difference );
         return 0;
      }
   }
   else if( integerTolerance == 10 )
   {
      /* Check for no difference of more
       * than 1/1000
       */
      if( val1 > val2 )
         difference = (val1-val2);
      else
         difference = (val2-val1);

      difference *= 10.0;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else if( (int)difference > 1 )
      {
         printf( "\nFail: Value diffferent by more than 1/10 (%f)\n", difference );
         return 0;
      }
   }
   else if( integerTolerance == 360 )
   {
      /* Check for no difference of no more
       * than 10% when the value is higher than
       * 1 degree.
       *
       * Difference of less than 1 degree are not significant.
       */
      val1_int = (unsigned int)val1;
      val2_int = (unsigned int)val2;
      if( val1_int > val2_int )
         tempInt = val1_int - val2_int;
      else
         tempInt = val2_int - val1_int;

      if( val1 > val2 )
         difference = (val1-val2)/val1;
      else
         difference = (val2-val1)/val2;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else if( (tempInt > 1) && (difference > 0.10) )
      {
         printf( "\nFail: Value diffferent by more than 10 percent over 1 degree (%d)\n", tempInt );
         return 0;
      }
   }
   else if( integerTolerance )
   {
      /* Check that the integer part of the value
       * is not different more than the specified
       * integerTolerance.
       */
      val1_int = (unsigned int)val1;
      val2_int = (unsigned int)val2;
      if( val1_int > val2_int )
         tempInt = val1_int - val2_int;
      else
         tempInt = val2_int - val1_int;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else if( temp < 100 )
      {
         if( tempInt >= 3*integerTolerance )
         {
            printf( "\nFail: Value out of 3*tolerance range (%d,%d)\n", tempInt, integerTolerance );
            return 0; /* Value considered different */
         }
      }
      else if( temp < 150 )
      {
         if( tempInt >= 2*integerTolerance )
         {
            printf( "\nFail: Value out of 2*tolerance range (%d,%d)\n", tempInt, integerTolerance );
            return 0; /* Value considered different */
         }
      }
      else if( temp < 200 )
      {
         if( tempInt >= integerTolerance )
         {
            printf( "\nFail: Value out of tolerance range (%d,%d)\n", tempInt, integerTolerance );
            return 0; /* Value considered different */
         }
      }
      else if( tempInt >= 1 )
      {
         printf( "\nFail: Value not equal (difference is %d)\n", tempInt );
         return 0; /* Value considered different */
      }
   }
   else
   {
      if( val1 > val2 )
         difference = (val1-val2)/val1;
      else
         difference = (val2-val1)/val2;

      temp = outputPosition+TA_GetUnstablePeriod(unstId)+1;
      if( temp <= periodToIgnore )
      {
         /* Pretend it is fine. */
         return 1;
      }
      else
      {
         temp -= periodToIgnore;
         tolerance = 0.5/temp;
      }

      if( difference > tolerance )
      {
         printf( "\nFail: Value out of tolerance range (%g,%g)\n", difference, tolerance );
         return 0; /* Out of tolerance... values are not equal. */
      }
   }

   return 1; /* Value equal within tolerance. */
}

static TA_RetCode CallTestFunction( RangeTestFunction testFunction,
                                    TA_Integer    startIdx,
                                    TA_Integer    endIdx,
                                    TA_Real      *outputBuffer,
                                    TA_Integer   *outputBufferInt,
                                    TA_Integer   *outBegIdx,
                                    TA_Integer   *outNbElement,
                                    TA_Integer   *lookback,
                                    void         *opaqueData,
                                    unsigned int  outputNb,
                                    unsigned int *isOutputInteger )
{
   /* Call the function and do profiling. */
   TA_RetCode retCode;
   double clockDelta;

#ifdef WIN32
   LARGE_INTEGER startClock;
   LARGE_INTEGER endClock;
#else
   clock_t startClock;
   clock_t endClock;
#endif

#ifdef WIN32
   QueryPerformanceCounter(&startClock);
#else
   startClock = clock();
#endif
	retCode = testFunction( startIdx,
                  endIdx,
                  outputBuffer,
                  outputBufferInt,
                  outBegIdx,
                  outNbElement,
                  lookback,
                  opaqueData,
                  outputNb,
                  isOutputInteger );

	/* Profile only functions producing at least 20 values. */
	if( *outNbElement < 20 )
	{
		return retCode;
	}

#ifdef WIN32
   QueryPerformanceCounter(&endClock);
   clockDelta = (double)((__int64)endClock.QuadPart - (__int64) startClock.QuadPart);
#else
   endClock = clock();
   clockDelta = (double)(endClock - startClock);
#endif

   if( clockDelta <= 0 )
   {
	   insufficientClockPrecision = 1;
   }
   else
   {
      if( clockDelta > worstProfiledCall )
         worstProfiledCall = clockDelta;
      timeInProfiledCall += clockDelta;
      nbProfiledCall++;
   }

   return retCode;
}
