/* Copyright (C) 2021-2024 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   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 3, 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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include "util.h"
#include "Command.h"
#include "DbeSession.h"
#include "MetricList.h"
#include "StringBuilder.h"

//  Build a metric reference list
MetricList::MetricList (Vector<BaseMetric*> *base_metrics, MetricType _mtype)
{
  mtype = _mtype;
  items = new Vector<Metric*>;
  sort_ref_index = 0;
  sort_reverse = false;

  Metric *mitem;
  // loop over the base_metrics, and add in all the appropriate subtypes
  for (long i = 0, sz = base_metrics ? base_metrics->size () : 0; i < sz; i++)
    {
      BaseMetric *mtr = base_metrics->get (i);
      if (mtr->is_internal ())
	continue;
      switch (mtype)
	{
	case MET_DATA:
	  if ((mtr->get_flavors () & BaseMetric::DATASPACE) != 0)
	    {
	      mitem = new Metric (mtr, BaseMetric::DATASPACE);
	      items->append (mitem);
	    }
	  break;

	case MET_INDX:
	  {
	    if ((mtr->get_flavors () & BaseMetric::INCLUSIVE) != 0
		|| (mtr->get_flavors () & BaseMetric::EXCLUSIVE) != 0)
	      {
		int index2;
		Metric *item2 = NULL;
		bool found = false;
		Vec_loop (Metric*, items, index2, item2)
		{
		  if (item2->get_subtype () == BaseMetric::EXCLUSIVE
		      && dbe_strcmp (item2->get_cmd (), mtr->get_cmd ()) == 0)
		    {
		      found = true;
		      break;
		    }
		}
		if (found == false)
		  {
		    mitem = new Metric (mtr, BaseMetric::EXCLUSIVE);
		    items->append (mitem);
		  }
	      }
	  }
	  break;

	case MET_CALL:
	case MET_CALL_AGR:
	  if ((mtr->get_flavors () & BaseMetric::ATTRIBUTED) != 0)
	    {
	      mitem = new Metric (mtr, BaseMetric::ATTRIBUTED);
	      items->append (mitem);
	    }
	  // now fall through to add exclusive and inclusive

	case MET_NORMAL:
	case MET_COMMON:
	  if (mtr->get_flavors () & BaseMetric::EXCLUSIVE)
	    {
	      mitem = new Metric (mtr, BaseMetric::EXCLUSIVE);
	      items->append (mitem);
	    }
	  if (mtr->get_flavors () & BaseMetric::INCLUSIVE)
	    {
	      mitem = new Metric (mtr, BaseMetric::INCLUSIVE);
	      items->append (mitem);
	    }
	  break;
	case MET_SRCDIS:
	  if (mtr->get_flavors () & BaseMetric::INCLUSIVE)
	    {
	      mitem = new Metric (mtr, BaseMetric::INCLUSIVE);
	      items->append (mitem);
	    }
	  break;
	case MET_IO:
	  {
	    if (mtr->get_packet_type () == DATA_IOTRACE
		&& ((mtr->get_flavors () & BaseMetric::INCLUSIVE) != 0
		    || (mtr->get_flavors () & BaseMetric::EXCLUSIVE) != 0))
	      {
		int index2;
		Metric *item2 = NULL;
		bool found = false;
		Vec_loop (Metric*, items, index2, item2)
		{
		  if (item2->get_subtype () == BaseMetric::EXCLUSIVE
		      && dbe_strcmp (item2->get_cmd (), mtr->get_cmd ()) == 0)
		    {
		      found = true;
		      break;
		    }
		}
		if (found == false)
		  {
		    mitem = new Metric (mtr, BaseMetric::EXCLUSIVE);
		    items->append (mitem);
		  }
	      }
	  }
	  break;
	case MET_HEAP:
	  {
	    if (mtr->get_packet_type () == DATA_HEAP
		&& ((mtr->get_flavors () & BaseMetric::INCLUSIVE) != 0
		    || (mtr->get_flavors () & BaseMetric::EXCLUSIVE) != 0))
	      {
		int index2;
		Metric *item2 = NULL;
		bool found = false;
		Vec_loop (Metric*, items, index2, item2)
		{
		  if ((item2->get_subtype () == BaseMetric::EXCLUSIVE) &&
		      (dbe_strcmp (item2->get_cmd (), mtr->get_cmd ()) == 0))
		    {
		      found = true;
		      break;
		    }
		}
		if (found == false)
		  {
		    mitem = new Metric (mtr, BaseMetric::EXCLUSIVE);
		    items->append (mitem);
		  }
	      }
	  }
	  break;
	}

      // add the static
      if (mtr->get_flavors () & BaseMetric::STATIC)
	{
	  switch (mtype)
	    {
	    case MET_NORMAL:
	    case MET_COMMON:
	    case MET_CALL:
	    case MET_CALL_AGR:
	    case MET_SRCDIS:
	      mitem = new Metric (mtr, BaseMetric::STATIC);
	      items->append (mitem);
	      break;
	    default:
	      if (mtr->get_type () == BaseMetric::ONAME)
		{
		  mitem = new Metric (mtr, BaseMetric::STATIC);
		  items->append (mitem);
		}
	      break;
	    }
	}
    }
  // set all metrics visible
  for (long i = 0, sz = items ? items->size () : 0; i < sz; i++)
    items->get (i)->enable_all_visbits ();
}

// Constructor for an empty list -- items will be added one at a time
MetricList::MetricList (MetricType _mtype)
{
  mtype = _mtype;
  items = new Vector<Metric*>;
  sort_ref_index = 0;
  sort_reverse = false;
}

MetricList::~MetricList ()
{
  Destroy (items);
}

// Duplicate a metric list
MetricList::MetricList (MetricList *old)
{
  mtype = old->mtype;

  // get an empty vector
  items = new Vector<Metric*>;
  Metric *item;
  Metric *nitem;
  int index;
  sort_ref_index = old->get_sort_ref_index ();
  sort_reverse = old->get_sort_rev ();
  Vec_loop (Metric*, old->items, index, item)
  {
    nitem = new Metric (*item);
    items->append (nitem);
  }
}

// set_metrics:
//	Sets the particular metric list, according to the metric spec
//      If fromRcFile, updates dbeSession->get_reg_metrics_tree() with new defaults.
char *
MetricList::set_metrics (const char *mspec, bool fromRcFile,
			 DerivedMetrics * /* derived_metrics */)
{
  BaseMetric::SubType subtypes[10];
  int nsubtypes;
  int dmetrics_vis; // literal translation of metrics/dmetrics %.+
  bool parseOK = false;
  char *errbuf;
  Vector<Metric*> *old_items = items;
  items = new Vector<Metric*>;
  Vector<BaseMetric*> *base_items = dbeSession->get_base_reg_metrics ();

  // and copy the input specification
  char *buf = dbe_strdup (mspec);

  // append metric items from parsing the string
  for (char *mcmd = strtok (buf, NTXT (":")); mcmd != NULL;
	  mcmd = strtok (NULL, NTXT (":")))
    {
      // parse the single metric_spec, based on the type of list being constructed, into:
      //	a vector of SubTypes (any of [iead] or STATIC)
      //	a integer mask for the visibility bits
      //	and the string name of the base metric
      // 	    it might be "all", "any", or "hwc" or it should match a metric in the list
      //	    it might also be "bit", meaning any bit-computed metric
      char *mname = parse_metric_spec (mcmd, subtypes, &nsubtypes,
				       &dmetrics_vis, &parseOK);
      if (!parseOK)
	{
	  // error parsing the metric specification
	  // not from an rc file, it's an error
	  if (!fromRcFile)
	    {
	      delete base_items;
	      items->destroy ();
	      delete items;
	      items = old_items;
	      free (buf);
	      return mname;
	    }
	  continue;
	}

      // loop over subtypes requested
      // set the visibility of and sort order according to the vis bits,
      //	and the order of encounter in the processing
      int ret = add_matching_dmetrics (base_items, mname, subtypes, nsubtypes,
				       dmetrics_vis, fromRcFile);
      if (ret != 0 && !fromRcFile)
	{
	  if (ret == 1)
	    errbuf = dbe_sprintf (GTXT ("No data recorded to support metric specification: %s\n"),
				  mcmd);
	  else
	    errbuf = dbe_sprintf (GTXT ("Metric specification for `%s' has appeared before in %s"),
				    mcmd, mspec);
	  delete base_items;
	  items->destroy ();
	  delete items;
	  items = old_items;
	  free (buf);
	  return errbuf;
	}
    } // we've processed the entire spec

  // update metric defaults
  if (fromRcFile)
    {
      for (long i = 0, sz = items->size (); i < sz; i++)
	{
	  Metric *m = items->get (i);
	  int visbits = m->get_visbits ();
	  BaseMetric::SubType subtype = m->get_subtype ();
	  BaseMetric *reg_bm = m->get_base_metric ();
	  reg_bm->set_default_visbits (subtype, visbits);
	  BaseMetricTreeNode *mtree = dbeSession->get_reg_metrics_tree ();
	  BaseMetricTreeNode *bmtnode = mtree->register_metric (m);
	  BaseMetric *tree_bm = bmtnode->get_BaseMetric ();
	  tree_bm->set_default_visbits (subtype, visbits);
	}
    }

  // ensure that name is present, remove hidden metrics
  nsubtypes = 1;
  for (long i = items->size () - 1; i >= 0; i--)
    {
      Metric *m = items->fetch (i);
      if (!m->is_any_visible ())
	{
	  delete m;
	  items->remove (i);
	  continue;
	}
      if (m->get_type () == BaseMetric::ONAME)
	nsubtypes = 0;
    }

  // did we get at least one valid match?
  if (items->size () == 0 && !fromRcFile)
    {
      errbuf = dbe_sprintf (GTXT ("No valid metrics specified in `%s'\n"), mspec);
      delete base_items;
      items->destroy ();
      delete items;
      items = old_items;
      free (buf);
      return errbuf;
    }

  if (nsubtypes == 1)
    {
      subtypes[0] = BaseMetric::STATIC;
      (void) add_matching_dmetrics (base_items, NTXT ("name"), subtypes, 1, VAL_VALUE, true);
    }

  // replace the old list of items, with the new set
  if (old_items)
    {
      old_items->destroy ();
      delete old_items;
    }
  set_fallback_sort ();
  free (buf);
  delete base_items;
  return NULL;
}

void
MetricList::set_fallback_sort ()
{
  // sort by first visible of the appropriate flavor
  char *sortcmd = NULL;
  switch (mtype)
    {
    case MET_NORMAL:
    case MET_COMMON:
      sortcmd = NTXT ("ei.any:name");
      break;
    case MET_SRCDIS:
      sortcmd = NTXT ("i.any:name");
      break;
    case MET_CALL:
    case MET_CALL_AGR:
      sortcmd = NTXT ("a.any:name");
      break;
    case MET_DATA:
      sortcmd = NTXT ("d.any:name");
      break;
    case MET_INDX:
      sortcmd = NTXT ("e.any:name");
      break;
    case MET_IO:
      sortcmd = NTXT ("e.any:name");
      break;
    case MET_HEAP:
      sortcmd = NTXT ("e.any:name");
      break;
    }
  if (NULL != sortcmd)
    (void) set_sort (sortcmd, true);
}

void
MetricList::set_metrics (MetricList *mlist)
{
  // verify that the type is appropriate for the call
  if (mtype == MET_NORMAL || mtype == MET_COMMON
      || (mlist->mtype != MET_NORMAL && mlist->mtype != MET_COMMON))
    abort ();

  Vector<Metric*> *mlist_items = mlist->get_items ();
  items->destroy ();
  items->reset ();

  int sort_ind = mlist->get_sort_ref_index ();
  for (int i = 0, mlist_sz = mlist_items->size (); i < mlist_sz; i++)
    {
      Metric *mtr = mlist_items->fetch (i);
      if (!mtr->is_any_visible ())
	continue;

      //  Add a new Metric with probably a new sub_type to this->items:
      //    for MET_CALL and MET_CALL_AGR the matching entry to an e. or i. is itself
      //    for MET_DATA, the matching entry to an e. or i. is the d. metric
      //    for MET_INDX, the matching entry to an e. or i. is the e. metric
      //    for MET_IO, the matching entry to an e. or i. is the e. metric
      //    for MET_HEAP, the matching entry to an e. or i. is the e. metric
      //    Save static entries (SIZES and ADDRESS) only for MET_NORMAL, MET_CALL, MET_CALL_AGR, MET_SRCDIS
      switch (mtr->get_type ())
	{
	case BaseMetric::SIZES:
	case BaseMetric::ADDRESS:
	  switch (mtype)
	    {
	    case MET_NORMAL:
	    case MET_COMMON:
	    case MET_CALL:
	    case MET_CALL_AGR:
	    case MET_SRCDIS:
	      break;
	    default:
	      continue;
	    }
	  break;
	default:
	  break;
	}

      BaseMetric::SubType st = mtr->get_subtype ();
      if (st != BaseMetric::STATIC)
	{
	  if (mtype == MET_CALL || mtype == MET_CALL_AGR)
	    {
	      if ((mtr->get_flavors () & BaseMetric::ATTRIBUTED) == 0)
		continue;
	      st = BaseMetric::ATTRIBUTED;
	    }
	  else if (mtype == MET_DATA)
	    {
	      if ((mtr->get_flavors () & BaseMetric::DATASPACE) == 0)
		continue;
	      st = BaseMetric::DATASPACE;
	    }
	  else if (mtype == MET_INDX)
	    {
	      if ((mtr->get_flavors () & BaseMetric::EXCLUSIVE) == 0)
		continue;
	      st = BaseMetric::EXCLUSIVE;
	    }
	  else if (mtype == MET_IO)
	    {
	      if (mtr->get_packet_type () != DATA_IOTRACE ||
		  (mtr->get_flavors () & BaseMetric::EXCLUSIVE) == 0)
		continue;
	      st = BaseMetric::EXCLUSIVE;
	    }
	  else if (mtype == MET_HEAP)
	    {
	      if (mtr->get_packet_type () != DATA_HEAP ||
		  (mtr->get_flavors () & BaseMetric::EXCLUSIVE) == 0)
		continue;
	      st = BaseMetric::EXCLUSIVE;
	    }
	  else if (mtype == MET_SRCDIS)
	    {
	      if ((mtr->get_flavors () & BaseMetric::INCLUSIVE) == 0)
		continue;
	      st = BaseMetric::INCLUSIVE;
	    }
	}

      bool found = false;
      for (int i1 = 0, items_sz = items->size (); i1 < items_sz; i1++)
	{
	  Metric *m1 = items->fetch (i1);
	  if (mtr->get_id () == m1->get_id () && st == m1->get_subtype ())
	    {
	      if (sort_ind == i)
		sort_ind = i1;
	      found = true;
	      break;
	    }
	}
      if (found)
	continue;
      Metric *m = new Metric (*mtr);
      m->set_subtype (st);
      m->set_raw_visbits (mtr->get_visbits ());
      if (sort_ind == i)
	sort_ind = items->size ();
      items->append (m);
    }
  if (sort_ind >= items->size ())
    sort_ind = 0;
  if (mtype == MET_IO)
    sort_ind = 0;
  if (mtype == MET_HEAP)
    sort_ind = 0;
  sort_ref_index = sort_ind;

}


// set_sort:
//	Sets the sort for the metric list to the first metric
//	in mspec that is present; if fromRcFile is false, then
//	only one metric may be specified.  The requested sort
//	metric must be visible, or it won't  be in the metric list

char *
MetricList::set_sort (const char *mspec, bool fromRcFile)
{
  char *mcmd;
  BaseMetric::SubType subtypes[10];
  int nsubtypes;
  int vis;
  bool parseOK = false;
  bool reverse = false;
  char buf[BUFSIZ];
  char *list = buf;
  char *mname;

  // copy the input specification
  snprintf (buf, sizeof (buf), NTXT ("%s"), mspec);
  char *listp = list;
  if (*listp == '-')
    {
      // reverse sort specified
      reverse = true;
      listp++;
    }

  // search for metric items from parsing the string
  while ((mcmd = strtok (listp, NTXT (":"))) != NULL)
    {
      listp = NULL; // let strtok keep track

      // parse the single metric_spec, based on the type of list being constructed, into:
      //	a vector of SubTypes (any of [iead] or STATIC)
      //	a integer mask for the visibility bits
      //	and the string name of the base metric
      mname = parse_metric_spec (mcmd, subtypes, &nsubtypes, &vis, &parseOK);
      if (!parseOK)
	{
	  // error parsing the metric specification
	  // not from an rc file, it's an error
	  if (!fromRcFile)
	    return (mname);
	  continue;
	}
      if (VAL_IS_HIDDEN (vis))
	continue;

      // loop over subtypes requested to find metric
      // add a metric of that subtype, with specified vis.bits
      for (int i = 0; i < nsubtypes; i++)
	{
	  // make sure the subtype is acceptable
	  if ((mtype == MET_CALL || mtype == MET_CALL_AGR)
	      && subtypes[i] != BaseMetric::ATTRIBUTED
	      && subtypes[i] != BaseMetric::STATIC)
	      return dbe_sprintf (GTXT ("Inclusive, Exclusive, or Data metrics cannot be specified for caller-callee sort: %s\n"),
				  mcmd);
	  if (mtype == MET_DATA && subtypes[i] != BaseMetric::DATASPACE
	      && subtypes[i] != BaseMetric::STATIC)
	      return dbe_sprintf (GTXT ("Inclusive, Exclusive, or Attributed metrics cannot be specified for data-derived sort: %s\n"),
				  mcmd);
	  if (mtype == MET_INDX && subtypes[i] != BaseMetric::EXCLUSIVE
				   && subtypes[i] != BaseMetric::STATIC)
	    return dbe_sprintf (GTXT ("Inclusive, Data or Attributed metrics cannot be specified for index sort: %s\n"),
				mcmd);
	  if ((mtype == MET_NORMAL || mtype == MET_COMMON
	       || mtype == MET_SRCDIS)
	      && (subtypes[i] == BaseMetric::DATASPACE
		  || subtypes[i] == BaseMetric::ATTRIBUTED))
	    return dbe_sprintf (GTXT ("Data or Attributed metrics cannot be specified for sort: %s\n"), mcmd);
	  if (set_sort_metric (mname, subtypes[i], reverse))
	    return NULL;
	}
      // continue looking at entries
    }

  // not found on the list at all
  switch (mtype)
    {
    case MET_NORMAL:
    case MET_COMMON:
    case MET_SRCDIS:
      return dbe_sprintf (GTXT ("Invalid sort specification: %s\n"), mspec);
    case MET_CALL:
    case MET_CALL_AGR:
      return dbe_sprintf (GTXT ("Invalid caller-callee sort specification: %s\n"),
			  mspec);
    case MET_DATA:
      return dbe_sprintf (GTXT ("Invalid data-derived sort specification: %s\n"),
			  mspec);
    case MET_INDX:
      return dbe_sprintf (GTXT ("Invalid index sort specification: %s\n"),
			  mspec);
    case MET_IO:
      return dbe_sprintf (GTXT ("Invalid I/O sort specification: %s\n"), mspec);
    case MET_HEAP:
      return dbe_sprintf (GTXT ("Invalid heap sort specification: %s\n"),
			  mspec);
    }
  return NULL;
}

// set_sort to the metric with the given visible index

void
MetricList::set_sort (int visindex, bool reverse)
{
  Metric *mitem;
  if (visindex < items->size ())
    {
      mitem = items->fetch (visindex);
      if (mitem->is_any_visible ())
	{
	  sort_ref_index = visindex;
	  sort_reverse = reverse;
	  return;
	}
    }
  set_fallback_sort ();
}

bool
MetricList::set_sort_metric (char *mname, BaseMetric::SubType mst, bool reverse)
{
  bool any = false, hwc = false, bit = false;

  // check keywords 'any', 'all', 'bit' and 'hwc'
  if (!strcasecmp (mname, Command::ANY_CMD))
    any = true;
  else if (!strcasecmp (mname, Command::ALL_CMD))
    any = true;
  else if (!strcasecmp (mname, Command::HWC_CMD))
    hwc = true;
  else if (!strcasecmp (mname, Command::BIT_CMD))
    bit = true;

  for (int i = 0, items_sz = items->size (); i < items_sz; i++)
    {
      Metric *m = items->fetch (i);
      if (mst == m->get_subtype ()
	  && (any || (hwc && m->get_type () == BaseMetric::HWCNTR)
	      || (bit && m->get_cmd ()
		  && strncmp (Command::BIT_CMD, m->get_cmd (),
			      strlen (Command::BIT_CMD)) == 0)
	      || dbe_strcmp (mname, m->get_cmd ()) == 0))
	{
	  sort_ref_index = i;
	  sort_reverse = reverse;
	  return true;
	}
    }
  return false;
}

// Print to a file of a list of metrics from a supplied vector
//	Debug flag = 1, prints the short name and address of the list
//	Debug flag = 2, prints the details of the list
void
MetricList::print_metric_list (FILE *dis_file, char *leader, int debug)
{
  Metric *item;
  int index;
  char fmt_name[64];
  fprintf (dis_file, NTXT ("%s"), leader);
  if (items == NULL)
    {
      fprintf (dis_file, GTXT ("NULL metric list can not be printed; aborting"));
      abort ();
    }

  if (items->size () == 0)
    {
      fprintf (dis_file, GTXT ("metric list is empty; aborting\n"));
      abort ();
    }

  // if debugging, print list address and string, and sort name
  if (debug != 0)
    {
      char *s = get_metrics ();
      fprintf (dis_file, "\tmetriclist at 0x%lx: %s, %lld metrics; sort by %s\n",
	       (unsigned long) this, s, (long long) items->size (),
	       get_sort_name ());
      free (s);
      if (debug == 1)
	return;
    }

  // Find the longest metric name & command
  size_t max_len = 0;
  size_t max_len2 = 0;

  Vec_loop (Metric*, items, index, item)
  {
    // get the name
    char *mn = item->get_name ();
    size_t len = strlen (mn);
    if (max_len < len)
      max_len = len;

    mn = item->get_mcmd (true);
    len = strlen (mn);
    if (max_len2 < len)
      max_len2 = len;
    free (mn);

  }
  if (debug == 2)
    snprintf (fmt_name, sizeof (fmt_name), "%%%ds: %%-%ds", (int) max_len,
	      (int) max_len2);
  else
    snprintf (fmt_name, sizeof (fmt_name), "%%%ds: %%s", (int) max_len);

  Vec_loop (Metric*, items, index, item)
  {
    char *mcmd = item->get_mcmd (true);
    fprintf (dis_file, fmt_name, item->get_name (), mcmd);
    free (mcmd);
    if (debug == 2)
      fprintf (dis_file, "\t[st %2d, VT %d, vis = %4s, T=%d, sort = %c]",
	       item->get_subtype (), item->get_vtype (),
	       item->get_vis_str (), item->is_time_val (),
	       sort_ref_index == index ? 'Y' : 'N');
    fputc ('\n', dis_file);
  }

  fputc ('\n', dis_file);
  fflush (dis_file);
}

// Return a string formatted from a vector of metrics
//	string is in the form suitable for a "metrics <string>" command
char *
MetricList::get_metrics ()
{
  Metric *item;
  int index;
  StringBuilder sb;
  Vec_loop (Metric*, items, index, item)
  {
    if (sb.length () != 0)
      sb.append (':');
    char *mcmd = item->get_mcmd (false);
    sb.append (mcmd);
    free (mcmd);
  }
  return sb.toString ();
}

int
MetricList::get_listorder (Metric *mtr)
{
  for (int i = 0, items_sz = items->size (); i < items_sz; i++)
    {
      Metric *m = items->fetch (i);
      if (m->get_subtype () == mtr->get_subtype ()
	  && m->get_id () == mtr->get_id ())
	return i;
    }
  return -1;
}

int
MetricList::get_listorder (char *cmd, BaseMetric::SubType st, const char *expr)
{
  for (long i = 0, items_sz = items->size (); i < items_sz; i++)
    {
      Metric *m = items->fetch (i);
      if (m->get_subtype () == st && dbe_strcmp (m->get_cmd (), cmd) == 0
	  && dbe_strcmp (m->get_expr_spec (), expr) == 0)
	return (int) i;
    }
  return -1;
}

Metric *
MetricList::find_metric_by_name (char *cmd)
{
  for (long i = 0, items_sz = items->size (); i < items_sz; i++)
    {
      Metric *m = items->fetch (i);
      if (dbe_strcmp (m->get_cmd (), cmd) == 0)
	return m;
    }
  return NULL;
}

//  find a metric by name and subtype
Metric *
MetricList::find_metric (char *cmd, BaseMetric::SubType st)
{
  int i = get_listorder (cmd, st);
  if (i < 0)
    return NULL;
  return items->fetch (i);
}

//  Get the sort metric from a list; forces sort by first if not set
Metric *
MetricList::get_sort_metric ()
{
  int i = get_sort_ref_index ();
  return i >= 0 ? items->fetch (i) : NULL;
}

char *
MetricList::get_sort_name ()
{
  Metric *item = get_sort_metric ();
  if (item == NULL)
    return dbe_strdup (NTXT (""));
  char *n = item->get_name ();
  return sort_reverse ? dbe_sprintf ("-%s", n) : dbe_strdup (n);
}

char *
MetricList::get_sort_cmd ()
{
  char *buf;
  Metric *item = get_sort_metric ();
  if (item == NULL)
    return dbe_strdup (NTXT (""));
  char *n = item->get_mcmd (false);
  if (sort_reverse)
    {
      buf = dbe_sprintf (NTXT ("-%s"), n);
      free (n);
    }
  else
    buf = n;
  return buf;
}

Metric *
MetricList::append (BaseMetric *bm, BaseMetric::SubType st, int visbits)
{
  for (long i = 0, sz = items->size (); i < sz; i++)
    {
      Metric *m = items->get (i);
      if (m->get_id () == bm->get_id () && m->get_subtype () == st)
	return NULL;
    }
  Metric *met = new Metric (bm, st);
  met->set_dmetrics_visbits (visbits);
  items->append (met);
  return met;
}

int
MetricList::add_matching_dmetrics (Vector<BaseMetric*> *base_items,
				   char *mcmd, BaseMetric::SubType *_subtypes,
				   int nsubtypes, int dmetrics_visbits,
				   bool fromRcFile)
{
  bool any = false, hwc = false, bit = false;
  int got_metric = 1;

  // check keywords 'any', 'all', 'bit', and 'hwc'
  if (!strcasecmp (mcmd, Command::ANY_CMD))
    any = true;
  else if (!strcasecmp (mcmd, Command::ALL_CMD))
    any = true;
  else if (!strcasecmp (mcmd, Command::HWC_CMD))
    hwc = true;
  else if (!strcasecmp (mcmd, Command::BIT_CMD))
    bit = true;

  BaseMetric::SubType *subtypes = _subtypes;
  BaseMetric::SubType all_subtypes[2] =
    { BaseMetric::EXCLUSIVE, BaseMetric::INCLUSIVE };

  if (nsubtypes == 0 || (nsubtypes == 1 && subtypes[0] == BaseMetric::STATIC))
    {
      // user did not specify ei; treat as wildcard and supply both.
      subtypes = all_subtypes;
      nsubtypes = 2;
    }

  // scan the metrics to find all matches
  for (int i = 0, base_sz = base_items->size (); i < base_sz; i++)
    {
      BaseMetric *item = base_items->fetch (i);
      if (!(any || (hwc && item->get_type () == BaseMetric::HWCNTR)
	    || (bit && item->get_cmd ()
		&& strncmp (item->get_cmd (), Command::BIT_CMD,
			    strlen (Command::BIT_CMD)) == 0)
	    || dbe_strcmp (item->get_cmd (), mcmd) == 0))
	continue;
      if (item->is_internal ())
	continue;
      if (item->get_flavors () & BaseMetric::STATIC)
	{
	  got_metric = 0;
	  int vis = item->get_type () != BaseMetric::ONAME ?
		  dmetrics_visbits : VAL_VALUE;
	  if (append (item, BaseMetric::STATIC, vis) == NULL && !fromRcFile)
	    return 2;
	  continue;
	}

      // special case for omp metrics: make visible only if
      // omp data has been collected
      if (!dbeSession->is_omp_available ()
	  && (strcasecmp (mcmd, "ompwork") == 0
	      || strcasecmp (mcmd, "ompwait") == 0))
	  continue;

      for (int j = 0; j < nsubtypes; j++)
	{
	  if (append (item, subtypes[j], dmetrics_visbits) == NULL
	      && !fromRcFile)
	    return 2;
	}
      got_metric = 0;
      if (!(any || hwc || bit))
	break;
    }
  return got_metric;
}

// parse a single metric specification, to give:
//	a vector of subtypes, and a count of the number of them
//	an integer visibility
//	return the string for the metric name

char *
MetricList::parse_metric_spec (char *mcmd, BaseMetric::SubType *subtypes,
			       int *nsubtypes, int *dmetrics_visb, bool *isOK)
{
  size_t len_vtype;
  int index;
  int vis;
  bool got_e, got_i, got_a, got_d;
  char *str = mcmd;
  char *str2;

  *isOK = true;

  // For dynamic metrics, each keyword is of the form  <flavor><visibility><metric-name>
  // For static metrics, each keyword is of the form [<visibility>]<metric-name>
  // <flavor> can be either "i" for inclusive or "e" for exclusive
  // <visibility> can be any combination of "." (to show the metric as a time),
  //    "%" (to show it as a percentage), "+" (to show it as a count), and "!" (turn off the metric)

  // find subtype
  index = 0;
  size_t len_subtype = strspn (str, NTXT ("eiad"));
  str2 = str + len_subtype;

  // find vis
  if (len_subtype == 0)
    {
      // only a . or ! is possible if no subtypes
      len_vtype = strspn (str2, NTXT (".!"));
      vis = VAL_VALUE;
    }
  else
    {
      len_vtype = strspn (str2, NTXT (".+%!"));
      vis = VAL_NA;
    }

  // if no visibility bits, there can't be a subtype
  if (len_vtype == 0)
    len_subtype = 0;

  if (len_subtype == 0)
    {
      // must be a static metric
      subtypes[index++] = BaseMetric::STATIC;
      vis = VAL_VALUE;
    }
  else
    {
      // figure out which subtypes are specified
      got_e = got_i = got_a = got_d = false;
      for (size_t i = 0; i < len_subtype; i++)
	{
	  str += len_subtype;
	  if (mcmd[i] == 'e')
	    { // exclusive
	      if (mtype == MET_DATA)
		{
		  *isOK = false;
		  return dbe_sprintf (GTXT ("Invalid metric specification: %s inapplicable for data metrics\n"),
				      mcmd);
		}
	      if (!got_e)
		{
		  got_e = true;
		  subtypes[index++] = BaseMetric::EXCLUSIVE;
		}
	    }
	  else if (mcmd[i] == 'i')
	    { // inclusive
	      if (mtype == MET_DATA)
		{
		  *isOK = false;
		  return dbe_sprintf (GTXT ("Invalid metric specification: %s inapplicable for data metrics\n"),
				      mcmd);
		}
	      if (mtype == MET_INDX)
		{
		  *isOK = false;
		  return dbe_sprintf (GTXT ("Invalid metric specification: %s inapplicable for index metrics\n"),
				      mcmd);
		}
	      if (!got_i)
		{
		  got_i = true;
		  subtypes[index++] = BaseMetric::INCLUSIVE;
		}
	    }
	  else if (mcmd[i] == 'a')
	    { // attributed
	      if (mtype != MET_CALL && mtype != MET_CALL_AGR)
		{
		  *isOK = false;
		  return dbe_sprintf (GTXT ("Invalid metric specification: %s applicable for caller-callee metrics only\n"),
				      mcmd);
		}
	      if (!got_a)
		{
		  got_a = true;
		  subtypes[index++] = BaseMetric::ATTRIBUTED;
		}
	    }
	  else if (mcmd[i] == 'd')
	    { // data-space
	      if (mtype != MET_DATA)
		{
		  *isOK = false;
		  return dbe_sprintf (GTXT ("Invalid metric specification: %s applicable for data-derived metrics only\n"),
				      mcmd);
		}
	      if (!got_d)
		{
		  got_d = true;
		  subtypes[index++] = BaseMetric::DATASPACE;
		}
	    }
	}
    }
  *nsubtypes = index;

  // now determine the visiblity bits
  if (len_vtype > 0)
    {
      for (size_t i = 0; i < len_vtype; i++)
	{
	  if (str2[i] == '+')
	    vis = (vis | VAL_VALUE);
	  else if (str2[i] == '.')
	    vis = (vis | VAL_TIMEVAL);
	  else if (str2[i] == '%')
	    vis = (vis | VAL_PERCENT);
	  else if (str2[i] == '!')
	    vis = (vis | VAL_HIDE_ALL);
	}
    }
  *dmetrics_visb = vis;
  return mcmd + len_subtype + len_vtype;
}