/* 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 <ctype.h>

#include "util.h"
#include "DbeSession.h"
#include "Application.h"
#include "Experiment.h"
#include "Exp_Layout.h"
#include "MetricList.h"
#include "MemObject.h"
#include "PathTree.h"
#include "DbeView.h"
#include "Metric.h"
#include "MemorySpace.h"
#include "Table.h"
#include "IndexObject.h"

MemObjType_t::MemObjType_t ()
{
  type = -1;
  name = NULL;
  index_expr = NULL;
  machmodel = NULL;
  mnemonic = 0;
  short_description = NULL;
  long_description = NULL;
}

MemObjType_t::~MemObjType_t ()
{
  free (name);
  free (index_expr);
  free (machmodel);
  free (short_description);
  free (long_description);
}

MemorySpace::MemorySpace (DbeView *_dbev, int _mstype)
{
  char *mname;
  dbev = _dbev;
  phaseIdx = -1;

  // set up the MemoryObject information
  objs = new HashMap<uint64_t, MemObj*>;
  mstype = _mstype;
  msindex_exp = NULL;
  msname = NULL;
  msindex_exp_str = NULL;

  // find the memory space in the table
  MemObjType_t *mot = findMemSpaceByIndex (mstype);
  if (mot)
    {
      msname = dbe_strdup (mot->name);
      if (mot->index_expr != NULL)
	{
	  msindex_exp_str = dbe_strdup (mot->index_expr);
	  msindex_exp = dbeSession->ql_parse (msindex_exp_str);
	  if (msindex_exp == NULL)
	    // this was checked when the definition was created
	    abort ();
	}
    }

  // create the Total and Unknown objects
  mname = dbe_strdup (NTXT ("<Total>"));
  total_memobj = createMemObject ((uint64_t) - 2, mname);
  mname = dbe_strdup (GTXT ("<Unknown>"));
  unk_memobj = createMemObject ((uint64_t) - 1, mname);
  hist_data_all = NULL;
  selected_mo_index = (uint64_t) - 3;
  sel_ind = -1;
}

MemorySpace::~MemorySpace ()
{
  reset ();
  delete objs;
  free (msname);
  free (msindex_exp);
  free (msindex_exp_str);
}

void
MemorySpace::reset ()
{
  if (hist_data_all != NULL)
    {
      delete hist_data_all;
      hist_data_all = NULL;
    }
  // do not clear the selected object's index
  // selected_mo_index = (uint64_t)-3;

  // destroy any existing objects, but keep the vector
  // Now that we have a hashmap, which has its own vector,
  //     safe to delete and reallocate
  delete objs;
  objs = new HashMap<uint64_t, MemObj*>;
}

// find a memory object by its memory-object index
int
MemorySpace::findMemObject (uint64_t indx)
{
  int index;
  Hist_data::HistItem *hi;
  if (indx == (uint64_t) - 3)
    return -1;

  Vec_loop (Hist_data::HistItem *, hist_data_all->hist_items, index, hi)
  {
    if (((uint64_t) ((MemObj *) hi->obj)->id) == indx)
      return index;
  }
  // object does not exist; filter change eliminated it, for example
  return -1;
}

// find the object referenced in the packet
MemObj *
MemorySpace::lookupMemObject (Experiment *exp, DataView *packets, long i)
{
  uint64_t idx;
  uint64_t va = (uint64_t) packets->getLongValue (PROP_VADDR, i);
  if (va == ABS_UNSUPPORTED)
    // return NULL, to ignore the record
    return NULL;
  if (va < ABS_CODE_RANGE)
    // The va is not valid, rather, it's an error code
    // return the <Unknown> object
    return unk_memobj;

  Expression::Context ctx (dbev, exp, packets, i);
  idx = msindex_exp->eval (&ctx);
  if (idx == (uint64_t) - 1)
    return unk_memobj;

  // do a binary search for the memory object
  MemObj *res = objs->get (idx);
  if (res == NULL)
    {
      res = createMemObject (idx, NULL);
      objs->put (idx, res);
    }
  else
    return res;

  // recompute range
  if (idx < idx_min)
    idx_min = idx;
  if (idx > idx_max)
    idx_max = idx;
  return res;
}

MemObj *
MemorySpace::createMemObject (uint64_t index, char *moname)
{
  MemObj *res;
  char *name;
  if (moname != NULL)
    {
      res = new MemObj (index, moname);
      return res;
    }

  // Custom memory objects
  // The memory_page_size is defined in the machine model file such
  // as ./machinemodels/t4.ermm.
  // Most users prefer to look at the hexadecimal version of virtual
  // addresses. Display only the hexadecimal version of virtual addresses
  // for all machine model views with an exception of virtual page size.
  if (dbe_strcmp (msname, NTXT ("Memory_page_size")) == 0)
    name = dbe_sprintf (NTXT ("%s 0x%16.16llx (%llu)"), msname,
			(long long) index, (unsigned long long) index);
  else if (dbe_strcmp (msname, NTXT ("Memory_in_home_lgrp")) == 0)
    name = dbe_sprintf (NTXT ("%s: %s"), msname,
			index == 1 ? GTXT ("True") : index == 0 ? GTXT ("False")
			: GTXT ("<Unknown>"));
  else if (dbe_strcmp (msname, NTXT ("Memory_lgrp")) == 0)
    name = dbe_sprintf (NTXT ("%s %llu"), msname, (unsigned long long) index);
  else
    name = dbe_sprintf (NTXT ("%s 0x%16.16llx"), msname, (long long) index);

  res = new MemObj (index, name);
  return res;
}


static Vector<MemObjType_t*> dyn_memobj_vec;
static Vector<MemObjType_t*> *dyn_memobj = &dyn_memobj_vec;
static Vector<int> *ordlist;

// Static function to get a vector of custom memory object definitions

Vector<void*> *
MemorySpace::getMemObjects ()
{
  MemObjType_t *mot;
  int ii;
  int size = dyn_memobj->size ();
  Vector<int> *indx = new Vector<int>(size);
  Vector<char*> *name = new Vector<char*>(size);
  Vector<char> *mnemonic = new Vector<char>(size);
  Vector<char*> *formula = new Vector<char*>(size);
  Vector<char*> *machmodel = new Vector<char*>(size);
  Vector<int> *order = new Vector<int>(size);
  Vector<char*> *sdesc = new Vector<char*>(size);
  Vector<char*> *ldesc = new Vector<char*>(size);

  if (size > 0)
    {
      Vec_loop (MemObjType_t *, dyn_memobj, ii, mot)
      {
	indx->store (ii, mot->type);
	order->store (ii, ii);
	name->store (ii, dbe_strdup (mot->name));
	formula->store (ii, dbe_strdup (mot->index_expr));
	mnemonic->store (ii, mot->mnemonic);
	sdesc->store (ii, mot->short_description == NULL ? NULL
		      : dbe_strdup (mot->short_description));
	ldesc->store (ii, mot->long_description == NULL ? NULL
		      : dbe_strdup (mot->long_description));
	if (mot->machmodel == NULL)
	  machmodel->store (ii, NULL);
	else
	  machmodel->store (ii, dbe_strdup (mot->machmodel));
      }
    }
  Vector<void*> *res = new Vector<void*>(8);
  res->store (0, indx);
  res->store (1, name);
  res->store (2, mnemonic);
  res->store (3, formula);
  res->store (4, machmodel);
  res->store (5, order);
  res->store (6, sdesc);
  res->store (7, ldesc);
  return (res);
}

// Static function to set order of memory object tabs
void
MemorySpace::set_MemTabOrder (Vector<int> *orders)
{
  int size = orders->size ();
  ordlist = new Vector<int>(size);
  for (int i = 0; i < size; i++)
    ordlist->store (i, orders->fetch (i));
}

// Static function to define a new memory object type
char *
MemorySpace::mobj_define (char *mname, char *mindex_exp, char *_machmodel,
			  char *short_description, char *long_description)
{
  MemObjType_t *mot;

  if (mname == NULL)
    return dbe_strdup (GTXT ("No memory object name has been specified."));
  if (isalpha ((int) (mname[0])) == 0)
    return dbe_sprintf (GTXT ("Memory Object type name %s does not begin with an alphabetic character"),
			mname);
  char *p = mname;
  while (*p != 0)
    {
      if (isalnum ((int) (*p)) == 0 && *p != '_')
	return dbe_sprintf (GTXT ("Memory Object type name %s contains a non-alphanumeric character"),
			    mname);
      p++;
    }

  mot = findMemSpaceByName (mname);
  if (mot != NULL)
    {
      if (strcmp (mot->index_expr, mindex_exp) == 0)
	// It's a redefinition, but the new definition is the same
	return NULL;
      return dbe_sprintf (GTXT ("Memory/Index Object type name %s is already defined"),
			  mname);
    }

  // make sure the name is not in use
  if (dbeSession->findIndexSpaceByName (mname) >= 0)
    return dbe_sprintf (GTXT ("Memory/Index Object type name %s is already defined"),
			mname);

  if (mindex_exp == NULL || *mindex_exp == 0)
    return dbe_strdup (GTXT ("No index-expr has been specified."));

  // verify that the index expression parses correctly
  Expression *e = dbeSession->ql_parse (mindex_exp);
  if (e == NULL)
    return dbe_sprintf (GTXT ("Memory Object index expression is invalid: %s"),
			mindex_exp);
  delete e;

  // It's OK, create the new table entry
  char *s = dbeSession->indxobj_define (mname, NULL, mindex_exp,
					short_description, long_description);
  if (s)
    return s;
  IndexObjType_t *indObj = dbeSession->findIndexSpace (mname);

  mot = new MemObjType_t;
  mot->type = indObj->type;
  indObj->memObj = mot;
  mot->name = dbe_strdup (mname);
  mot->index_expr = dbe_strdup (mindex_exp);
  mot->mnemonic = MemorySpace::pickMnemonic (mname);
  mot->machmodel = dbe_strdup (_machmodel);
  mot->short_description = dbe_strdup (short_description);
  mot->long_description = dbe_strdup (long_description);

  // add it to the list
  dyn_memobj->append (mot);

  // tell the session
  if (dbeSession != NULL)
    dbeSession->mobj_define (mot);
  return NULL;
}

// Static function to delete a new memory object type

char *
MemorySpace::mobj_delete (char *mname)
{
  if (mname == NULL)
    return dbe_strdup (GTXT ("No memory object name has been specified.\n"));

  // search the dynamic types
  for (long idx = 0, sz = VecSize (dyn_memobj); idx < sz; idx++)
    {
      MemObjType_t *mt = dyn_memobj->get (idx);
      if (strcasecmp (mt->name, mname) == 0)
	{
	  // delete it from the vector
	  mt = dyn_memobj->remove (idx);
	  delete mt;
	  dbeSession->removeIndexSpaceByName (mname);
	  return NULL;
	}
    }
  return dbe_sprintf (GTXT ("Memory object `%s' is not defined.\n"), mname);
}

// Static function to get a list of memory object names from a machine model

Vector <char*> *
MemorySpace::getMachineModelMemObjs (char *mname)
{
  Vector <char *> *ret = new Vector <char *> ();
  if (mname == NULL)
    return ret;

  // search the memory objects
  int idx;
  MemObjType_t *mt;
  Vec_loop (MemObjType_t*, dyn_memobj, idx, mt)
  {
    if (mt->machmodel != NULL && strcmp (mt->machmodel, mname) == 0)
      {
	char *n = dbe_strdup (mt->name);
	ret->append (n);
      }
  }
  return ret;
}

char
MemorySpace::pickMnemonic (char *name)
{
  return name[0];
}

void
MemorySpace::get_filter_keywords (Vector <void*> *res)
{
  Vector <char*> *kwCategory = (Vector<char*>*) res->fetch (0);
  Vector <char*> *kwCategoryI18N = (Vector<char*>*) res->fetch (1);
  Vector <char*> *kwDataType = (Vector<char*>*) res->fetch (2);
  Vector <char*> *kwKeyword = (Vector<char*>*) res->fetch (3);
  Vector <char*> *kwFormula = (Vector<char*>*) res->fetch (4);
  Vector <char*> *kwDescription = (Vector<char*>*) res->fetch (5);
  Vector <void*> *kwEnumDescs = (Vector<void*>*) res->fetch (6);

  char *vtypeNames[] = VTYPE_TYPE_NAMES;
  for (int i = 0, sz = dyn_memobj ? dyn_memobj->size () : 0; i < sz; i++)
    {
      MemObjType_t *obj = dyn_memobj->fetch (i);
      kwCategory->append (dbe_strdup (NTXT ("FK_MEMOBJ")));
      kwCategoryI18N->append (dbe_strdup (GTXT ("Memory Object Definitions")));
      kwDataType->append (dbe_strdup (vtypeNames[TYPE_INT64]));
      kwKeyword->append (dbe_strdup (obj->name));
      kwFormula->append (dbe_strdup (obj->index_expr));
      kwDescription->append (NULL);
      kwEnumDescs->append (NULL);
    }
}

MemObjType_t *
MemorySpace::findMemSpaceByName (const char *mname)
{
  int idx;
  MemObjType_t *mt;

  // search the dynamic types
  Vec_loop (MemObjType_t*, dyn_memobj, idx, mt)
  {
    if (strcasecmp (mt->name, mname) == 0)
      return mt;
  }
  return NULL;
}

MemObjType_t *
MemorySpace::findMemSpaceByIndex (int index)
{
  int idx;
  MemObjType_t *mt;

  // search the dynamic types
  Vec_loop (MemObjType_t*, dyn_memobj, idx, mt)
  {
    if (mt->type == index)
      return mt;
  }
  return NULL;
}