#include <Attributes.h>
#include <AttributeParser.h>
#include <StringAttribute.h>
#include <IntegerAttribute.h>

#include "dtClusterHMM.hh"
#include "System.h"
#include "SmartSamplerExceptions.hh"

#define DEFAULT_CLUSTER_NAME "plume"

/*********************************************************************************
 **
 ** class dtClusterHMM  -- Decision tool for choosing to gulp based on a hidden
 **       Markov Model of cluster state.
 **
 *********************************************************************************/

// structors :

dtClusterHMM::dtClusterHMM(char *name)
  :cluster_name(NULL),m_name(strdup(name)),m_hmm(m_name),ssd(NULL)
{
  ssdbg(DBG_LOAD)("creating dtClusterHMM \"%s\" class",m_name);
}

dtClusterHMM::~dtClusterHMM() {
  ssdbg(DBG_CLEAN)("dtClusterHMM -- Destructor called.");
  free((void *)m_name);
  free((void *)cluster_name);
}

// Manipulators :

// Parse configuration file, load in the name of cluster, the state
// names, the transition table, and the table of conditional
// probabilities.  Check that the appropriate cluster exists, and
// initialize the HMM.  Some of these configuration attributes should
// really be parsed by the HMM class, but I didn't want to end up with
// too many short configuration files.
void dtClusterHMM::loadConfig(char *cfgname,SmartSamplerData *ssdIn){
  Attributes cfg_attributes(cfgname);
  char       *statestr,*initstr,*tabcond, *tabtrans,*tmp,*tmp2,*fotstr;
  long       maxobs;
  std::vector<string> statelist;
  int        i,thresh;
  fot_t      fot;
  const char *cfgFileName = System::configurationFile( cfgname );

  ssd = ssdIn;

  System::copyToLogDir(cfgFileName);

  cfg_attributes.add(new StringAttribute("classifier", "cluster classifier name", 
					 &cluster_name, DEFAULT_CLUSTER_NAME ) );
  cfg_attributes.add(new StringAttribute("hmmstates", "list of hmm states", 
					 &statestr) );
  cfg_attributes.add(new StringAttribute("hmminitial", "initial state at time zero", 
					 &initstr) );
  cfg_attributes.add(new IntegerAttribute("hmmmaxobs", "total number of allowed observations", 
					  &maxobs) );
  cfg_attributes.add(new StringAttribute("hmmtrans", "config file for hmm transition table", 
					 &tabtrans) );
  cfg_attributes.add(new StringAttribute("hmmcond", "config file for hmm conditional table", 
					 &tabcond) );
  cfg_attributes.add(new StringAttribute("fireonthresh","probability threshold based firing specs",
					 &fotstr));

  // pick up attributes defined at this level:
  AttributeParser::reset();
  try{
      AttributeParser::parse(cfgFileName, &cfg_attributes); 
  }catch(...){
    throw LoadError("Failed to parse dtClusterHMM attributes.");
    exit(0);
  }

  // Once we've picked up cluster_name, we can tell data manager we want to attach.
  ssd->useCluster(cluster_name);

  ssdbg(DBG_LOAD)("dtClusterHMM -- Found classifier %s, hmm trans table cfg %s, hmm cond table cfg %s",
	cluster_name,tabcond,tabtrans);
  ssdbg(DBG_LOAD)("dtClusterHMM -- Initial state %s, num obs %d, state list %s,fireonthresh list %s",
	initstr,maxobs,statestr,fotstr);

  // Next we split the list of states into individual strings and initialize each state.
  // We are limited by the AttributeParser as to how we form our list.
  tmp = strtok(statestr,".");
  while(tmp != NULL){
    if(!strcmp(tmp,initstr)){
      statelist.insert(statelist.begin(),tmp);
    }else{
      statelist.push_back(tmp);
    }
    tmp = strtok(NULL,".");
  }

  // Next, we call the HMM loader to load the tables.
  m_hmm.loadConfig(tabtrans,tabcond,&statelist,maxobs);

  // Here we handle the threshold firing directives.
  tmp = strtok(fotstr,".");
  while(tmp != NULL){
    tmp2 = strtok(NULL,".");
    if(tmp2 == NULL){
      Syslog::write("Fire On Threshold configuration attribute %s has no associated threshold!",tmp);
      throw LoadError("Error reading Fire On Threshold attribute.");
      exit(0);
    }
    fot.state = m_hmm.findstate(tmp);
    if(EOF == sscanf(tmp2,"%d",&thresh)){
      Syslog::write("Error converting Fire On Threshold string \"%s\" (for state %s) to an integer!",tmp2,tmp);
      throw LoadError("Error reading Fire On Threshold attribute.");
      exit(0);
    }
    fot.thresh = (prob_t)thresh/100; // Convert to a percentage
    Syslog::write("SmartSampler -- will fire when probability of state %s (index=%d) crosses threshold of %.04f",
		  tmp,fot.state,fot.thresh);
    firethresh.push_back(fot);
    tmp = strtok(NULL,".");
  }

  // Free up strings allocated by StringAttribute.
  free((void *)statestr);
  free((void *)initstr);
  free((void *)tabcond);
  free((void *)tabtrans);
  free((void *)fotstr);
}

// update the state machine based on cluster values, return true if
// we want to fire gulper.
bool dtClusterHMM::update(){
  unsigned short cl_best = 0;
  double cl_dist;
  bool requestFire = false;

  ssd->getCluster(cluster_name,&cl_best,&cl_dist);
  m_hmm.update((obs_t)cl_best);
  requestFire = fireDecision();
  if(DEBUG_CLUSTER_STATE){
    dumpstate();
  }
  return(requestFire);
}

void dtClusterHMM::dumpstate(){
  m_hmm.dumpstate();
}

void dtClusterHMM::reset(){
  m_hmm.reset();
}

// Right now, we're just hardcoding a very simple decision.  If either
// of the states called "fire" crosses the probability threshold, fire
// the next gulper.
bool dtClusterHMM::fireDecision(){
  bool doFire;
  foti_t fot;

  doFire = false;
  for(fot=firethresh.begin();fot!=firethresh.end();fot++){
    if((m_hmm.maxprev(fot->state) <= fot->thresh) && 
       (m_hmm.statecur(fot->state) > fot->thresh)){
      doFire = true;
    }
  }
  return(doFire);
}
