// cm_comp_0.cc
//

// system include files
//
#include <string.h>

// isip include files
//
#include "create_mixtures.h"
#include "create_mixtures_constants.h"

// method: compute_cc
//
// arguments:
//  State** in_states : (input) input states
//  State** out_states : (output) output states with increased mixture count
//  int_4 num_states : (input) number of input states
//  int_4 num_features : (input) number of input features
//  int_4 target_mixtures : (input) target number of mixtures
//
// return: a logical_1 flag indicating success
//
// this method splits existing mixtures to form required number of mixtures
//
static int float_compare_cc(const void* in_1_a, const void* in_2_a) {
  if (*(float_4*)in_1_a < *(float_4*)in_2_a)	
    return (1);	
  if (*(float_4*)in_1_a > *(float_4*)in_2_a)	
    return (-1);	
  return (0);
}

logical_1 compute_cc(Train_State** in_states_a, Train_State**& out_states_a,
		     int_4 num_states_a, int_4 num_features_a,
		     int_4 target_mixtures_a){

  // define local variables
  //
  int_4 num_mixtures;
  int_4 new_num_mix = 0;
  int_4* hash = (int_4*)NULL;
  logical_1* done = (logical_1*)NULL;
  float_4* mixtures = (float_4*)NULL;
  float_4* temp_mixtures = (float_4*)NULL;
  float_4* mix_weights = (float_4*)NULL;
  float_4* old_scale = (float_4*)NULL;
  float_4* scale = (float_4*)NULL;
  float_4** old_mean = (float_4**)NULL;
  float_4** old_covar = (float_4**)NULL;
  float_4** mean = (float_4**)NULL;
  float_4** covar = (float_4**)NULL;
  
  // loop over all states
  //
  for (int_4 i = 1; i < num_states_a; i++) {
    
    // set the num mixtures in new state
    //
    out_states_a[i] = new Train_State();
    out_states_a[i]->set_num_mixtures_cc(target_mixtures_a);

    // sort the mixtures based on mixture weight (descending order)
    //
    num_mixtures = in_states_a[i]->get_num_mixtures_cc();
    old_scale = in_states_a[i]->get_scale_cc();
    temp_mixtures = new float_4[num_mixtures];
    mixtures = in_states_a[i]->get_weights_cc();
    memcpy(temp_mixtures, mixtures, num_mixtures*sizeof(float_4));    
    qsort((char*)mixtures, num_mixtures, sizeof(float_4), float_compare_cc);

    // get the hash values: a primitive way of hashing
    //
    hash = new int_4[num_mixtures];
    done = new logical_1[num_mixtures];
    memset(done, (logical_1)ISIP_FALSE, num_mixtures);
    for (int_4 j = 0; j < num_mixtures; j++) {
      for (int_4 k = 0; k < num_mixtures; k++) {
	if (temp_mixtures[k] == mixtures[j] && done[k] == ISIP_FALSE) {
	  hash[j] = k;
	  done[k] = ISIP_TRUE;
	  break;
	}
      }
    }	       
      
    // create space for new means and covariances for mixtures in the new
    // state and for old means and covariances in old state
    //

    // means
    //
    old_mean = in_states_a[i]->get_mean_cc();
    mean = new float_4*[target_mixtures_a];
    for (int_4 j = 0; j < target_mixtures_a; j++) {
      mean[j] = new float_4[num_features_a];
    }

    // variances
    //
    old_covar = in_states_a[i]->get_covar_cc();
    covar = new float_4*[target_mixtures_a];
    for (int_4 j = 0; j < target_mixtures_a; j++) {
      covar[j] = new float_4[num_features_a];
    }

    // mixture weights
    //
    mix_weights = new float_4[target_mixtures_a];
    scale = new float_4[target_mixtures_a];
    
    // traverse through the ordered list and spilt till required number of
    // mixtures is created
    //
    int_4 mix_num = 0;
    new_num_mix = 0;

    while (new_num_mix < target_mixtures_a) {

      // split mixture into two
      //
      memcpy(mean[new_num_mix], old_mean[hash[mix_num]],
	     num_features_a*sizeof(float_4));
      memcpy(covar[new_num_mix], old_covar[hash[mix_num]],
	     num_features_a*sizeof(float_4));

      // perturb the mean
      //
      // loop over all features and update mean by perturbing
      //
      for (int_4 j = 0; j < num_features_a; j++) {
	
	// set the new mean
	//
	mean[new_num_mix][j] += CM_PERTURB_FACTOR*
	  sqrt(1.0/covar[new_num_mix][j]);
      }
      memcpy(mean[new_num_mix+1], old_mean[hash[mix_num]],
	     num_features_a*sizeof(float_4));
      memcpy(covar[new_num_mix+1], old_covar[hash[mix_num]],
	     num_features_a*sizeof(float_4));

      // perturb the mean
      //
      // loop over all features and update mean by perturbing
      //
      for (int_4 j = 0; j < num_features_a; j++) {
	
	// set the new mean
	//
	mean[new_num_mix+1][j] += CM_PERTURB_FACTOR*
	  (-1)*sqrt(1.0/covar[new_num_mix+1][j]);
      }
      scale[new_num_mix] = old_scale[hash[mix_num]];
      scale[new_num_mix+1] = old_scale[hash[mix_num]];
      
      // increment mixture counts
      //
      new_num_mix += 2;
      mix_num++;

      // stop splitting if current split plus rest of original mixtures equal
      // the target number of mixtures
      //
      if ((new_num_mix + num_mixtures - mix_num) == target_mixtures_a &&
	new_num_mix != target_mixtures_a) {
	for (int_4 j = 0; j < num_mixtures - mix_num; j++) {
	  memcpy(mean[new_num_mix+j], old_mean[hash[mix_num+j]],
		 num_features_a*sizeof(float_4));
	  memcpy(covar[new_num_mix+j], old_covar[hash[mix_num+j]],
		 num_features_a*sizeof(float_4));
	  scale[new_num_mix+j] = old_scale[hash[mix_num+j]];
	}
	break;
      }
      
      // if more mixtures needed after looping through current mixtures,
      // wrap around
      //
      if (mix_num == num_mixtures) {
	mix_num = 0;
      }
    }

    // set the mixture weights to be equal
    //
    for (int_4 j = 0; j < target_mixtures_a; j++) {
      mix_weights[j] = -1*log(target_mixtures_a);
    }
      
    // set the new means and convariances of the new state
    //
    out_states_a[i]->set_mean_cc(mean);
    out_states_a[i]->set_covar_cc(covar);
    out_states_a[i]->set_weights_cc(mix_weights);
    out_states_a[i]->set_scale_cc(scale);
    
    // clean up memory
    //
    delete [] done;
    done = (logical_1*)NULL;
    delete [] hash;
    hash = (int_4*)NULL;
    delete [] mix_weights;
    mix_weights = (float_4*)NULL;
    delete [] scale;
    scale = (float_4*)NULL;
    
    for (int_4 j =0; j < target_mixtures_a; j++) {
      delete [] mean[j];
      delete [] covar[j];
      mean[j] = (float_4*)NULL;
      covar[j] = (float_4*)NULL;
    }    
    delete [] mean;
    delete [] covar;
    delete [] temp_mixtures;
    mixtures = (float_4*)NULL;
    mix_weights = (float_4*)NULL;
    old_mean = (float_4**)NULL;
    old_covar = (float_4**)NULL;
    mean = (float_4**)NULL;
    covar = (float_4**)NULL;
  }
  
  // exit gracefully
  //
  return ISIP_TRUE;
}
