/*
 *		  Unofficial release 1.3.0
 *			B I N G
 *
 * This file implements the data gathering and analysis algorithms
 * that are specific to bing.
 *
 */

/* $Id: bing_stats.c,v 1.9 1999/10/23 21:56:06 fgouget Exp $ */

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <float.h>

#include "bing_stats.h"

/* (!!) for debug, remove... */
#include "bing_misc.h"

/*
 * Now the bing stats
 */

int rtt_stats_init(rtt_stats_t *rtt_stats)
{
    rtt_stats->nb_samples=0;
    rtt_stats->nb_dropped=0;

    rtt_stats->nb_bits=0;

    rtt_stats->state=RTT_STATE_HINVALID;
    rtt_stats->min_rtt=DBL_MAX;
#ifdef _DEBUG
    rtt_stats->min_rtt_sav=DBL_MAX;
#endif
    rtt_stats->sum_rtt=0;
    rtt_stats->max_rtt=DBL_MIN;

    rtt_stats->samples_size=0;
    rtt_stats->samples=NULL;
    return 0;
}

int rtt_law_init(rtt_law_t *rtt_law, int nb_sizes)
{
    int s;

    rtt_law->rtt_stats=calloc(nb_sizes,sizeof(*rtt_law->rtt_stats));
    for (s=0;s<nb_sizes;s++) {
        rtt_stats_init(&rtt_law->rtt_stats[s]);
    }

    rtt_law->nb_samples=0;
    rtt_law->nb_dropped=0;

    rtt_law->nb_hinvalid=nb_sizes;
    rtt_law->nb_linvalid=0;
    rtt_law->nb_redo=0;

    linreg_init(&rtt_law->host_reg);
    linreg_init(&rtt_law->link_reg);

    return 0;
}

/* (!!) note that rtt_law_add is not updating the linear regression anymore */
int rtt_law_add(rtt_law_t* rtt_law, int index, int size, double rtt)
{
    /* Check the parameters */
    if (rtt_law==NULL) {
        errno=EINVAL;
        return -1;
    }

    if (size<0) {
        rtt_law->rtt_stats[index].nb_dropped++;
        rtt_law->nb_dropped++;
    } else {
        rtt_stats_t* rtt_stats;

        rtt_stats=&rtt_law->rtt_stats[index];
        if (rtt_stats->nb_bits==0) {
             rtt_stats->nb_bits=8*size;
        } else if (rtt_stats->nb_bits!=8*size) {
            /* (!!) oups, do something more, this is really wrong */
            printf("the packet size has changed: %d!=%d\n",8*size,rtt_stats->nb_bits);
            return -1;
        }

        /* Add this new sample */
#if 0
        if (rtt_stats->nb_samples==rtt_stats->samples_size) {
            rtt_stats->samples_size+=10;
            rtt_stats->samples=realloc(rtt_stats->samples,
                sizeof(*rtt_stats)*(rtt_stats->samples_size));
        }
        rtt_stats->samples[rtt_stats->nb_samples++]=rtt;
#endif
        rtt_law->nb_samples++;

        /* Update the statistics */
        rtt_stats->sum_rtt+=rtt;
        if (rtt<rtt_stats->min_rtt) {
            if (rtt_stats->state==RTT_STATE_HINVALID) {
#if 0
                if (rtt_stats->min_rtt_sav!=DBL_MAX)
                    printf("validating %d: %9.7f -> %9.7f (min was %9.7f)\n",index,rtt_stats->min_rtt_sav,rtt,rtt_stats->min_rtt);
#endif
                rtt_law_set_rtt_state(rtt_law,index,RTT_STATE_OK,0);
            }
            rtt_stats->min_rtt=rtt;
            return 1;
        } else if (rtt>rtt_stats->max_rtt) {
            rtt_stats->max_rtt=rtt;
        }
    }

    return 0;
}

/**
 * Sets the state of the specified RTT measurement and updates 
 * the rtt_law_t level statistics accordingly.
 *
 * @param rtt_law the host,method on which to operate
 * @param s the index of the RTT to modify
 * @param state the new state of the RTT, one of the RTT_STATE_* values.
 *        To unset the RTT_STATE_LINVALID1 or RTT_STATE_LINVALID2 flags 
 *        use respectively RTT_STATE_OK|RTT_STATE_LINVALID1 and 
 *        RTT_STATE_OK|RTT_STATE_LINVALID2
 * @param max_rtt if state is RTT_STATE_HINVALID this is the RTT value 
 *        to attain before the RTT is considered valid again. Otherwise 
 *        this parameter is not significant
 * @return the previous state of the RTT
 */
int rtt_law_set_rtt_state(rtt_law_t *rtt_law, int s, int state, double max_rtt)
{
    int ostate;

    ostate=rtt_law->rtt_stats[s].state;
    if (state==ostate) {
        if ((state==RTT_STATE_HINVALID) && (rtt_law->rtt_stats[s].min_rtt>=max_rtt))
            rtt_law->rtt_stats[s].min_rtt=max_rtt;
        return state;
    }

    switch (state) {
    case RTT_STATE_OK:
        if (ostate==RTT_STATE_HINVALID)
            rtt_law->nb_hinvalid--;
        else if ((ostate & RTT_STATE_MASK)==RTT_STATE_LINVALID) {
            rtt_law->nb_linvalid--;
        } else
            rtt_law->nb_redo--;
        rtt_law->rtt_stats[s].state=state;
        break;
    case RTT_STATE_OK|RTT_STATE_LINVALID1:
        /* Should only be called if we are already in a the RTT_STATE_LINVALID state */
        if (ostate==(RTT_STATE_LINVALID|RTT_STATE_LINVALID1)) {
            rtt_law->nb_linvalid--;
            rtt_law->rtt_stats[s].state=RTT_STATE_OK;
	} else {
            rtt_law->rtt_stats[s].state&=~RTT_STATE_LINVALID1;
	}
        break;
    case RTT_STATE_OK|RTT_STATE_LINVALID2:
        /* Should only be called if we are already in a the RTT_STATE_LINVALID state */
        if (ostate==(RTT_STATE_LINVALID|RTT_STATE_LINVALID2)) {
            rtt_law->nb_linvalid--;
            rtt_law->rtt_stats[s].state=RTT_STATE_OK;
	} else {
            rtt_law->rtt_stats[s].state&=~RTT_STATE_LINVALID2;
	}
        break;
    case RTT_STATE_HINVALID:
        if (ostate==RTT_STATE_REDO)
            rtt_law->nb_redo--;
        else if ((ostate & RTT_STATE_MASK)==RTT_STATE_LINVALID) {
            rtt_law->nb_linvalid--;
        }
        rtt_law->nb_hinvalid++;
#ifdef _DEBUG
        /* printf("invalidating %d %9.7f > %9.7f\n",s,rtt_law->rtt_stats[s].min_rtt,max_rtt); */
        rtt_law->rtt_stats[s].min_rtt_sav=rtt_law->rtt_stats[s].min_rtt;
#endif
        rtt_law->rtt_stats[s].min_rtt=max_rtt;
        rtt_law->rtt_stats[s].state=state;
        break;
    case RTT_STATE_LINVALID|RTT_STATE_LINVALID1:
        if (ostate==RTT_STATE_REDO)
            rtt_law->nb_redo--;
        if ((ostate & RTT_STATE_MASK)!=RTT_STATE_LINVALID)
            rtt_law->nb_linvalid++;
        rtt_law->rtt_stats[s].state=RTT_STATE_LINVALID | (rtt_law->rtt_stats[s].state & ~RTT_STATE_MASK) | RTT_STATE_LINVALID1;
        break;
    case RTT_STATE_LINVALID|RTT_STATE_LINVALID2:
        if (ostate==RTT_STATE_REDO)
            rtt_law->nb_redo--;
        if ((ostate & RTT_STATE_MASK)!=RTT_STATE_LINVALID)
            rtt_law->nb_linvalid++;
        rtt_law->rtt_stats[s].state=RTT_STATE_LINVALID | (rtt_law->rtt_stats[s].state & ~RTT_STATE_MASK) | RTT_STATE_LINVALID2;
        break;
    case RTT_STATE_REDO:
        if (ostate==RTT_STATE_OK) {
            rtt_law->nb_redo++;
        }
        rtt_law->rtt_stats[s].state=state;
        break;
    }
    return ostate;
}


int method_init_probe(bp_handle* probe, int method)
{
    /* nothing to do it seems */
    return 0;
}

int method_do_probe(bp_handle* probe_handle, host_stats_t* host, int packet_size, rtt_law_t* rtt_law, int index)
{
    bp_probedata_t probe;
    int res;

    /*printf("method_do_probe target=%s port=%d ttl=%d size=%d\n",host_addr2name(&host->address,0),SOCKADDR_IN(&host->address)->sin_port,host->ttl,packet_size);*/
    res=do_probe(probe_handle,
        &host->address,
        packet_size,
        host->ttl,&probe);
    if (probe.contents!=NULL)
        free(probe.contents);
    switch (res) {
    case BP_RES_TTL_EXCEEDED:
        /* (!!) this is really bad because we should have the right rtt */
        printf("(!!) BP_RES_TTL_EXCEEDED\n");fflush(stdout);
        break;
    case BP_RES_HIT:
        /* update the stats about this host */
        /* (!!) is it packet_size or probe->size or does it depend on the method ? */
        return rtt_law_add(rtt_law,index,2*probe.size,probe.rtt);
        break;
    case BP_RES_TIMEOUT:
        /* this is something to expect */
        printf("method_do_probe *time out*\n");fflush(stdout);
        rtt_law_add(rtt_law,index,-1,0.0);
        break;
    case -1:
        printf("method_do_probe *failed*\n");fflush(stdout);
        /* (!!) we should display an error message, this is not supposed to happen */
        /*(!!)fprintf(stderr,"%s: error: could not send probe of size %d to %s\n",
            tool_name,packet_size,
            host->name,
            host_addr2name(&host->address,0)); / * (!!) is it 0 here ? */
        printf("(!!) could not send probe\n");fflush(stdout);
        break;
    default:
        /* (!!) display more details, there may also be more cases to handle */
        /*(!!)fprintf(stderr,"%s: error: failed probe of size %d to %s\n",
            tool_name,packet_size,
            host->name,
            host_addr2name(&host->address,0)); / * (!!) is it 0 here ? */
        printf("(!!) unknown result %d\n",res);fflush(stdout);
        break;
    }
    return 0;
}
