/**************************************************************
 * Copyright (C) 2001 Alex Rozin, Optical Access 
 *
 *                     All Rights Reserved
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 * 
 * ALEX ROZIN DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * ALEX ROZIN BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 ******************************************************************/

#include <net-snmp/net-snmp-config.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "util_funcs.h"

#include "history.h"

/*
 * Implementation headers 
 */
#include "agutil_api.h"
#include "row_api.h"

/*
 * File scope definitions section 
 */

#define historyControlEntryFirstIndexBegin      11

#define CTRL_INDEX		3
#define CTRL_DATASOURCE		4
#define CTRL_BUCKETSREQUESTED	5
#define CTRL_BUCKETSGRANTED	6
#define CTRL_INTERVAL		7
#define CTRL_OWNER		8
#define CTRL_STATUS		9

#define DATA_INDEX		3
#define DATA_SAMPLEINDEX	4
#define DATA_INTERVALSTART	5
#define DATA_DROPEVENTS		6
#define DATA_OCTETS		7
#define DATA_PKTS		8
#define DATA_BROADCASTPKTS	9
#define DATA_MULTICASTPKTS	10
#define DATA_CRCALIGNERRORS	11
#define DATA_UNDERSIZEPKTS	12
#define DATA_OVERSIZEPKTS	13
#define DATA_FRAGMENTS		14
#define DATA_JABBERS		15
#define DATA_COLLISIONS		16
#define DATA_UTILIZATION	17

/*
 * defaults & limitations 
 */

#define MAX_BUCKETS_IN_CRTL_ENTRY	50
#define HIST_DEF_BUCK_REQ		50
#define HIST_DEF_INTERVAL		1800
static VAR_OID_T DEFAULT_DATA_SOURCE = { 11,    /* ifIndex.1 */
    {1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 1}
};

typedef struct data_struct_t {
    struct data_struct_t *next;
    u_long          data_index;
    u_long          start_interval;
    u_long          utilization;
    ETH_STATS_T     EthData;
} DATA_ENTRY_T;

typedef struct {
    u_long          interval;
    u_long          timer_id;
    VAR_OID_T       data_source;

    u_long          coeff;
    DATA_ENTRY_T    previous_bucket;
    SCROLLER_T      scrlr;

} CRTL_ENTRY_T;

static TABLE_DEFINTION_T HistoryCtrlTable;
static TABLE_DEFINTION_T *table_ptr = &HistoryCtrlTable;

/*
 * Main section 
 */

#  define Leaf_historyControlDataSource                    2
#  define Leaf_historyControlBucketsRequested              3
#  define Leaf_historyControlInterval                      5
#  define Leaf_historyControlOwner                         6
#  define Leaf_historyControlStatus                        7
#  define MIN_historyControlBucketsRequested               1
#  define MAX_historyControlBucketsRequested               65535
#  define MIN_historyControlInterval                       1
#  define MAX_historyControlInterval                       3600

static int
write_historyControl(int action, u_char * var_val, u_char var_val_type,
                     size_t var_val_len, u_char * statP,
                     oid * name, size_t name_len)
{
    long            long_temp;
    int             leaf_id, snmp_status;
    static int      prev_action = COMMIT;
    RMON_ENTRY_T   *hdr;
    CRTL_ENTRY_T   *cloned_body;
    CRTL_ENTRY_T   *body;

    switch (action) {
    case RESERVE1:
    case FREE:
    case UNDO:
    case ACTION:
    case COMMIT:
    default:
        return ROWAPI_do_another_action(name,
                                        historyControlEntryFirstIndexBegin,
                                        action, &prev_action, table_ptr,
                                        sizeof(CRTL_ENTRY_T));
    case RESERVE2:
        /*
         * get values from PDU, check them and save them in the cloned entry 
         */
        long_temp = name[historyControlEntryFirstIndexBegin];
        leaf_id = (int) name[historyControlEntryFirstIndexBegin - 1];
        hdr = ROWAPI_find(table_ptr, long_temp);        /* it MUST be OK */
        cloned_body = (CRTL_ENTRY_T *) hdr->tmp;
        body = (CRTL_ENTRY_T *) hdr->body;
        switch (leaf_id) {
        case Leaf_historyControlDataSource:
            snmp_status = AGUTIL_get_oid_value(var_val, var_val_type,
                                               var_val_len,
                                               &cloned_body->data_source);
            if (SNMP_ERR_NOERROR != snmp_status) {
                ag_trace("can't browse historyControlDataSource");
                return snmp_status;
            }
            if (RMON1_ENTRY_UNDER_CREATION != hdr->status &&
                snmp_oid_compare(cloned_body->data_source.objid,
                                 cloned_body->data_source.length,
                                 body->data_source.objid,
                                 body->data_source.length)) {
                ag_trace
                    ("can't change historyControlDataSource - not Creation");
                return SNMP_ERR_BADVALUE;
            }
            break;
        case Leaf_historyControlBucketsRequested:
            snmp_status = AGUTIL_get_int_value(var_val, var_val_type,
                                               var_val_len,
                                               MIN_historyControlBucketsRequested,
                                               MAX_historyControlBucketsRequested,
                                               &cloned_body->scrlr.
                                               data_requested);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }
#if 0
            if (RMON1_ENTRY_UNDER_CREATION != hdr->status &&
                cloned_body->scrlr.data_requested !=
                body->scrlr.data_requested)
                return SNMP_ERR_BADVALUE;
#endif
            break;
        case Leaf_historyControlInterval:
            snmp_status = AGUTIL_get_int_value(var_val, var_val_type,
                                               var_val_len,
                                               MIN_historyControlInterval,
                                               MAX_historyControlInterval,
                                               &cloned_body->interval);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }
#if 0
            if (RMON1_ENTRY_UNDER_CREATION != hdr->status &&
                cloned_body->interval != body->interval)
                return SNMP_ERR_BADVALUE;
#endif
            break;
        case Leaf_historyControlOwner:
            if (hdr->new_owner)
                AGFREE(hdr->new_owner);
            hdr->new_owner = AGMALLOC(MAX_OWNERSTRING);;
            if (!hdr->new_owner)
                return SNMP_ERR_TOOBIG;
            snmp_status = AGUTIL_get_string_value(var_val, var_val_type,
                                                  var_val_len,
                                                  MAX_OWNERSTRING,
                                                  1, NULL, hdr->new_owner);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }

            break;
        case Leaf_historyControlStatus:
            snmp_status = AGUTIL_get_int_value(var_val, var_val_type,
                                               var_val_len,
                                               RMON1_ENTRY_VALID,
                                               RMON1_ENTRY_INVALID,
                                               &long_temp);
            if (SNMP_ERR_NOERROR != snmp_status) {
                return snmp_status;
            }
            hdr->new_status = long_temp;
            break;
        default:
            ag_trace("%s:unknown leaf_id=%d\n", table_ptr->name,
                     (int) leaf_id);
            return SNMP_ERR_NOSUCHNAME;
        }                       /* of switch by 'leaf_id' */
        break;

    }                           /* of switch by actions */

    prev_action = action;
    return SNMP_ERR_NOERROR;
}

/*
 * var_historyControlTable():
 */
unsigned char  *
var_historyControlTable(struct variable *vp,
                        oid * name,
                        size_t * length,
                        int exact,
                        size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static CRTL_ENTRY_T theEntry;
    RMON_ENTRY_T   *hdr;

    *write_method = write_historyControl;
    hdr = ROWAPI_header_ControlEntry(vp, name, length, exact, var_len,
                                     table_ptr,
                                     &theEntry, sizeof(CRTL_ENTRY_T));
    if (!hdr)
        return NULL;

    *var_len = sizeof(long);    /* default */

    switch (vp->magic) {
    case CTRL_INDEX:
        long_ret = hdr->ctrl_index;
        return (unsigned char *) &long_ret;

    case CTRL_DATASOURCE:
        *var_len = sizeof(oid) * theEntry.data_source.length;
        return (unsigned char *) theEntry.data_source.objid;

    case CTRL_BUCKETSREQUESTED:
        long_ret = theEntry.scrlr.data_requested;
        return (unsigned char *) &long_ret;

    case CTRL_BUCKETSGRANTED:

        long_ret = theEntry.scrlr.data_granted;
        return (unsigned char *) &long_ret;

    case CTRL_INTERVAL:
        long_ret = theEntry.interval;
        return (unsigned char *) &long_ret;

    case CTRL_OWNER:
        if (hdr->owner) {
            *var_len = strlen(hdr->owner);
            return (unsigned char *) hdr->owner;
        } else {
            *var_len = 0;
            return (unsigned char *) "";
        }

    case CTRL_STATUS:
        long_ret = hdr->status;
        return (unsigned char *) &long_ret;

    default:
        ag_trace("HistoryControlTable: unknown vp->magic=%d",
                 (int) vp->magic);
        ERROR_MSG("");
    }
    return NULL;
}

/*
 * history row management control callbacks 
 */

static void
compute_delta(ETH_STATS_T * delta,
              ETH_STATS_T * newval, ETH_STATS_T * prevval)
{
#define CNT_DIF(X) delta->X = newval->X - prevval->X

    CNT_DIF(octets);
    CNT_DIF(packets);
    CNT_DIF(bcast_pkts);
    CNT_DIF(mcast_pkts);
    CNT_DIF(crc_align);
    CNT_DIF(undersize);
    CNT_DIF(oversize);
    CNT_DIF(fragments);
    CNT_DIF(jabbers);
    CNT_DIF(collisions);
}

static void
history_get_backet(unsigned int clientreg, void *clientarg)
{
    RMON_ENTRY_T   *hdr_ptr;
    CRTL_ENTRY_T   *body;
    DATA_ENTRY_T   *bptr;
    ETH_STATS_T     newSample;

    /*
     * ag_trace ("history_get_backet: timer_id=%d", (int) clientreg); 
     */
    hdr_ptr = (RMON_ENTRY_T *) clientarg;
    if (!hdr_ptr) {
        ag_trace
            ("Err: history_get_backet: hdr_ptr=NULL ? (Inserted in shock)");
        return;
    }

    body = (CRTL_ENTRY_T *) hdr_ptr->body;
    if (!body) {
        ag_trace
            ("Err: history_get_backet: body=NULL ? (Inserted in shock)");
        return;
    }

    if (RMON1_ENTRY_VALID != hdr_ptr->status) {
        ag_trace("Err: history_get_backet when entry %d is not valid ?!!",
                 (int) hdr_ptr->ctrl_index);
        /*
         * snmp_alarm_print_list (); 
         */
        snmp_alarm_unregister(body->timer_id);
        ag_trace("Err: unregistered %ld", (long) body->timer_id);
        return;
    }

    SYSTEM_get_eth_statistics(&body->data_source, &newSample);

    bptr = ROWDATAAPI_locate_new_data(&body->scrlr);
    if (!bptr) {
        ag_trace
            ("Err: history_get_backet for %d: empty bucket's list !??\n",
             (int) hdr_ptr->ctrl_index);
        return;
    }

    bptr->data_index = ROWDATAAPI_get_total_number(&body->scrlr);

    bptr->start_interval = body->previous_bucket.start_interval;

    compute_delta(&bptr->EthData, &newSample,
                  &body->previous_bucket.EthData);

    bptr->utilization =
        bptr->EthData.octets * 8 + bptr->EthData.packets * (96 + 64);
    bptr->utilization /= body->coeff;

    /*
     * update previous_bucket 
     */
    body->previous_bucket.start_interval = AGUTIL_sys_up_time();
    memcpy(&body->previous_bucket.EthData, &newSample,
           sizeof(ETH_STATS_T));
}

/*
 * Control Table RowApi Callbacks 
 */

int
history_Create(RMON_ENTRY_T * eptr)
{                               /* create the body: alloc it and set defaults */
    CRTL_ENTRY_T   *body;

    eptr->body = AGMALLOC(sizeof(CRTL_ENTRY_T));
    if (!eptr->body)
        return -3;
    body = (CRTL_ENTRY_T *) eptr->body;

    /*
     * set defaults 
     */
    body->interval = HIST_DEF_INTERVAL;
    body->timer_id = 0;
    memcpy(&body->data_source, &DEFAULT_DATA_SOURCE, sizeof(VAR_OID_T));

    ROWDATAAPI_init(&body->scrlr, HIST_DEF_BUCK_REQ,
                    MAX_BUCKETS_IN_CRTL_ENTRY, sizeof(DATA_ENTRY_T), NULL);

    return 0;
}

int
history_Validate(RMON_ENTRY_T * eptr)
{
    /*
     * T.B.D. (system dependent) check valid inteface in body->data_source; 
     */
    return 0;
}

int
history_Activate(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;

    body->coeff = 100000L * (long) body->interval;

    ROWDATAAPI_set_size(&body->scrlr,
                        body->scrlr.data_requested,
                        (u_char)(RMON1_ENTRY_VALID == eptr->status) );

    SYSTEM_get_eth_statistics(&body->data_source,
                              &body->previous_bucket.EthData);
    body->previous_bucket.start_interval = AGUTIL_sys_up_time();

    body->scrlr.current_data_ptr = body->scrlr.first_data_ptr;
    /*
     * ag_trace ("Dbg:   registered in history_Activate"); 
     */
    body->timer_id = snmp_alarm_register(body->interval, SA_REPEAT,
                                         history_get_backet, eptr);
    return 0;
}

int
history_Deactivate(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;

    snmp_alarm_unregister(body->timer_id);
    /*
     * ag_trace ("Dbg: unregistered in history_Deactivate timer_id=%d",
     * (int) body->timer_id); 
     */

    /*
     * free data list 
     */
    ROWDATAAPI_descructor(&body->scrlr);

    return 0;
}

int
history_Copy(RMON_ENTRY_T * eptr)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) eptr->body;
    CRTL_ENTRY_T   *clone = (CRTL_ENTRY_T *) eptr->tmp;

    if (body->scrlr.data_requested != clone->scrlr.data_requested) {
        ROWDATAAPI_set_size(&body->scrlr, clone->scrlr.data_requested,
                            (u_char)(RMON1_ENTRY_VALID == eptr->status) );
    }

    if (body->interval != clone->interval) {
        if (RMON1_ENTRY_VALID == eptr->status) {
            snmp_alarm_unregister(body->timer_id);
            body->timer_id =
                snmp_alarm_register(clone->interval, SA_REPEAT,
                                    history_get_backet, eptr);
        }

        body->interval = clone->interval;
    }

    if (snmp_oid_compare
        (clone->data_source.objid, clone->data_source.length,
         body->data_source.objid, body->data_source.length)) {
        memcpy(&body->data_source, &clone->data_source, sizeof(VAR_OID_T));
    }

    return 0;
}

static SCROLLER_T *
history_extract_scroller(void *v_body)
{
    CRTL_ENTRY_T   *body = (CRTL_ENTRY_T *) v_body;
    return &body->scrlr;
}

/*
 * var_etherHistoryTable():
 */
unsigned char  *
var_etherHistoryTable(struct variable *vp,
                      oid * name,
                      size_t * length,
                      int exact,
                      size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static DATA_ENTRY_T theBucket;
    RMON_ENTRY_T   *hdr;
    CRTL_ENTRY_T   *ctrl;

    *write_method = NULL;
    hdr = ROWDATAAPI_header_DataEntry(vp, name, length, exact, var_len,
                                      table_ptr,
                                      &history_extract_scroller,
                                      sizeof(DATA_ENTRY_T), &theBucket);
    if (!hdr)
        return NULL;

    *var_len = sizeof(long);    /* default */

    ctrl = (CRTL_ENTRY_T *) hdr->body;

    switch (vp->magic) {
    case DATA_INDEX:
        long_ret = hdr->ctrl_index;
        return (unsigned char *) &long_ret;
    case DATA_SAMPLEINDEX:
        long_ret = theBucket.data_index;
        return (unsigned char *) &long_ret;
    case DATA_INTERVALSTART:
        long_ret = 0;
        return (unsigned char *) &theBucket.start_interval;
    case DATA_DROPEVENTS:
        long_ret = 0;
        return (unsigned char *) &long_ret;
    case DATA_OCTETS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.octets;
    case DATA_PKTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.packets;
    case DATA_BROADCASTPKTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.bcast_pkts;
    case DATA_MULTICASTPKTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.mcast_pkts;
    case DATA_CRCALIGNERRORS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.crc_align;
    case DATA_UNDERSIZEPKTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.undersize;
    case DATA_OVERSIZEPKTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.oversize;
    case DATA_FRAGMENTS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.fragments;
    case DATA_JABBERS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.jabbers;
    case DATA_COLLISIONS:
        long_ret = 0;
        return (unsigned char *) &theBucket.EthData.collisions;
    case DATA_UTILIZATION:
        long_ret = 0;
        return (unsigned char *) &theBucket.utilization;
    default:
        ag_trace("etherHistoryTable: unknown vp->magic=%d",
                 (int) vp->magic);
        ERROR_MSG("");
    }
    return NULL;
}

#if 1                           /* debug, but may be used for init. TBD: may be token snmpd.conf ? */
int
add_hist_entry(int ctrl_index, int ifIndex,
               u_long interval, u_long requested)
{
    register RMON_ENTRY_T *eptr;
    register CRTL_ENTRY_T *body;
    int             ierr;

    ierr = ROWAPI_new(table_ptr, ctrl_index);
    if (ierr) {
        ag_trace("ROWAPI_new failed with %d", ierr);
        return ierr;
    }

    eptr = ROWAPI_find(table_ptr, ctrl_index);
    if (!eptr) {
        ag_trace("ROWAPI_find failed");
        return -4;
    }

    body = (CRTL_ENTRY_T *) eptr->body;

    /*
     * set parameters 
     */

    body->data_source.objid[body->data_source.length - 1] = ifIndex;
    body->interval = interval;
    body->scrlr.data_requested = requested;

    eptr->new_status = RMON1_ENTRY_VALID;
    ierr = ROWAPI_commit(table_ptr, ctrl_index);
    if (ierr) {
        ag_trace("ROWAPI_commit failed with %d", ierr);
    }

    return ierr;

}

#endif

/*
 * Registration & Initializatio section 
 */

oid             historyControlTable_variables_oid[] =
    { 1, 3, 6, 1, 2, 1, 16, 2, 1 };

struct variable2 historyControlTable_variables[] = {
    /*
     * magic number        , variable type, ro/rw , callback fn  ,           L, oidsuffix 
     */
    {CTRL_INDEX, ASN_INTEGER, RONLY, var_historyControlTable, 2, {1, 1}},
    {CTRL_DATASOURCE, ASN_OBJECT_ID, RWRITE, var_historyControlTable, 2,
     {1, 2}},
    {CTRL_BUCKETSREQUESTED, ASN_INTEGER, RWRITE, var_historyControlTable,
     2, {1, 3}},
    {CTRL_BUCKETSGRANTED, ASN_INTEGER, RONLY, var_historyControlTable, 2,
     {1, 4}},
    {CTRL_INTERVAL, ASN_INTEGER, RWRITE, var_historyControlTable, 2,
     {1, 5}},
    {CTRL_OWNER, ASN_OCTET_STR, RWRITE, var_historyControlTable, 2,
     {1, 6}},
    {CTRL_STATUS, ASN_INTEGER, RWRITE, var_historyControlTable, 2, {1, 7}},

};

oid             etherHistoryTable_variables_oid[] =
    { 1, 3, 6, 1, 2, 1, 16, 2, 2 };

struct variable2 etherHistoryTable_variables[] = {
    /*
     * magic number     , variable type , ro/rw , callback fn  ,        L, oidsuffix 
     */
    {DATA_INDEX, ASN_INTEGER, RONLY, var_etherHistoryTable, 2, {1, 1}},
    {DATA_SAMPLEINDEX, ASN_INTEGER, RONLY, var_etherHistoryTable, 2,
     {1, 2}},
    {DATA_INTERVALSTART, ASN_TIMETICKS, RONLY, var_etherHistoryTable, 2,
     {1, 3}},
    {DATA_DROPEVENTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 4}},
    {DATA_OCTETS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2, {1, 5}},
    {DATA_PKTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2, {1, 6}},
    {DATA_BROADCASTPKTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 7}},
    {DATA_MULTICASTPKTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 8}},
    {DATA_CRCALIGNERRORS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 9}},
    {DATA_UNDERSIZEPKTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 10}},
    {DATA_OVERSIZEPKTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 11}},
    {DATA_FRAGMENTS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 12}},
    {DATA_JABBERS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2, {1, 13}},
    {DATA_COLLISIONS, ASN_COUNTER, RONLY, var_etherHistoryTable, 2,
     {1, 14}},
    {DATA_UTILIZATION, ASN_INTEGER, RONLY, var_etherHistoryTable, 2,
     {1, 15}},

};

void
init_history(void)
{
    REGISTER_MIB("historyControlTable", historyControlTable_variables,
                 variable2, historyControlTable_variables_oid);
    REGISTER_MIB("etherHistoryTable", etherHistoryTable_variables,
                 variable2, etherHistoryTable_variables_oid);

    ROWAPI_init_table(&HistoryCtrlTable, "History", 0, &history_Create, NULL,   /* &history_Clone, */
                      NULL,     /* &history_Delete, */
                      &history_Validate,
                      &history_Activate,
                      &history_Deactivate, &history_Copy);

    /*
     * add_hist_entry (2, 3, 4, 2); 
     */
}
