/*
 * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
 * Copyright (c) 2022 Red Hat.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include "pmapi.h"
#include "libpcp.h"
#include "pmlc.h"

/* this pmResult is built after parsing the current statement.  The action
 * routines (Status, LogReq) send it to the logger as a request.
 */
__pmResult	*logreq;
int		sz_logreq;
int		n_logreq;

int		n_metrics;
int		sz_metrics;
metric_t	*metric;

int		n_indoms;
int		sz_indoms;
indom_t		*indom;

void
freemetrics(void)
{
    int		i;
    metric_t	*mp;

    for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
	free(mp->name);
	if (mp->status.has_insts && mp->inst != NULL)
	    free(mp->inst);
    }
    n_metrics = 0;
    /* keep the array around for re-use */
}

void
freeindoms(void)
{
    int		i;
    indom_t	*ip;

    for (i = 0, ip = indom; i < n_indoms; i++, ip++) {
	free(ip->inst);
	free(ip->name);
    }
    free(indom);
    sz_indoms = n_indoms = 0;
    indom = NULL;
}

/* Return a pointer to the specified instance domain, adding it to the list if
 * it is not already present.  Returns < 0 on error.
 */
int
addindom(pmInDom newindom, int *resptr)
{
    int		i;
    indom_t	*ip;

    for (i = 0; i < n_indoms; i++)
	if (newindom == indom[i].indom) {
	    *resptr = i;
	    return 0;
	}

    *resptr = -1;			/* in case of errors */
    if (n_indoms >= sz_indoms) {
	sz_indoms += 4;
	i = sz_indoms * sizeof(indom_t);
	if ((indom = (indom_t *)realloc(indom, i)) == NULL) {
	    pmNoMem("expanding instance domain array", i, PM_FATAL_ERR);
	}
    }
    ip = &indom[n_indoms];
    ip->inst = NULL;
    ip->name = NULL;

    if ((i = pmGetInDom(newindom, &ip->inst, &ip->name)) < 0) {
	*resptr = -1;
	return i;
    }
    ip->indom = newindom;
    ip->n_insts = i;
    *resptr = n_indoms;
    n_indoms++;
    return 0;
}

int		metric_cnt;		/* status of addmetric operation(s) */
static int	had_insts;		/* new metric(s) had instances specified */
static int	n_selected;		/* number of metrics selected */
static int	first_inst;		/* check consistency of new metrics' InDoms */

void
beginmetrics(void)
{
    metric_cnt = 0;
    freemetrics();
}

void
beginmetgrp(void)
{
    int		i;
    metric_t	*mp;

    had_insts = 0;
    n_selected = 0;
    first_inst = 1;
    for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
	mp->status.selected = 0;
	mp->status.new = 0;
    }
}

/* Perform any instance domain fixups and error checks required for the latest
 * metrics added.
 */
void
endmetgrp(void)
{
    int		i;
    metric_t	*mp;

    if (n_metrics <= 0) {
	if (metric_cnt >= 0)
	    metric_cnt = PM_ERR_PMID;
	return;
    }

    for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
	if (!mp->status.selected)
	    continue;

	/* just added metric, no instances => use all instances */
	if (mp->status.new) {
	    if (mp->indom != -1 && !had_insts) {
		mp->n_insts = indom[mp->indom].n_insts;
		mp->inst = indom[mp->indom].inst;
		mp->status.has_insts = 0;
	    }
	}
	/* metric was there already */
	else
	    if (mp->indom == -1)
		fprintf(stderr, "Warning: %s has already been specified\n", mp->name);
	    else
		/* if metric had specific instances */
		if (mp->status.has_insts) {
		    if (!had_insts) {
			fprintf(stderr,
				"Warning: %s had instance(s) specified previously.\n"
				"         Using all instances since none specified this time\n",
				mp->name);
			if (mp->inst != NULL)
			    free(mp->inst);
			mp->n_insts = indom[mp->indom].n_insts;
			mp->inst = indom[mp->indom].inst;
			mp->status.has_insts = 0;
		    }
		}
		/* metric had "use all instances" */
		else
		    if (!had_insts)
			fprintf(stderr,
				"Warning: already using all instances for %s\n",
				mp->name);
		    else
			fprintf(stderr,
				"Warning: instance(s) specified for %s\n"
				"         (already using all instances)\n",
				mp->name);
    }
}

void
endmetrics(void)
{
    int			i, j, need;
    metric_t		*mp;
    pmValueSet		*vsp;

    if (metric_cnt < 0) {
	if (connected())
	    fputs("Logging statement ignored due to error(s)\n", stderr);
	goto done;
    }

    if (pmDebugOptions.appl0)
	dumpmetrics(stdout);

    /* free any old values in the reusable pmResult skeleton */
    if (n_logreq) {
	for (i = 0; i < n_logreq; i++) {
	    free(logreq->vset[i]);
	    logreq->vset[i] = NULL;
	}
	logreq->numpmid = 0;
	n_logreq = 0;
    }

    /* build a result from the metrics and instances in the metric array */
    if (n_metrics > sz_logreq) {
	if (sz_logreq)
	    __pmFreeResult(logreq);
	/* - 1 because a pmResult already contains one pmValueSet ptr */
	if ((logreq = __pmAllocResult(n_metrics)) == NULL) {
	    pmNoMem("building result to send", sizeof(__pmResult) + (n_metrics - 1) * sizeof(pmValueSet *), PM_FATAL_ERR);
	}
	sz_logreq = n_metrics;
    }
    for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
	need = sizeof(pmValueSet);
	/* pmValueSet contains one pmValue, allow for more if > 1 inst */
	if (mp->status.has_insts && mp->n_insts > 1)
	    need += (mp->n_insts - 1) * sizeof(pmValue);
	if ((vsp = (pmValueSet *)malloc(need)) == NULL) {
	    pmNoMem("building result value set", need , PM_FATAL_ERR);
	}
	logreq->vset[i] = vsp;
	vsp->pmid = mp->pmid;
	if (mp->indom == -1)
	    vsp->numval = 1;
	else
	    vsp->numval = mp->status.has_insts ? mp->n_insts : 0;
	vsp->valfmt = PM_VAL_INSITU;
	if (mp->status.has_insts) {
	    for (j = 0; j < vsp->numval; j++) {
		vsp->vlist[j].inst = mp->inst[j];
		vsp->vlist[j].value.pval = NULL;
	    }
	} else {
	    vsp->vlist[0].inst = PM_IN_NULL;
	    vsp->vlist[0].value.pval = NULL;
	}
    }
    logreq->numpmid = n_metrics;
    n_logreq = n_metrics;

done:
    return;
}

/* Add a metric to the metric list. Sets metric_cnt to < 0 on fatal error. */

void
addmetric(const char *name)
{
    int		i;
    pmID	pmid;
    int		need;
    metric_t	*mp;
    pmDesc	desc;
    int		sts;

    if (metric_cnt < 0)
	return;

    /* Cast const away as pmLookUpName should not modify name */
    if ((sts = pmLookupName(1, &name, &pmid)) < 0) {
	metric_cnt = sts;
	return;
    }

    for (i = 0, mp = metric; i < n_metrics; i++, mp++)
	if (pmid == mp->pmid)
	    break;

    if (i >= n_metrics) {
	/* expand metric array if necessary */
	if (n_metrics >= sz_metrics) {
	    sz_metrics += 4;
	    need = sz_metrics * sizeof(metric_t);
	    if ((metric = (metric_t *)realloc(metric, need)) == NULL) {
		pmNoMem("expanding metric array", need, PM_FATAL_ERR);
	    }
	}
	mp = &metric[i];
	mp->status.new = 1;
    }
    mp->status.selected = 1;
    n_selected++;
    if (i < n_metrics)			/* already have this metric */
	return;

    if ((sts = pmLookupDesc(pmid, &desc)) == PM_ERR_PMID
	|| desc.type == PM_TYPE_NOSUPPORT) {
	/*
	 * Name is bad or descriptor is not available ...
	 * this is not fatal, but need to back off a little
	 */
	n_selected--;
	return;
    }
    if (sts < 0) {
	/* other errors are more serious */
	metric_cnt = sts;
	return;
    }
    if (desc.indom == PM_INDOM_NULL)
	i = -1;
    else
	if ((metric_cnt = addindom(desc.indom, &i)) < 0)
	    return;

    mp->name = strdup(name);
    mp->pmid = pmid;
    mp->indom = i;
    mp->n_insts = 0;
    mp->inst = NULL;
    mp->status.has_insts = 0;
    metric_cnt = ++n_metrics;
    return;
}

/* Add an instance to the selected metric(s) on the metric list.
 * If name is NULL, use instance number in inst.
 * Check that
 *    - the last group of metrics added have the same pmInDom
 *    - the specified instance exists in the metrics' instance domain.
 */
void
addinst(char *name, int instid)
{
    metric_t	*mp, *first_mp;
    indom_t	*ip;
    int		i, j, need;
    int		inst;
    static int	done_inst_msg = 0;

    /* don't try to add instances if one more metrics were erroneous */
    if (metric_cnt < 0)
	return;

    had_insts = 1;

    /* Find the first selected metric */
    for (mp = metric; mp < &metric[n_metrics]; mp++)
	if (mp->status.selected)
	    break;
    if (mp >= &metric[n_metrics]) {
	fprintf(stderr, "Internal botch: No metrics selected for addinst(%s, %d)\n", name, instid);
	abort();
    }
    first_mp = mp;

    if (first_inst) {
	int	bad = 0;
	int	nullindom = 0;

	first_inst = 0;

	/* check that all of the metrics have the same instance domain */
	for ( ; mp < &metric[n_metrics]; mp++) {
	    if (!mp->status.selected)
		continue;

	    /* check that the metric doesn't have PM_INDOM_NULL */
	    if (mp->indom == -1) {
		fprintf(stderr, "Error: %s has no instance domain but an instance was specified\n",
			mp->name);
		nullindom = 1;
	    }

	    if (mp == first_mp)
		continue;

	    if (first_mp->indom != mp->indom) {
		if (bad == 0) {
		    fprintf(stderr, "Error: different instance domains:\n");
		    fprintf(stderr, "    %s (%s)\n", first_mp->name, pmInDomStr(indom[first_mp->indom].indom));
		    bad = 1;
		}
		if (mp->indom == -1)
		    fprintf(stderr, "    %s (PM_INDOM_NULL)\n", mp->name);
		else
		    fprintf(stderr, "    %s (%s)\n", mp->name, pmInDomStr(indom[mp->indom].indom));
	    }
	}
	if (bad || nullindom) {
	    metric_cnt = PM_ERR_INDOM;
	    return;
	}
    }

    mp = first_mp;			/* go back to first selected metric */
    ip = &indom[mp->indom];
    for (i = 0; i < ip->n_insts; i++)
	if (name != NULL) {
	    if (strcasecmp(name, ip->name[i]) == 0)
		break;
	}
	else
	    if (instid == ip->inst[i])
		break;

    for ( ; mp < &metric[n_metrics]; mp++) {
	if (!mp->status.selected)
	    continue;

	/* check that metric with explicit instances has the specified inst.
	 * For those metrics that specify "all instances" an instance not there yet
	 * is OK (but generates a warning) since we don't need to give pmlogger any
	 * instance ids.
	 * For metrics with specific instances, an unknown instance is an error
	 * since the instance id can't be given to pmlogger.
	 */
	if (i >= ip->n_insts) {
	    if (mp->status.has_insts || mp->status.new) {
		metric_cnt = PM_ERR_INST;
		if (name != NULL)
		    fprintf(stderr, "Error: metric %s currently has no instance named %s\n", mp->name, name);
		else
		    fprintf(stderr, "Error: metric %s currently has no instance number %d\n", mp->name, instid);
		if (!done_inst_msg) {
		    fputs("    - you may only specify metric instances active now.  However if no\n"
			  "      instances are specified, pmlogger will use all of the instances\n"
			  "      available at the time the metric is logged\n",
			  stderr);
		    done_inst_msg = 1;
		}
	    }
	    /* for an old metric specifying all instances warn if the specified
	     * instance is not currently in the instance domain.
	     */	    
	    else {
		if (name != NULL)
		    fprintf(stderr,
			    "Warning: instance name %s not currently in metric %s's instance domain\n",
			    name, mp->name);
		else
		    fprintf(stderr,
			    "Warning: instance number %d not currently in metric %s's instance domain\n",
			    instid, mp->name);
		fputs("         (getting all instances anyway)\n", stderr);
	    }
	    continue;
	}
	inst = ip->inst[i];

	/* check that we don't already have the same instance */
	for (j = 0; j < mp->n_insts; j++)
	    if (inst == mp->inst[j])
		break;

	if (j < mp->n_insts) {		/* already have inst */
	    if (mp->status.has_insts) {
		if (name != NULL) {
		    fprintf(stderr,
			    "Warning: instance name %s already specified for metric %s\n",
			    name, mp->name);
		}
		else {
		    fprintf(stderr,
			    "Warning: instance number %d already specified for %s\n",
			    instid, mp->name);
		}
	    }

	    /* if the metric had no insts of its own, (it specifies all insts)
	     * just do nothing.  endmetgrp() will print a single message which
	     * is better than having, one printed for each instance specified.
	     */
	    continue;
	}

	else {				/* don't have this inst */
	    /* add inst for new metric or old metric with explicit insts */
	    if (mp->status.new || mp->status.has_insts) {
		j = mp->n_insts++;
		if (j == 0)
		    mp->status.has_insts = 1;
		need = mp->n_insts * sizeof(int);
		if ((mp->inst = (int *)realloc(mp->inst, need)) == NULL) {
		    if (name != NULL)
			fprintf(stderr, "metric %s inst name %s: ", mp->name, name);
		    else
			fprintf(stderr, "metric %s inst number %d: ", mp->name, instid);
		    pmNoMem("expanding instance array", need, PM_FATAL_ERR);
		}
		mp->inst[j] = inst;
	    }
	}
    }
}

void
dumpmetrics(FILE *f)
{
    int		i, j, k;
    metric_t	*mp;

    fprintf(f, "     Inst  Inst Name\n");
    fprintf(f, "    ====== =========\n");

    for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
	fprintf(f, "%s\n", mp->name);
	if (mp->indom == -1)
	    fprintf(f, "           singular instance\n");
	else {
	    indom_t	*ip = &indom[mp->indom];		    
	    int		*inst = ip->inst;
	    char	**name = ip->name;
	    int		n_insts = ip->n_insts;

	    /* No instances specified, use them all */
	    if (mp->n_insts == 0)
		for (j = 0; j < n_insts; j++)
		    fprintf(f, "    %6d %s\n", inst[j], name[j]);
	    else
		for (j = 0; j < mp->n_insts; j++) {
		    int		m_inst = mp->inst[j];;

		    for (k = 0; k < n_insts; k++)
			if (m_inst == inst[k])
			    break;
		    if (k < n_insts)
			fprintf(f, "    %6d %s\n", inst[k], name[k]);
		    else
			fprintf(f, "    KAPOWIE! inst %d not found\n", m_inst);
		}
	}
	putc('\n', f);
    }
}
