// file: $isip/class/stat/StatisticalModel/sm_05.cc
// version: $Id: sm_05.cc 9378 2003-12-23 20:56:11Z alphonso $
//

// system include files
//
#include <typeinfo>

// isip include files
//
#include "StatisticalModel.h"
#include <GaussianModel.h>
#include <MixtureModel.h>
#include <UniformModel.h>
#include <SupportVectorModel.h>
#include <RelevanceVectorModel.h>

// method: splitMixtureModel
//
// arguments:
//  long target_mixtures: (input) target number of mixtures desired
//
// return: a boolean value indicating status
//
// this method splits the mixture into the required number of components
//
boolean StatisticalModel::splitMixtureModel(long target_mixtures_a) {

  // declare local variables
  //
  long mix_num = 0;  
  long new_num_mix = 0;
  long num_mixtures = 0;
  
  VectorFloat mean;
  VectorFloat temp;  
  VectorFloat mean1;
  VectorFloat mean2;  
  VectorFloat variance;  
  MatrixFloat covar;
  VectorFloat perturbation;
  GaussianModel gauss_model1;
  GaussianModel gauss_model2;
	  
  StatisticalModel* stat_model = (StatisticalModel*)NULL;
  MixtureModel* mixture_model = (MixtureModel*)NULL;
  GaussianModel* gauss_model = (GaussianModel*)NULL;
  SingleLinkedList<StatisticalModel> mixture_models;  
  SingleLinkedList<StatisticalModel> new_mixture_models;
  
  // check to make sure that the underlying model is a mixture
  //
  if (typeid(*virtual_model_d) != typeid(MixtureModel)) {
    return Error::handle(name(), L"splitMixtureModel", Error::ARG, __FILE__, __LINE__);
  }

  // branch on the implementation
  //
  if (algorithm_d == MIXTURE_SPLITTING &&
      implementation_d == VARIANCE_SPLITTING) {

    // get the underlying mixture model
    //
    mixture_model = (MixtureModel*)virtual_model_d;

    // get the mixture models
    //
    mixture_models = mixture_model->getModels();
    
    // loop over the existing models in the mixture and split them until
    // the required number of mixtures are created
    //
    while (new_mixture_models.length() < target_mixtures_a) {

      // get the number of existing mixtures
      //
      new_num_mix = 0;
      num_mixtures = mixture_models.length();
      new_mixture_models.clear();
      
      // make sure we have some models to split to start off
      //
      if (num_mixtures == 0) {
	return Error::handle(name(), L"splitMixtureModel", Error::ARG, __FILE__, __LINE__);
      }
          
      // loop over all models in the mixture
      //
      for (boolean more = mixture_models.gotoFirst(); more;
	   more = mixture_models.gotoNext()) {

	// get the underlying model from the mixture
	//
	stat_model = mixture_models.getCurr();

	// when the underlying model is gaussian
	//
	if (typeid(*stat_model->virtual_model_d) == typeid(GaussianModel)) {

	  // get the gaussian model form the statistical model
	  //
	  gauss_model = (GaussianModel*)stat_model->virtual_model_d;

	  // get the mean and covariance of the current model
	  //
	  gauss_model->getMean(mean);
	  gauss_model->getCovariance(covar);
	  
	  // get the diagonal of the covariance matrix
	  //
	  covar.getDiagonal(variance);

	  // compute the model perturbation
	  //
	  variance.sqrt();
	  perturbation.assign(variance);
	  perturbation.mult(DEF_PERTURB_FACTOR);
	  
	  // perturb the mean of the model
	  //
	  temp.assign(mean);
	  temp.add(perturbation);
	  mean1.assign(temp);
	  
	  temp.assign(mean);
	  temp.sub(perturbation);
	  mean2.assign(temp);  
	  
	  // create two gaussian models formed by splitting the current model
	  //
	  gauss_model->setMean(mean1);
	  gauss_model1.assign(*gauss_model);
	  gauss_model->setMean(mean2);	  
	  gauss_model2.assign(*gauss_model);
	  
	  StatisticalModel stat_model1(gauss_model1);
	  StatisticalModel stat_model2(gauss_model2);	  

	  // add the new models to the list
	  //
	  new_mixture_models.insert(&stat_model1);
	  new_mixture_models.insert(&stat_model2);	  
	  
	  // increment the mixture counts
	  //
	  new_num_mix += 2;
	  mix_num++;

	  // stop splitting if the current split plus the rest of the original
	  // mixtures are equal to the target number of mixtures
	  //
	  if (((new_num_mix + num_mixtures - mix_num) == target_mixtures_a) &&
	      (new_num_mix != target_mixtures_a)) {
	    while (mixture_models.gotoNext()) {
	      new_mixture_models.insert(mixture_models.getCurr());
	    }
	    break;
	  }

	  // if more mixtures are needed after loop over the current mixtures
	  // then we wrap around
	  //
	  if (mix_num == num_mixtures) {
	    mix_num = 0;
	    mixture_models.assign(new_mixture_models);
	    break;
	  }
	}

	// all other unsupported models
	//
	else {
	  return Error::handle(name(), L"splitMixtureModel", Error::ARG, __FILE__, __LINE__);
	}
      }
    }

    // assign the new models to the mixture model
    //
    mixture_model->setModels(new_mixture_models);
    
    // set the mixture weights to be equal
    //
    mixture_model->initializeWeights();
  }

  else {
    return Error::handle(name(), L"splitMixtureModel", Error::ARG, __FILE__, __LINE__);
  }
    
  // exit gracefully
  //
  return true;
}

// method: addModelToMixture
//
// arguments:
//  StatisticalModel& model: (input) input model
//  StatisticalModel& mixture: (output) mixture model
//
// return: a boolean value indicating status
//
// this method adds the input model to the mixture
//
boolean StatisticalModel::addModelToMixture(StatisticalModel& model_a,
					    StatisticalModel& mixture_a) {

  // declare local variables
  //
  MixtureModel* mixture_model = (MixtureModel*)NULL;  
  StatisticalModelBase* model = (StatisticalModelBase*)NULL;

  // make sure that the model and mixture are not the same
  //
  if (&model_a == &mixture_a) {
    return Error::handle(name(), L"addModelToMixture", Error::ARG, __FILE__, __LINE__);
  }
  
  // set the output model to be of type mixture if needed
  //
  if (typeid(*mixture_a.virtual_model_d) != typeid(MixtureModel)) {
    mixture_a.setType(MIXTURE_MODEL);
  }

  // cast the statistical model virtual pointer to a mixture model
  //
  mixture_model = (MixtureModel*)mixture_a.virtual_model_d;

  // get the virtual base pointer of the model
  //
  model = model_a.virtual_model_d;
    
  // add the input model to the mixture
  //
  mixture_model->add(*model);

  // reinitialize the weights of the mixture model
  //
  mixture_model->initializeWeights();
  
  // exit gracefully
  //
  return true;
}

// method: assign
//
// arguments:
//  const StatisticalModelBase& arg: (input) object to assign
//
// return: a boolean value indicating status
//
// this method assigns input argument to the current model
//
boolean StatisticalModel::assign(const StatisticalModelBase& arg_a) {

  // shortcut assign the data if we are already of the appropriate type
  //
  if (typeid(arg_a) == typeid(virtual_model_d)) {
    return virtual_model_d->assign(arg_a);
  }

  if (typeid(arg_a) == typeid(GaussianModel)) {
    setType(GAUSSIAN_MODEL);
  }
  else if (typeid(arg_a) == typeid(MixtureModel)) {
    setType(MIXTURE_MODEL);
  }
  else if (typeid(arg_a) == typeid(UniformModel)) {
    setType(UNIFORM_MODEL);
  }
  else if (typeid(arg_a) == typeid(SupportVectorModel)) {
    setType(SUPPORT_VECTOR_MODEL);
  }
  else if (typeid(arg_a) == typeid(RelevanceVectorModel)) {
    setType(RELEVANCE_VECTOR_MODEL);
  }  
  else {
    return Error::handle(name(), L"assign", Error::ENUM, __FILE__, __LINE__);
  }
  
  // exit gracefully
  //
  return virtual_model_d->assign(arg_a);
}

// method: clear
//
// arguments:
//  Integral::CMODE cmode: (input) clear mode
//
// return: a boolean value indicating status
//
// this method clears the model. in RETAIN mode, the virtual model pointer
// is maintained; all other modes reset the virtual model pointer to the
// default value.
//
boolean StatisticalModel::clear(Integral::CMODE cmode_a) {

  // clear up memory and reset
  //
  virtual_model_d->clear(cmode_a);

  // further clear internal data if necessary
  //
  if ((cmode_a == Integral::RESET) || (cmode_a == Integral::FREE) ||
      (cmode_a == Integral::RELEASE)) {
    if (virtual_model_d != (StatisticalModelBase*)&NO_STAT_MODEL) {
      delete virtual_model_d;
      virtual_model_d = (StatisticalModelBase*)&NO_STAT_MODEL;
    }
  }

  // exit gracefully
  //
  return true;  
}

// method: setType
//
// arguments:
//  TYPE type: (input) type of algorithm
//
// return: a boolean value indicating status
//
// this method sets the type of the virtual model. this object must
// be clear before setType is called.
//
boolean StatisticalModel::setType(TYPE type_a) {

  if (virtual_model_d != (StatisticalModelBase*)&NO_STAT_MODEL) {
    clear();
  }

  if (type_a == UNKNOWN) {
    return true;
  }
  else if (type_a == GAUSSIAN_MODEL) {
    virtual_model_d = new GaussianModel;
  } 
  else if (type_a == MIXTURE_MODEL) {
    virtual_model_d = new MixtureModel;
  }
  else if (type_a == UNIFORM_MODEL) {
    virtual_model_d = new UniformModel;
  }
  else if (type_a == SUPPORT_VECTOR_MODEL) {
    virtual_model_d = new SupportVectorModel;
  }
  else if (type_a == RELEVANCE_VECTOR_MODEL) {
    virtual_model_d = new RelevanceVectorModel;
  }  
  
  // invalid type
  //
  else {
    return Error::handle(name(), L"setType", Error::ENUM, __FILE__, __LINE__);
  }

  // exit gracefully
  //
  return true;
}
