
/*
 * Copyright (c) 2006 International Computer Science Institute
 * 
 * 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 names and trademarks of copyright holders may not be used in
 * advertising or publicity pertaining to the software without specific
 * prior permission. Title to copyright in this software and any
 * associated documentation will at all times remain with the copyright
 * holders.
 * 
 * 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 <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <strings.h>
#include <sys/time.h>
#include "trans.h"
#include "util.h"

struct tfunc
{
    char *cmd;
    unsigned short num_args;
    double (*func) ();
    unsigned short (*valid_arg_func) ();
    char *usage;
};

struct tfunc trans_list [] = 
{
    { "average",   0, average,   NULL,
      "average X   x_i <- sum x_0...x_i / i" },
    { "ceil",      0, ceil,      NULL,
      "ceil        x_i <- ceil (x_i)" },
    { "div",       1, divide,    NULL,
      "div X       x_i <- x_i / X" },
    { "floor",     0, floor,     NULL,
      "floor X     x_i <- floor (x_i)" },
    { "ewma",      1, ewma,      zero_to_one,
      "ewma X      x_i <- (x_i * X) + (x_i-1 * (1 - X))" },
    { "incr",      1, incr,      NULL,
      "incr X      x_i <- x_i + X" },
    { "loge",      0, log,       NULL,
      "loge        x_i <- log_e (x_i)" },
    { "log10",     0, log10,     NULL,
      "log10       x_i <- log_10 (x_i)" },
    { "mod",       1, mod,       NULL,
      "mod X       x_i <- x_i % X" },
    { "power",     1, power,     NULL,
      "power X     x_i <- x_i ** X" },
    { "relative",  0, relative,  NULL,
      "relative    x_i <- x_i - x_0" },
    { "sample",    1, rsample,   zero_to_one,
      "sample X    sample data with P(X)" },
    { "scale",     1, scale,     NULL,
      "scale X     x_i <- x_i * X" },
    { "slide-win", 1, slide_win, positive_int,
      "slide-win X x_i <- sum x_i-X...x_i / X" },
    { "sqrt",      0, sqrt,      NULL,
      "sqrt        x_i <- sqrt (x_i)" }
};


double *arg = NULL;
double (*trans_func) () = NULL;
char *outfile = NULL;
FILE *ofp;


/* various utility functions */

void dump_version ()
{
    fprintf (stdout,"\ntransform v%s\n", VERSION);
    fprintf (stdout,"Mark Allman (mallman@icir.org)\n\n");
    exit (0);
}


unsigned short num_trans ()
{
    unsigned short list_size;

    list_size = sizeof (trans_list);
    if (list_size == 0)
	return (0);
    return (list_size / sizeof (struct tfunc));
}


void usage (progname)
char *progname;
{
    unsigned short ntrans = num_trans ();
    unsigned short i;

    fprintf (stderr,"usage: %s [options] function [arg(s)] input_file[(s)]\n",
             progname);
    fprintf (stderr,"options:\n");
    fprintf (stderr,"  -h          usage information\n");
    fprintf (stderr,"  -o of       send output to file \"of\"\n");
    fprintf (stderr,"  --version   version information\n");
    fprintf (stderr,"functions:\n");
    for (i = 0; i < ntrans; i++)
        fprintf (stderr,"  %s\n", trans_list [i].usage);
    exit (1);
}


void alloc_args ()
{
    unsigned short ntrans = num_trans ();
    unsigned short max_args = 0;
    int i;

    for (i = 0; i < ntrans; i++)
        if (trans_list [i].num_args > max_args)
            max_args = trans_list [i].num_args;
    if (max_args == 0)
        return;
    if ((arg = (double *)malloc (max_args * sizeof (double))) == NULL)
    {
        fprintf (stderr,"not enough memory to hold arguments\n");
        exit (1);
    }       
}


int parseargs (argc,argv)
int argc;
char *argv [];
{
    unsigned short ntrans = num_trans ();
    int i, j, k;

    for (i = 1; i < argc; i++)
    {
        if (argv [i] [0] != '-')
            break;
        if (!strcmp (argv [i],"-h"))
            usage (argv [0]);
        if (!strcmp (argv [i],"--version"))
            dump_version ();
        if (!strcmp (argv [i],"-o") && ((i + 1) < argc) && (outfile == NULL))
            outfile = argv [++i];
        else
        {
            fprintf (stderr,"unknwon option: %s\n", argv [i]);
            usage (argv [0]);
        }
    }
    if (i >= argc)
        usage (argv [0]);
    for (j = 0; j < ntrans; j++)
        if (!strcmp (trans_list [j].cmd,argv [i]))
        {
            trans_func = trans_list [j].func;
            if ((i + trans_list [j].num_args) >= argc)
                usage (argv [0]);
            for (i++, k = 0; k < trans_list [j].num_args; k++, i++)
                arg [k] = atof (argv [i]);
            if (trans_list [j].valid_arg_func != NULL)
                if (!trans_list [j].valid_arg_func (trans_list [j].num_args))
                {
                    fprintf (stderr,"invalid transform argument(s)\n");
                    usage (argv [0]);
                }
            break;
        }
    return (i);
}


/* argument validation routines */

unsigned short zero_to_one (num_args)
unsigned short num_args;
{
    unsigned short i;

    for (i = 0; i < num_args; i++)
        if ((arg [i] <= 0.0) || (arg [i] >= 1.0))
            return (FALSE);
    return (TRUE);
}


unsigned short positive_int (num_args)
unsigned short num_args;
{
    unsigned short i;

    for (i = 0; i < num_args; i++)
    {
        if (arg [i] <= 0.0)
            return (FALSE);
        if ((long)arg [i] != arg [i])
            return (FALSE);
    }
    return (TRUE);
}


/* transformations */

double average (v)
double v;
{
    static double sum = 0;
    static unsigned long items = 0;

    sum += v;
    items++;
    return (sum / (double)items);
}


double divide (v)
double v;
{
    arg [0] = 1.0 / arg [0];
    trans_func = scale;
    return (scale (v));
}


double ewma (v)
double v;
{
    static unsigned first = TRUE;
    static double prev_ewma = 0;
    
    if (first)
    {
        prev_ewma = v;
        first = FALSE;
    }
    else
        prev_ewma = (arg [0] * v) + ((1.0 - arg [0]) * prev_ewma);
    return (prev_ewma);
}


double incr (v)
double v;
{
    return (v + arg [0]);
}


double mod (v)
double v;
{
    return ((double)((long)v % (long)arg [0]));
}


double power (v)
double v;
{
    return (pow (v,arg [0]));
}


double relative (v)
double v;
{
    static unsigned short first = TRUE;
    static double base;

    if (first)
    {
        base = v;
        first = FALSE;
    }
    return (v - base);
}


double rsample (v)
double v;
{
    static unsigned short first = TRUE;
    struct timeval t;

    if (first)
    {
        gettimeofday (&t,NULL);
        srandom (t.tv_usec);        
    } 
    if ((random() / (double) RAND_MAX) <= arg [0])
        return (v);
    return (0.0 / 0.0);
}

double scale (v)
double v;
{
    return (v * arg [0]);
}


double slide_win (v)
double v;
{
    static unsigned long items = 0;
    static double *win = NULL;
    static unsigned long i = 0;
    static double sum = 0.0;
    unsigned long mem_size;

    if (items == 0)
    {
        mem_size = (long)arg [0] * sizeof (double);
        if ((win = (double *)malloc (mem_size)) == NULL)
        {
            fprintf (stderr,"cannot allocate memory for window\n");
            exit (1);
        }
        bzero (win,mem_size);
    }
    sum -= win [i];
    sum += v;
    win [i] = v;
    i = (i + 1) % (long)arg [0];
    if ((items + 1) < (long)arg [0])
    {
        items++;
        return (NaN);
    }
    return (sum / arg [0]);
}        


/* dump transformed values */

void transform_printer (v)
double v;
{
    double newv;

    newv = trans_func (v);
    if (!isnan (newv))
        fprintf (ofp,"%f\n", newv);
}


int main (argc,argv)
int argc;
char *argv [];
{
    int i;

    set_process_func (transform_printer);
    alloc_args ();
    i = parseargs (argc,argv);
    if ((trans_func == NULL) || (i >= argc))
        usage (argv [0]);
    if ((outfile == NULL) || !strcmp (outfile,"-"))
        ofp = stdout;
    else
    {
        if ((ofp = fopen (outfile,"w")) == NULL)
        {
            fprintf (stderr,"cannot open output file %s\n", outfile);
            exit (1);
        }
    }
    for (; i < argc; i++)
        process_file (argv [i]);
    if (ofp != stdout)
        fclose (ofp);
    exit (0);
}
