/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * 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.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "mas_stats.h"

#define WINSIZE_LIMIT            8192

int32
masc_stats_init( struct mas_stats* s, int win_size, int compute_flags )
{
    int length;
    
    if ( s == 0 )
        return mas_error(MERR_NULLPTR);
    if ( win_size < 0 || win_size > WINSIZE_LIMIT )
        return mas_error(MERR_INVALID);

    s->win_size = win_size;
    length = sizeof (double) * s->win_size;

    if ( length > 0 )
        s->val = masc_rtalloc( length );
    
    masc_stats_reset( s, TRUE );
    s->compute = compute_flags;
    
    return 0;
}

int32
masc_stats_update( struct mas_stats* s, double val )
{
    s->valid = 0;
    
    if ( s->compute & MASC_STATS_WIN_MEAN )
    {
        /** Keep a running summation.  For each call once the window
            is full, the oldest time is subtracted and the new time is
            added. Floating point round-off errors might eventually
            catch up with us though, so we may want to sum over the
            whole window every once in a while.

            NOTE: I've done this for 1,0E+6 trials and the error is
            minimal, but it WILL grow with the size of the window and
            with the sizes of the values you're working with -- rocko,
            13 NOV 2002. */
        
        /* subtract old measurement from this slot */
        s->win_sum -= s->val[s->i];

        /* set new measurement */
        s->val[s->i] = val;
        
        /* increment window element */
        s->i = ( s->i + 1 ) % s->win_size;
        if ( s->N < s->win_size )
            s->N++;

        /* add to windowed summation */
        s->win_sum += val;
        
        /* mean is the sum normalized the no. of entries in the window
         * array. */
        s->win_mean = s->win_sum / s->N;
        s->valid |= MASC_STATS_WIN_MEAN;
    }

    /* compute total mean */
    if ( s->compute & MASC_STATS_MEAN )
    {
        s->count++;
        s->sum += val;

        /* Compute the mean */
        s->mean = s->sum / s->count;
        s->valid |= MASC_STATS_MEAN;
    }

    /* compute total mean */
    if ( s->compute & MASC_STATS_MINMAX )
    {
        if ( s->count == 1 )
        {
            s->max = val;
            s->min = val;
        }
        else
        {
            if ( val > s->max ) s->max = val;
            if ( val < s->min ) s->min = val;
        }
        s->valid |= MASC_STATS_MINMAX;
    }

    return 0;
}

void
masc_stats_cleanup( struct mas_stats* s )
{
    if ( s->win_size )
    {
        if ( s->val )
            masc_rtfree( s->val );
    }

    memset( s, 0, sizeof *s );
    return;
}

void
masc_stats_reset( struct mas_stats* s, int reset_flags_bool )
{
    double* val;
    int     win_size;
    int     length;
    int     compute;

    /* make temporary copies */
    val = s->val;
    win_size = s->win_size;
    compute = s->compute;
    
    length = sizeof (double) * s->win_size;

    /* zero the entire structure */
    memset( s, 0, sizeof *s );

    /* and reset from temporary copies */
    s->val = val;
    s->win_size = win_size;

    /* zero the arrays, if the window size is > 0 */
    if ( length > 0 )
    {
        if ( val != 0 )
            memset( val, 0, length );


        /* defaults */
        if ( ! reset_flags_bool )
            s->compute = compute;
        else
            s->compute = MASC_STATS_WIN_MEAN;
    }
    else
    {
        /* Since there's no window size, we assume that we're only
         * computing total statistics, unless we can re-use the
         * compute flags. */
        if ( ! reset_flags_bool )
            s->compute = compute;
        else
            s->compute = MASC_STATS_MEAN | MASC_STATS_MINMAX;
    }
    
    return;
}

int32
masc_stats_recompute_window( struct mas_stats* s )
{
    int32 err;
    
    if ( s->compute & MASC_STATS_WIN_MEAN )
    {
        err = masc_stats_compute_win_mean( s );
        /* ignore error */
    }
    

    if ( s->compute & MASC_STATS_WIN_ERROR )
    {
        err = masc_stats_compute_win_error( s );
        /* ignore error */
    }

    if ( s->compute & MASC_STATS_WIN_MINMAX )
    {
        err = masc_stats_compute_win_minmax( s );
        /* ignore error */
    }

    return 0;
}

int32
masc_stats_compute_win_mean( struct mas_stats* s )
{
    int i;
    
    s->win_sum = 0.0;

    if ( s->N > 0 )
    {
        for ( i=0; i<s->N; i++ )
        {
            /* add to windowed summation */
            s->win_sum += s->val[i];
        }
        
        /* mean is the sum normalized the no. of entries in the window
         * array. */
        s->win_mean = s->win_sum / s->N;
    }
    
    return 0;
}

int32
masc_stats_compute_win_error( struct mas_stats* s )
{
    int i;
    double sq_error;

    if ( s->N <= 1 )
        return mas_error(MERR_INVALID);

    /* the windowed mean must be valid to get a valid standard deviation */
    if ( ! (s->valid & MASC_STATS_WIN_MEAN) )
        masc_stats_compute_win_mean( s );
    
    s->win_sq_error_sum = 0.0;
    
    for ( i=0; i<s->N; i++ )
    {
        /* compute squared error ( x - M )^2 */
        sq_error = s->val[i] - s->win_mean;
        sq_error *= sq_error;
        s->win_sq_error_sum += sq_error;
    }
    
    /* standard deviation is the square root of the normalized squared
       error sum */
    s->win_std_dev = sqrt(s->win_sq_error_sum / (s->N - 1));
    s->valid |= MASC_STATS_WIN_ERROR;

    return 0;
}

int32
masc_stats_compute_win_minmax( struct mas_stats* s )
{
    int i;
    
    if ( s->N > 0 )
    {
        s->win_min = s->win_max = s->val[0];
        
        for (i=0; i<s->N; i++)
        {
            if ( s->val[i] > s->win_max ) s->win_max = s->val[i];
            if ( s->val[i] < s->win_min ) s->win_min = s->val[i];
        }
        
        s->valid |= MASC_STATS_WIN_MINMAX;
    }
    
    return 0;
}

