/*
*         OpenPBS (Portable Batch System) v2.3 Software License
*
* Copyright (c) 1999-2000 Veridian Information Solutions, Inc.
* All rights reserved.
*
* ---------------------------------------------------------------------------
* For a license to use or redistribute the OpenPBS software under conditions
* other than those described below, or to purchase support for this software,
* please contact Veridian Systems, PBS Products Department ("Licensor") at:
*
*    www.OpenPBS.org  +1 650 967-4675                  sales@OpenPBS.org
*                        877 902-4PBS (US toll-free)
* ---------------------------------------------------------------------------
*
* This license covers use of the OpenPBS v2.3 software (the "Software") at
* your site or location, and, for certain users, redistribution of the
* Software to other sites and locations.  Use and redistribution of
* OpenPBS v2.3 in source and binary forms, with or without modification,
* are permitted provided that all of the following conditions are met.
* After December 31, 2001, only conditions 3-6 must be met:
*
* 1. Commercial and/or non-commercial use of the Software is permitted
*    provided a current software registration is on file at www.OpenPBS.org.
*    If use of this software contributes to a publication, product, or
*    service, proper attribution must be given; see www.OpenPBS.org/credit.html
*
* 2. Redistribution in any form is only permitted for non-commercial,
*    non-profit purposes.  There can be no charge for the Software or any
*    software incorporating the Software.  Further, there can be no
*    expectation of revenue generated as a consequence of redistributing
*    the Software.
*
* 3. Any Redistribution of source code must retain the above copyright notice
*    and the acknowledgment contained in paragraph 6, this list of conditions
*    and the disclaimer contained in paragraph 7.
*
* 4. Any Redistribution in binary form must reproduce the above copyright
*    notice and the acknowledgment contained in paragraph 6, this list of
*    conditions and the disclaimer contained in paragraph 7 in the
*    documentation and/or other materials provided with the distribution.
*
* 5. Redistributions in any form must be accompanied by information on how to
*    obtain complete source code for the OpenPBS software and any
*    modifications and/or additions to the OpenPBS software.  The source code
*    must either be included in the distribution or be available for no more
*    than the cost of distribution plus a nominal fee, and all modifications
*    and additions to the Software must be freely redistributable by any party
*    (including Licensor) without restriction.
*
* 6. All advertising materials mentioning features or use of the Software must
*    display the following acknowledgment:
*
*     "This product includes software developed by NASA Ames Research Center,
*     Lawrence Livermore National Laboratory, and Veridian Information
*     Solutions, Inc.
*     Visit www.OpenPBS.org for OpenPBS software support,
*     products, and information."
*
* 7. DISCLAIMER OF WARRANTY
*
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT
* ARE EXPRESSLY DISCLAIMED.
*
* IN NO EVENT SHALL VERIDIAN CORPORATION, ITS AFFILIATED COMPANIES, OR THE
* U.S. GOVERNMENT OR ANY OF ITS AGENCIES BE LIABLE FOR ANY DIRECT OR INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This license will be governed by the laws of the Commonwealth of Virginia,
* without reference to its choice of law rules.
*/
#include <pbs_config.h>   /* the master config generated by configure */
#include <string>
#include <vector>
#include <stdio.h>
#include "attribute.h"
#include "resource.h"

#include "cray_energy.h"

class log_element
  {
public:
  std::string type;
  std::string value;
  log_element(std::string type_str,std::string value_str):type(type_str),value(value_str){}
  };

class rur_log_entry
  {
private:
  std::vector<log_element> elements;
  std::string getString(const char *start,const char *end);
public:
  rur_log_entry(){}
  bool jobid_matches(const char *jobid);
  std::string find_entry(const char *type);
  bool parse_line(const char *line);
  bool parse_comma_line(const char *line);
  u_long get_joules();
  };

u_long rur_log_entry::get_joules()
  {
  std::string energy = find_entry("plugin");
  if(energy.length() == 0) return 0;
  if(energy.find("energy",0) != 0) return 0;
  const char *ind = energy.c_str() + 6;
  while((*ind == ' ')||(*ind == '\t'))
    {
    ind++;
    }
  if(*ind == '[')
    {
    int start = ind + 1 - energy.c_str();
    int end = energy.rfind("]");
    if(end == (int)std::string::npos) return 0;
    std::string values = energy.substr(start,end - start);
    rur_log_entry energy_vals;
    if(!energy_vals.parse_comma_line(values.c_str())) return 0;
    std::string joules = energy_vals.find_entry("'energy_used'");
    if(joules.length() == 0) return 0;
    return(atol(joules.c_str()));
    }
  else if(*ind == '{')
    {
    int start = ind + 1 - energy.c_str();
    int end = energy.rfind("}");
    if(end == (int)std::string::npos) return 0;
    end--;
    std::string values = energy.substr(start,end - start);
    rur_log_entry energy_vals;
    if(!energy_vals.parse_line(values.c_str())) return 0;
    std::string joules = energy_vals.find_entry("\"energy_used\"");
    if(joules.length() == 0) return 0;
    return(atol(joules.c_str()));
    }
  return 0;
  }
std::string rur_log_entry::find_entry(const char *type)
  {
  std::string ret = "";
  for(std::vector<log_element>::iterator i = elements.begin();i != elements.end();i++)
    {
    if(!strcmp(i->type.c_str(),type))
      {
      ret = i->value;
      return ret;
      }
    }
  return ret;
  }

bool rur_log_entry::jobid_matches(const char *jobid)
  {
  std::string log_jobid = find_entry("jobid");
  if(log_jobid.length() == 0) return false;
  if(!strcmp(jobid,log_jobid.c_str())) return true;
  char *a = strdup(jobid);
  char *b = strdup(log_jobid.c_str());
  char *ind = a;
  while((*ind != '.')&&(*ind != '\0'))
    {
    ind++;
    }
  *ind = '\0';
  ind = b;
  while((*ind != '.')&&(*ind != '\0'))
    {
    ind++;
    }
  *ind = '\0';
  int result = strcmp(a,b);
  free(a);
  free(b);
  return (result == 0);
  }

std::string rur_log_entry::getString(const char *start,const char *end)
  {
  std::string ret = "";
  char *bf = (char *)malloc(end - start + 2);
  char *ind = bf;
  while((start <= end)&&((*start == ' ')||(*start == '\t')))
    {
    start++;
    }
  while((start < end)&&((*start != ' ')||(*start != '\t')))
    {
    *ind++ = *start++;
    }
  *ind = '\0';
  ret = bf;
  free(bf);
  return ret;
  }

bool rur_log_entry::parse_line(const char *line)
  {
  typedef enum {lookingForComma,inBrace,inSingleQuote,inDoubleQuote,inBracket} parseType;
  parseType parseState = lookingForComma;
  std::vector<int> stateStack;
  const char *firstColon = NULL;
  const char *typeStart = line;
  while(*line != '\0')
    {
    switch(parseState)
      {
      case lookingForComma:
        if(*line == ',')
          {
          if(firstColon == NULL) return false;
          std::string type = getString(typeStart,firstColon);
          std::string val = getString(firstColon + 1,line);
          elements.push_back(log_element(type,val));
          typeStart = line + 1;
          firstColon = NULL;
          }
        else if(*line == ':')
          {
          if(firstColon == NULL)
            {
            firstColon = line;
            }
          }
        else if(*line == '{')
          {
          parseState = inBrace;
          }
        else if(*line == '[')
          {
          parseState = inBracket;
          }
        else if(*line == '"')
          {
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          parseState = inSingleQuote;
          }
        break;
      case inBrace:
        if(*line == '}')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '[')
          {
          stateStack.push_back(parseState);
          parseState = inBracket;
          }
        else if(*line == '{')
          {
          stateStack.push_back(parseState);
          parseState = inBrace;
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inBracket:
        if(*line == ']')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '[')
          {
          stateStack.push_back(parseState);
          parseState = inBracket;
          }
        else if(*line == '{')
          {
          stateStack.push_back(parseState);
          parseState = inBrace;
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inDoubleQuote:
        if(*line == '"')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inSingleQuote:
        if(*line == '\'')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        break;
      }
    line++;
    }
  if(parseState != lookingForComma) return false;
  if(firstColon == NULL) return false;
  std::string type = getString(typeStart,firstColon);
  std::string val = getString(firstColon + 1,line);
  elements.push_back(log_element(type,val));

  return true;
  }

bool rur_log_entry::parse_comma_line(const char *line)
  {
  typedef enum {lookingForComma,inBrace,inSingleQuote,inDoubleQuote,inBracket} parseType;
  parseType parseState = lookingForComma;
  std::vector<int> stateStack;
  const char *firstComma = NULL;
  const char *typeStart = line;
  while(*line != '\0')
    {
    switch(parseState)
      {
      case lookingForComma:
        if(*line == ',')
          {
          if(firstComma == NULL)
            {
            firstComma = line;
            }
          else
            {
            std::string type = getString(typeStart,firstComma);
            std::string val = getString(firstComma + 1,line);
            elements.push_back(log_element(type,val));
            typeStart = line + 1;
            firstComma = NULL;
            }
          }
        else if(*line == '{')
          {
          parseState = inBrace;
          }
        else if(*line == '[')
          {
          parseState = inBracket;
          }
        else if(*line == '"')
          {
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          parseState = inSingleQuote;
          }
        break;
      case inBrace:
        if(*line == '}')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '[')
          {
          stateStack.push_back(parseState);
          parseState = inBracket;
          }
        else if(*line == '{')
          {
          stateStack.push_back(parseState);
          parseState = inBrace;
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inBracket:
        if(*line == ']')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '[')
          {
          stateStack.push_back(parseState);
          parseState = inBracket;
          }
        else if(*line == '{')
          {
          stateStack.push_back(parseState);
          parseState = inBrace;
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inDoubleQuote:
        if(*line == '"')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '\'')
          {
          stateStack.push_back(parseState);
          parseState = inSingleQuote;
          }
        break;
      case inSingleQuote:
        if(*line == '\'')
          {
          if(stateStack.size() == 0) parseState = lookingForComma;
          else
            {
            parseState = (parseType)stateStack.back();
            stateStack.pop_back();
            }
          }
        else if(*line == '"')
          {
          stateStack.push_back(parseState);
          parseState = inDoubleQuote;
          }
        break;
      }
    line++;
    }
  if(parseState != lookingForComma) return false;
  if(firstComma == NULL) return false;
  std::string type = getString(typeStart,firstComma);
  std::string val = getString(firstComma + 1,line);
  elements.push_back(log_element(type,val));

  return true;
  }

u_long get_energy_from_cray_file(const char *path,job *pjob)
  {
  char buff[10240];
  FILE *pLog = fopen(path,"r");
  if(pLog == NULL) return 0;
  u_long joules = 0;
  while(fgets(buff,sizeof(buff),pLog) != NULL)
    {
    rur_log_entry entry;
    if(entry.parse_line(buff))
      {
      //if(entry.jobid_matches(pjob->ji_globid))
      if(entry.jobid_matches(pjob->ji_qs.ji_jobid))
        {
        joules += entry.get_joules();
        }
      }
    }
  fclose(pLog);
  return joules;
  }

char *trimInPlace(char *buff)
  {
  char *start = buff;
  while(isspace(*start)) start++;
  char *end = start + strlen(start) - 1;
  while(isspace(*end)&&(end > start)) end--;
  end++;
  *end = '\0';
  return start;
  }

std::string get_path_to_rur_log(
    
  const char *configPath)

  {
  FILE        *pConfig = fopen(configPath,"r");
  std::string  path = "";

  if (pConfig == NULL)
    return path;

  char buff[20140];
  bool inFileSection = false;

  while (fgets(buff,sizeof(buff),pConfig) != NULL)
    {
    char *start = trimInPlace(buff);
    if (inFileSection)
      {
      if (*start == '[')
        {
        fclose(pConfig);
        return path;
        }

      if (!strncmp(start,"arg:",4))
        {
        start += 4;
        start = trimInPlace(start);
        path = start;
        fclose(pConfig);
        return path;
        }
      }
    else
      {
      if (!strcmp(start,"[file]"))
        {
        inFileSection = true;
        }
      }
    }

  fclose(pConfig);
  return(path);
  } // END get_path_to_rur_log()



void get_energy_used(
    
  job *pjob)

  {
  pbs_attribute *resc_used = &pjob->ji_wattr[JOB_ATR_resc_used];
  resource_def  *resc_def = NULL;
  resource      *pres = NULL;

  if(resc_used == NULL) return;
  if(resc_used->at_type != ATR_TYPE_RESC) return;
  resc_def = find_resc_def(svr_resc_def, "energy_used", svr_resc_size);
  if(resc_def == NULL) return;
  pres = find_resc_entry(resc_used, resc_def);

  resc_used->at_flags |= ATR_VFLAG_MODIFY;
  if (pres == NULL)
    {
    pres = add_resource_entry(resc_used, resc_def);
    pres->rs_value.at_flags |= ATR_VFLAG_SET;
    pres->rs_value.at_type = ATR_TYPE_LONG;
    }
  std::string rur = get_path_to_rur_log("/etc/opt/cray/rur/rur.conf");
  if(rur.length() == 0) return;
  pres->rs_value.at_val.at_long = get_energy_from_cray_file(rur.c_str(),pjob);
  }

