/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

/****************************************************************************
 *
 *  WebOverview.cc - code to overview page
 *
 *
 ****************************************************************************/

#include "libts.h"

#include "WebOverview.h"
#include "WebGlobals.h"
#include "WebMgmtUtils.h"

#include "LocalManager.h"
#include "ClusterCom.h"
#include "MgmtUtils.h"
#include "MgmtDefs.h"
#include "Diags.h"

// Make this pointer to avoid nasty destruction
//   problems do to alarm
//   fork, execl, exit squences
overviewPage *overviewGenerator;

overviewRecord::overviewRecord(unsigned long inet_addr, bool local, ClusterPeerInfo * cpi)
{

  char *name_l;                 // hostname looked up from node record
  bool name_found;
  struct in_addr nameFailed;

  inetAddr = inet_addr;

  this->up = false;
  this->localNode = local;

  // If this is the local node, there is no cluster peer info
  //   record.  Remote nodes require a cluster peer info record
  ink_assert((local == false && cpi != NULL)
             || (local == true && cpi == NULL));

  // Set up the copy of the records array and initialize it
  if (local == true) {
    node_rec_data.num_recs = 0;
    node_rec_data.recs = NULL;
    recordArraySize = 0;
    node_rec_first_ix = 0;
  } else {
    node_rec_data.num_recs = cpi->node_rec_data.num_recs;
    recordArraySize = node_rec_data.num_recs * sizeof(RecRecord);
    node_rec_data.recs = new RecRecord[recordArraySize];
    memcpy(node_rec_data.recs, cpi->node_rec_data.recs, recordArraySize);

    // Recaculate the old relative index
    RecGetRecordOrderAndId(node_rec_data.recs[0].name, &node_rec_first_ix, NULL);
  }


  // Query for the name of the node.  If it is not there, some
  //   their cluster ip address
  name_l = this->readString("proxy.node.hostname_FQ", &name_found);
  if (name_found == false || name_l == NULL) {
    nameFailed.s_addr = inetAddr;
    mgmt_log("[overviewRecord::overviewRecord] Unable to find hostname for %s\n", inet_ntoa(nameFailed));
    ats_free(name_l);              // about to overwrite name_l, so we need to free it first
    name_l = ats_strdup(inet_ntoa(nameFailed));
  }

  const size_t hostNameLen = strlen(name_l) + 1;
  this->hostname = new char[hostNameLen];
  ink_strlcpy(this->hostname, name_l, hostNameLen);
  ats_free(name_l);
}

overviewRecord::~overviewRecord()
{

  delete[]hostname;

  if (localNode == false) {
    delete[]node_rec_data.recs;
  }
}

// void overviewRecord::updateStatus(time_t, ClusterPeerInfo*)
// updates up/down status based on the cluster peer info record
//
//   currentTime is the value of localtime(time()) - sent in as
//     a parameter so we do not have to make repetitive system calls.
//     overviewPage::checkForUpdates can just make one call
//
//   cpi - is a pointer to a structure we got from ClusterCom that represnets
//         information about this node
//
//   a machine is up if we have heard from it in the last 15 seconds
//
void
overviewRecord::updateStatus(time_t currentTime, ClusterPeerInfo * cpi)
{

  // Update if the node is up or down
  if (currentTime - cpi->idle_ticks > 15) {
    up = false;
  } else {
    up = true;
  }

  // Update the node records by copying them from cpi
  //  (remote nodes only)
  if (localNode == false) {
    memcpy(node_rec_data.recs, cpi->node_rec_data.recs, recordArraySize);
    RecGetRecordOrderAndId(node_rec_data.recs[0].name, &node_rec_first_ix, NULL);
  }
}

// bool overviewRecord::ipMatch(char* ipStr)
//
//   Returns true if the passed in string matches
//     the ip address for this node
bool
overviewRecord::ipMatch(char *ipStr)
{
  if (inet_addr(ipStr) == inetAddr) {
    return true;
  } else {
    return false;
  }
}

//  overview::readCounter, overview::readInteger
//
//  Accessor functions for node records.  For remote node,
//    we get the value in the node_data array we maintain
//    in this object.  For the node, we do not maintain any data
//    and rely on lmgmt->record_data for both the retrieval
//    code and the records array
//
//  Locking should be done by overviewPage::accessLock.
//  CALLEE is responsible for obtaining and releasing the lock
//
RecCounter
overviewRecord::readCounter(const char *name, bool * found)
{
  RecCounter rec = 0;
  int rec_status = REC_ERR_OKAY;
  int order = -1;
  if (localNode == false) {
    rec_status = RecGetRecordOrderAndId(name, &order, NULL);
    if (rec_status == REC_ERR_OKAY) {
      order -= node_rec_first_ix; // Offset
      ink_release_assert(order < node_rec_data.num_recs);
      ink_assert(order < node_rec_data.num_recs);
      rec = node_rec_data.recs[order].data.rec_counter;
    } else {
      mgmt_log(stderr, "node variables '%s' not found!\n");
    }
  }

  if (found) {
    *found = (rec_status == REC_ERR_OKAY);
  } else {
    mgmt_log(stderr, "node variables '%s' not found!\n");
  }
  return rec;
}

RecInt
overviewRecord::readInteger(const char *name, bool * found)
{
  RecInt rec = 0;
  int rec_status = REC_ERR_OKAY;
  int order = -1;
  if (localNode == false) {
    rec_status = RecGetRecordOrderAndId(name, &order, NULL);
    if (rec_status == REC_ERR_OKAY) {
      order -= node_rec_first_ix; // Offset
      ink_release_assert(order < node_rec_data.num_recs);
      ink_assert(order < node_rec_data.num_recs);
      rec = node_rec_data.recs[order].data.rec_int;
    }
  } else {
    rec_status = RecGetRecordInt(name, &rec);
  }

  if (found) {
    *found = (rec_status == REC_ERR_OKAY);
  } else {
    mgmt_log(stderr, "node variables '%s' not found!\n");
  }
  return rec;
}

RecFloat
overviewRecord::readFloat(const char *name, bool * found)
{
  RecFloat rec = 0.0;
  int rec_status = REC_ERR_OKAY;
  int order = -1;
  if (localNode == false) {
    rec_status = RecGetRecordOrderAndId(name, &order, NULL);
    if (rec_status == REC_ERR_OKAY) {
      order -= node_rec_first_ix; // Offset
      ink_release_assert(order < node_rec_data.num_recs);
      ink_assert(order < node_rec_data.num_recs);
      rec = node_rec_data.recs[order].data.rec_float;
    }
  } else {
    rec_status = RecGetRecordFloat(name, &rec);
  }

  if (found) {
    *found = (rec_status == REC_ERR_OKAY);
  } else {
    mgmt_log(stderr, "node variables '%s' not found!\n");
  }
  return rec;
}

RecString
overviewRecord::readString(const char *name, bool * found)
{
  RecString rec = NULL;
  int rec_status = REC_ERR_OKAY;
  int order = -1;
  if (localNode == false) {
    rec_status = RecGetRecordOrderAndId(name, &order, NULL);
    if (rec_status == REC_ERR_OKAY) {
      order -= node_rec_first_ix; // Offset
      ink_release_assert(order < node_rec_data.num_recs);
      ink_assert(order < node_rec_data.num_recs);
      rec = ats_strdup(node_rec_data.recs[order].data.rec_string);
    }
  } else {
    rec_status = RecGetRecordString_Xmalloc(name, &rec);
  }

  if (found) {
    *found = (rec_status == REC_ERR_OKAY);
  } else {
    mgmt_log(stderr, "node variables '%s' not found!\n");
  }
  return rec;
}

//  overview::readData, read RecData according varType
//
//  Accessor functions for node records.  For remote node,
//    we get the value in the node_data array we maintain
//    in this object.  For the node, we do not maintain any data
//    and rely on lmgmt->record_data for both the retrieval
//    code and the records array
//
//  Locking should be done by overviewPage::accessLock.
//  CALLEE is responsible for obtaining and releasing the lock
//
RecData
overviewRecord::readData(RecDataT varType, const char *name, bool * found)
{
  int rec_status = REC_ERR_OKAY;
  int order = -1;
  RecData rec;
  RecDataClear(RECD_NULL, &rec);

  if (localNode == false) {
    rec_status = RecGetRecordOrderAndId(name, &order, NULL);
    if (rec_status == REC_ERR_OKAY) {
      order -= node_rec_first_ix; // Offset
      ink_release_assert(order < node_rec_data.num_recs);
      ink_assert(order < node_rec_data.num_recs);
      RecDataSet(varType, &rec, &node_rec_data.recs[order].data);
    } else {
      Fatal("node variables '%s' not found!\n", name);
    }
  } else
    rec_status = RecGetRecord_Xmalloc(name, varType, &rec, true);

  if (found) {
    *found = (rec_status == REC_ERR_OKAY);
  } else {
    mgmt_log(stderr, "node variables '%s' not found!\n");
  }
  return rec;
}

// bool overviewRecord::varStrFromName (char*, char*bufVal, char*, int)
//
//  Accessor function for node records.  Looks up varName for
//    this node and if found, turns it value into a string
//    and places it in bufVal
//
//  return true if bufVal was succefully set
//    and false otherwise
//
//  EVIL ALERT: varStrFromName in WebMgmtUtils.cc is extremely
//    similar to this function except in how it gets it's
//    data.  Changes to this fuction must be propogated
//    to its twin.  Cut and Paste sucks but there is not
//    an easy way to merge the functions
//
bool
overviewRecord::varStrFromName(const char *varNameConst, char *bufVal, int bufLen)
{
  char *varName;
  RecDataT varDataType;
  bool found = true;
  int varNameLen;
  char formatOption = '\0';

  union
  {
    MgmtIntCounter counter_data;        /* Data */
    MgmtInt int_data;
    MgmtFloat float_data;
    MgmtString string_data;
  } data;

  // Check to see if there is a \ option on the end of variable
  //   \ options indicate that we need special formatting
  //   of the results.  Supported \ options are
  //
  ///  b - bytes.  Ints and Counts only.  Amounts are
  //       transformed into one of GB, MB, KB, or B
  //
  varName = ats_strdup(varNameConst);
  varNameLen = strlen(varName);
  if (varNameLen > 3 && varName[varNameLen - 2] == '\\') {
    formatOption = varName[varNameLen - 1];

    // Now that we know the format option, terminate the string
    //   to make the option disappear
    varName[varNameLen - 2] = '\0';

    // Return not found for unknown format options
    if (formatOption != 'b' && formatOption != 'm' && formatOption != 'c' && formatOption != 'p') {
      ats_free(varName);
      return false;
    }
  }
  if (RecGetRecordDataType(varName, &varDataType) == REC_ERR_FAIL) {
    ats_free(varName);
    return false;
  }

  switch (varDataType) {
  case RECD_INT:
    data.int_data = this->readInteger(varName, &found);
    if (formatOption == 'b') {
      bytesFromInt(data.int_data, bufVal);
    } else if (formatOption == 'm') {
      MbytesFromInt(data.int_data, bufVal);
    } else if (formatOption == 'c') {
      commaStrFromInt(data.int_data, bufVal);
    } else {
      snprintf(bufVal, bufLen, "%" PRId64 "", data.int_data);
    }
    break;
  case RECD_COUNTER:
    data.counter_data = this->readCounter(varName, &found);
    if (formatOption == 'b') {
      bytesFromInt((MgmtInt) data.counter_data, bufVal);
    } else if (formatOption == 'm') {
      MbytesFromInt((MgmtInt) data.counter_data, bufVal);
    } else if (formatOption == 'c') {
      commaStrFromInt(data.counter_data, bufVal);
    } else {
      snprintf(bufVal, bufLen, "%" PRId64 "", data.counter_data);
    }
    break;
  case RECD_FLOAT:
    data.float_data = this->readFloat(varName, &found);
    if (formatOption == 'p') {
      percentStrFromFloat(data.float_data, bufVal);
    } else {
      snprintf(bufVal, bufLen, "%.2f", data.float_data);
    }
    break;
  case RECD_STRING:
    data.string_data = this->readString(varName, &found);
    if (data.string_data == NULL) {
      bufVal[0] = '\0';
    } else {
      ink_strlcpy(bufVal, data.string_data, bufLen);
    }
    ats_free(data.string_data);
    break;
  case RECD_NULL:
  default:
    found = false;
    break;
  }

  ats_free(varName);
  return found;
}

bool
overviewRecord::varCounterFromName(const char *name, MgmtIntCounter * value)
{
  bool found = false;

  if (value)
    *value = readCounter((char *) name, &found);
  return found;
}

bool
overviewRecord::varIntFromName(const char *name, MgmtInt * value)
{
  bool found = false;

  if (value)
    *value = readInteger((char *) name, &found);
  return found;
}

bool
overviewRecord::varFloatFromName(const char *name, MgmtFloat * value)
{
  bool found = false;

  if (value)
    *value = readFloat((char *) name, &found);

  return found;
}

overviewPage::overviewPage():sortRecords(10, false)
{

  ink_mutex_init(&accessLock, "overviewRecord");
  nodeRecords = ink_hash_table_create(InkHashTableKeyType_Word);
  numHosts = 0;
  ourAddr = 0;                  // We will update this when we add the record for
  //  this machine
}

overviewPage::~overviewPage()
{

  // Since we only have one global object and we never destruct it
  //  do not actually free memeory since it causes problems the
  //  process is vforked, and the child execs something
  // The below code is DELIBERTLY commented out
  //
  // ink_mutex_destroy(&accessLock);
  // ink_hash_table_destroy(nodeRecords);
}

// overviewPage::checkForUpdates - updates node records as to whether peers
//    are up or down
void
overviewPage::checkForUpdates()
{

  ClusterPeerInfo *tmp;
  InkHashTableEntry *entry;
  InkHashTableIteratorState iterator_state;
  overviewRecord *current;
  time_t currentTime;
  bool newHostAdded = false;

  // grok through the cluster communication stuff and update information
  //  about hosts in the cluster
  //
  ink_mutex_acquire(&accessLock);
  ink_mutex_acquire(&(lmgmt->ccom->mutex));
  currentTime = time(NULL);
  for (entry = ink_hash_table_iterator_first(lmgmt->ccom->peers, &iterator_state);
       entry != NULL; entry = ink_hash_table_iterator_next(lmgmt->ccom->peers, &iterator_state)) {

    tmp = (ClusterPeerInfo *) ink_hash_table_entry_value(lmgmt->ccom->peers, entry);

    if (ink_hash_table_lookup(nodeRecords, (InkHashTableKey) tmp->inet_address, (InkHashTableValue *) & current) == 0) {
      this->addRecord(tmp);
      newHostAdded = true;
    } else {
      current->updateStatus(currentTime, tmp);
    }
  }
  ink_mutex_release(&lmgmt->ccom->mutex);

  // If we added a new host we must resort sortRecords
  if (newHostAdded) {
    this->sortHosts();
  }

  ink_mutex_release(&accessLock);
}


// overrviewPage::sortHosts()
//
// resorts sortRecords, but always leaves the local node
//   as the first record
//
// accessLock must be held by callee
void
overviewPage::sortHosts()
{
  void **array = sortRecords.getArray();

  qsort(array + 1, numHosts - 1, sizeof(void *), hostSortFunc);
}

// overviewPage::addRecord(ClusterPerrInfo* cpi)
//   Adds a new node record
//   Assuems that this->accessLock is already held
//
void
overviewPage::addRecord(ClusterPeerInfo * cpi)
{

  overviewRecord *newRec;

  ink_assert(cpi != NULL);

  newRec = new overviewRecord(cpi->inet_address, false, cpi);
  newRec->updateStatus(time(NULL), cpi);

  ink_hash_table_insert(nodeRecords, (InkHashTableKey) cpi->inet_address, (InkHashTableEntry *) newRec);

  sortRecords.addEntry(newRec);
  numHosts++;
}

// adds a record to nodeRecords for the local machine.
//   gets IP addr from lmgmt->ccom so cluster communtication
//   must be intialized before calling this function
//
//
void
overviewPage::addSelfRecord()
{

  overviewRecord *newRec;

  ink_mutex_acquire(&accessLock);

  // We should not have been called before
  ink_assert(ourAddr == 0);

  // Find out what our cluster addr is from
  //   from cluster com
  this->ourAddr = lmgmt->ccom->getIP();

  newRec = new overviewRecord(ourAddr, true);
  newRec->up = true;

  ink_hash_table_insert(nodeRecords, (InkHashTableKey) this->ourAddr, (InkHashTableEntry *) newRec);

  sortRecords.addEntry(newRec);
  numHosts++;
  ink_mutex_release(&accessLock);
}

// int overviewPage::getClusterHosts(Expanding Array* hosts)
//
//   The names of all the cluster members are inserted
//     into parameter hosts.  The callee is responsible
//     for freeing the strings
//
int
overviewPage::getClusterHosts(ExpandingArray * hosts)
{
  int number = 0;

  overviewRecord *current;

  ink_mutex_acquire(&accessLock);
  number = sortRecords.getNumEntries();

  for (int i = 0; i < number; i++) {
    current = (overviewRecord *) sortRecords[i];
    hosts->addEntry(ats_strdup(current->hostname));
  }

  ink_mutex_release(&accessLock);
  return number;
}

// overviewRecord* overviewPage::findNodeByName(const char* nodeName)
//
//   Returns a pointer to node name nodeName
//     If node name is not found, returns NULL
//
//   CALLEE MUST BE HOLDING this->accessLock
//
overviewRecord *
overviewPage::findNodeByName(const char *nodeName)
{
  overviewRecord *current = NULL;
  bool nodeFound = false;

  // Do a linear search of the nodes for this nodeName.
  //   Yes, I know this is slow but the current word is ten
  //   nodes would be a huge cluster so this should not
  //   be a problem
  //
  for (int i = 0; i < numHosts; i++) {
    current = (overviewRecord *) sortRecords[i];
    if (strcmp(nodeName, current->hostname) == 0) {
      nodeFound = true;
      break;
    }
  }

  if (nodeFound == true) {
    return current;
  } else {
    return NULL;
  }
}

// MgmtString overviewPage::readString(const char* nodeName, char* *name, bool *found = NULL)
//
//   Looks up a node record for a specific by nodeName
//    CALLEE deallocates the string with free()
//
MgmtString
overviewPage::readString(const char *nodeName, const char *name, bool * found)
{
  MgmtString r = NULL;
  //  bool nodeFound = false;
  bool valueFound = false;
  overviewRecord *node;

  ink_mutex_acquire(&accessLock);

  node = this->findNodeByName(nodeName);

  if (node != NULL) {
    r = node->readString(name, &valueFound);
  }
  ink_mutex_release(&accessLock);

  if (found != NULL) {
    *found = valueFound;
  }

  return r;
}

// MgmtInt overviewPage::readInteger(const char* nodeName, char* *name, bool *found = NULL)
//
//   Looks up a node record for a specific by nodeName
//
MgmtInt
overviewPage::readInteger(const char *nodeName, const char *name, bool * found)
{
  MgmtInt r = -1;
  //  bool nodeFound = false;
  bool valueFound = false;
  overviewRecord *node;

  ink_mutex_acquire(&accessLock);

  node = this->findNodeByName(nodeName);

  if (node != NULL) {
    r = node->readInteger(name, &valueFound);
  }
  ink_mutex_release(&accessLock);

  if (found != NULL) {
    *found = valueFound;
  }

  return r;
}

// MgmtFloat overviewPage::readFloat(const char* nodeName, char* *name, bool *found = NULL)
//
//   Looks up a node record for a specific by nodeName
//
RecFloat
overviewPage::readFloat(const char *nodeName, const char *name, bool * found)
{
  RecFloat r = -1.0;
  //  bool nodeFound = false;
  bool valueFound = false;
  overviewRecord *node;

  ink_mutex_acquire(&accessLock);

  node = this->findNodeByName(nodeName);

  if (node != NULL) {
    r = node->readFloat(name, &valueFound);
  }
  ink_mutex_release(&accessLock);

  if (found != NULL) {
    *found = valueFound;
  }

  return r;
}

// int overviewPage::clusterSumData(RecDataT varType, const char* nodeVar,
//                                  RecData* sum)
//
//   Sums nodeVar for every up node in the cluster and stores the
//     sum in *sum.  Returns the number of nodes summed over
//
//   CALLEE MUST HOLD this->accessLock
//
int
overviewPage::clusterSumData(RecDataT varType, const char *nodeVar,
                             RecData *sum)
{
  int numUsed = 0;
  int numHosts_local = sortRecords.getNumEntries();
  overviewRecord *current;
  bool found;
  RecData recTmp;

  ink_assert(sum != NULL);
  RecDataClear(varType, sum);

  for (int i = 0; i < numHosts_local; i++) {
    current = (overviewRecord *) sortRecords[i];
    if (current->up == true) {
      numUsed++;
      recTmp = current->readData(varType, nodeVar, &found);
      *sum = RecDataAdd(varType, *sum, recTmp);
      if (found == false) {
      }
    }
  }
  return numUsed;
}

int
overviewPage::varClusterDataFromName(RecDataT varType, char *nodeVar,
                                     RecData *sum)
{
  int status = 0;

  ink_mutex_acquire(&accessLock);

  status = clusterSumData(varType, nodeVar, sum);

  ink_mutex_release(&accessLock);

  return (status);
}

// Moved from the now removed StatAggregation.cc
void
AgFloat_generic_scale_to_int(const char *processVar, const char *nodeVar, double factor)
{
  MgmtFloat tmp;

  if (varFloatFromName(processVar, &tmp)) {
    tmp = tmp * factor;
    tmp = tmp + 0.5;            // round up.
    varSetInt(nodeVar, (int) tmp);
  } else {
    varSetInt(nodeVar, -20);
  }
}

// char* overviewPage::resolvePeerHostname(char* peerIP)
//
//   A locking interface to overviewPage::resolvePeerHostname_ml
//
char *
overviewPage::resolvePeerHostname(const char *peerIP)
{
  char *r;

  ink_mutex_acquire(&accessLock);
  r = this->resolvePeerHostname_ml(peerIP);
  ink_mutex_release(&accessLock);

  return r;
}

// char* overviewPage::resolvePeerHostname_ml(char* peerIP)
//
// Resolves the peer the hostname from its IP address
//   The hostname is resolved by finding the overviewRecord
//   Associated with the IP address and copying its hostname
//
// CALLEE frees storage
// CALLEE is responsible for locking
//
char *
overviewPage::resolvePeerHostname_ml(const char *peerIP)
{
  unsigned long int ipAddr;
  InkHashTableValue lookup;
  overviewRecord *peerRecord;
  char *returnName = NULL;

  ipAddr = inet_addr(peerIP);

  // Check to see if our address is malformed
  if ((long int) ipAddr == -1) {
    return NULL;
  }

  if (ink_hash_table_lookup(nodeRecords, (InkHashTableKey) ipAddr, &lookup)) {
    peerRecord = (overviewRecord *) lookup;
    returnName = ats_strdup(peerRecord->hostname);
  }

  return returnName;
}

// int hostSortFunc(const void* arg1, const void* arg2)
//
//   A compare function that we can to qsort that sorts
//    overviewRecord*
//
int
hostSortFunc(const void *arg1, const void *arg2)
{
  overviewRecord *rec1 = (overviewRecord *) * (void **) arg1;
  overviewRecord *rec2 = (overviewRecord *) * (void **) arg2;

  return strcmp(rec1->hostname, rec2->hostname);
}
