#include "KD_train.h"

double *KD_train_grad_paralel(cell *pairs, mlp *net, double b)
{

  register unsigned int i, j, k, l;
  unsigned int n = mlp_get_total_connections(net);
  double *grad = malloc(sizeof(double)*n);
  
  double Eb, Ea;
  double step = 0.0001;
  double value, dEdw;

  Ea = -1;
  l = 0;
  for (i = 0; i<mlp_get_num_layers(net)-1; i++) {
    for (j = 0; j<mlp_get_neurons(net, i); j++) {
      for (k = 0; k<mlp_get_neurons(net, i+1); k++) {
        if (((double)rand())/((double)RAND_MAX)>0.99) {

          if (Ea == -1) {
            Ea = KD_train_energy_paralel(pairs, net, b)/cell_size_n(pairs);
          }

          value = net->weights[i][j][k];
          net->weights[i][j][k] = value + step;
          Eb = KD_train_energy_paralel(pairs, net, b)/cell_size_n(pairs);
          net->weights[i][j][k] = value;
          dEdw = ((Eb-Ea)/pairs->n)/(step);//printf("grad = %.16f\n", dEdw);
          //printf("Eb = %.12f , Ea = %.12f , dE %.12f , step = %f\n", Eb, Ea, dEdw, step);
          grad[l] = dEdw;
          l++;

          //if (Eb<Ea) {
          //  net->weights[i][j][k] = value+step;
          //} else {
          //  net->weights[i][j][k] = value-step;
          //}

          //printf(" - %i/%i\n", l, n);
        } else {
          grad[l] = 0;
          l++;
        }
      }
    }
  }

  return grad;
}

void KD_train_minimize (cell *pairs, mlp* net, double b, double lp, double max_error, unsigned int max_iter, const char *logfile, const char *mlpfile)
{
  unsigned i, j, k, l, iter;
  double E, *grad;
  iter = 1;
  unsigned int n = mlp_get_total_connections(net);
  FILE *pFile;
  double dw;

  do {

    grad = KD_train_grad_paralel(pairs, net, b);

    l = 0;
    dw = 0;
    for (i = 0; i<mlp_get_num_layers(net)-1; i++) {
      for (j = 0; j<mlp_get_neurons(net, i); j++) {
        for (k = 0; k<mlp_get_neurons(net, i+1); k++) {
          net->weights[i][j][k] = net->weights[i][j][k] - grad[l]*lp;
          dw += pow(grad[l]*lp, 2);
          l++;
        }
      }
    }

    E = KD_train_energy_paralel(pairs, net, b)/cell_size_n(pairs);
    printf("%i/%i. E = %f , |dw| = %.30f\n", iter, max_iter, E, dw);
    pFile = fopen(logfile, "a");
    fprintf(pFile, "%i/%i. E = %f , |dw| = %.30f\n", iter, max_iter, E, dw);
    fclose(pFile);

    if (mlpfile!=NULL){
      mlp_save(net, mlpfile);
    }
    
    free(grad);
    iter++;

  } while ((iter<=max_iter));
}


double KD_train_lk (double DWW, double DWO)
{
  double d;
  d = 1+DWW-DWO;
  if (d>0) {
    return d;
  }
  return 0;
}


double KD_train_lk2 (double DWW, double DWO, double border)
{
  double d;
  d = border+DWW-DWO;
  if (d>0) {
    return d;
  }
  return 0;
}


void KD_train_add_word_to_list(list *L_words, char *word) {
  element *el;

  if (list_get_size(L_words) == 0) {
    // Vytvorim prvni slovo a dam ho do seznamu.
    el = element_alloc_string(word);
    list_add_last(L_words, element_clone(el));
    element_throw_away(el);
    return;
  }

  // Zkontroluje jestli je slovo v seznamu.
  list_first(L_words);
  do {

    // Porovnani slova WORD s aktualnim slovem v seznamu.
    if (strcmp(element_get_string(list_get_curent(L_words)), word) == 0) {
      // slovo bylo nalezeno tkze se nemusi pridavat nakonec seznamu aproto se ukonci tato funkce.
      return;
    }

  } while (list_next(L_words));

  // Pokud slovo nebylo nalezeno prida se nakonec.
  el = element_alloc_string(word);
  list_add_last(L_words, element_clone(el));
  element_throw_away(el);
}



matrix *KD_train_transform(matrix *seq, mlp *net) {
#ifdef KD_train_DEBUG
  if (matrix_size_m(seq) != mlp_get_num_inputs(net)) {
    printf("%s: %i: KD_train_transform: Matice ma jiny pocet radku (%i) nez je pocet vstupu do neuronove site (%i).\n", __FILE__, __LINE__, matrix_size_m(seq), mlp_get_num_inputs(net));
  }
#endif

  unsigned int i, j;
  matrix *tseq = matrix_alloc(mlp_get_num_outputs(net), matrix_size_n(seq));//printf("%i %i\n", mlp_get_num_outputs(net), matrix_size_n(seq));
  double *x = malloc(sizeof (double) *mlp_get_num_inputs(net));
  double *y = malloc(sizeof (double) *mlp_get_num_outputs(net));

  // Transformace.
  for (i = 0; i < matrix_size_n(seq); i++) { // Kazdy sloupec bude transformovan.
    // Naplneni vstupu neuronove site.
    for (j = 0; j < mlp_get_num_inputs(net); j++) {
      x[j] = matrix_get(seq, j, i);
    }

    // Transformace priznaku.
    mlp_run(net, x, y);

    // Naplneni vystupni matice s transformovanymi priznaky.
    for (j = 0; j < mlp_get_num_outputs(net); j++) {
      matrix_set(tseq, j, i, y[j]);
    }
  }

  // uvolneni pomocnych promenych.
  free(x);
  free(y);
  // Trasformovana matice.
  return tseq;
}


unsigned int KD_train_rand_int(unsigned int size, unsigned int num, unsigned int yes_no) {
#ifdef KD_train_DEBUG
  if (!(size >= 0)) {
    printf("%s: %i: Velikost musi bt vetsi jak 0.\n", __FILE__, __LINE__);
  }
#endif
  if (yes_no == 0) {
    size--;
  }
  double rd;
  unsigned int ri;
  rd = ((double) rand())*((double) size) / ((double) RAND_MAX + 1);
  ri = (unsigned int) floor(rd);
  if (yes_no == 0) {
    // Nahodne cislo nesmi mit hodnotu NUM.
    if (ri == num) {
      if (ri != size) {
        ri++;
      } else {
        if (ri == 0) {
          ri++;
        } else {
          ri--;
        }
      }
    }
  }

  return ri;
}

// Vrati 0 pokud jsou v obou strukturach stejne fonemy. Poku jsou ruzne,
// vrati prumernou vzdalenost.

double KD_train_phonem_dist(cell *A, cell *B) {
  unsigned int i, j;
  double dist;
  char *pcha, *pchb;
  dist = 0;

  //puts("A");cell_print(A);puts("B");cell_print(B);

  // Pro vsechny fonemy z A.
  for (i = 0; i < cell_size_n(A); i++) {
    // Fonem z A.
    pcha = element_get_string(cell_get(A, 0, i));
    // Pro vsechny fonemy z B.
    for (j = 0; j < cell_size_n(B); j++) {
      // Fonem z B.
      pchb = element_get_string(cell_get(B, 0, j));

      // Pokud je fonem z A a fonem z B stejny inkrementujeste vzdalenost o 1.
      if (strcmp(pcha, pchb) != 0) {
        dist++;
      }
    }
  }

  dist = (dist / (cell_size_n(A) * cell_size_n(B)));
  //printf("dist = %f\n", dist);

  // Navratova hodntoa (vzdalenost).
  return dist;
}

matrix *KD_train_find_diferent_words(dictionary *dict_op, cell *phone_structure) {
  unsigned int j, k, n_max;
  matrix *d;
  cell *CB;
  matrix *difwords;
  double phdist, max;

  // Vytvoreni matice fonetickych vzdalenosti jednotlivych slov.
  d = matrix_alloc(1, dict_op->n);

  // Urcovani fonetickych vzdalenosti s jednotlivymi slovy.
  for (j = 0; j < dict_op->n; j++) {
    CB = dictionary_get_phonemes(dict_op, j); //cell_print(CB);
    phdist = KD_train_phonem_dist(phone_structure, CB);
    matrix_set(d, 0, j, phdist); //printf("word : %s , phdist = %f\n", dictionary_get_name(dict_op, j), phdist);
  }

  // Nalezeni maxima a zjisteni poctu.
  max = 0;
  for (j = 0; j < matrix_size_n(d); j++) {
    if (max < matrix_get(d, 0, j)) {
      max = matrix_get(d, 0, j);
    }
  }

  // Zjisteni poctu maxim.
  n_max = 0;
  for (j = 0; j < matrix_size_n(d); j++) {
    if ((max == matrix_get(d, 0, j))) {
      n_max++;
    }
  }

  // Vytvoreni seznamu rozdilnych slov.
  k = 0;
  difwords = matrix_alloc(1, n_max);
  for (j = 0; j < matrix_size_n(d); j++) {
    if ((max == matrix_get(d, 0, j))) {
      matrix_set(difwords, 0, k, (double) j);
      k++;
    }
  }

  // Uvolneni pomocnych promenych.
  matrix_free(d);

  // Navratova hodnota.
  return difwords;
}


cell *KD_train_create_train_pairs3(dictionary *dict, dictionary *not_belong, int report) {
  unsigned int i, j, ri, n, words_count, id;
  cell *wclnot, *trp, *phone_struct;
  matrix *rand_mat, *possible, *impossible, *diffwords, *feature, *feature_same, *feature_different;
  element *el;
  dictionary_class *word_class, *word_class_other;
  unsigned int min_utterance = 2;

  // Zjisteni velikosti treningove mnoziny.
  n = 0;
  words_count = 0;
  for (i = 0; i < dict->n; i++) {
    word_class = dictionary_get(dict, i);
    if (word_class->n >= min_utterance) {
      n += word_class->n;
      words_count++;
    }
  }
  
  // Vytvoreni treningove mnoziny.
  trp = cell_alloc(3, n);

  // Naplneni treningove mnoziny.
  id = 0;
  for (i = 0; i < dict->n; i++) {
    word_class = dictionary_get(dict, i);

    //if(cell_size_m(wcldict)>=2) {

    if (word_class->n >= min_utterance) { // Pokud ma dana trida vic jak jeden prvek.

      phone_struct = dictionary_class_get_phonemes(word_class);
      diffwords = KD_train_find_diferent_words(not_belong, phone_struct);
      //cell_print_info(diffwords);
      //printf("i = %i  ,  |diffwords| = %li\n", i, cell_size_n(diffwords));
      impossible = matrix_linspace(word_class->n, 1, 0, word_class->n-1, MATRIX_LINSPACE_COLUMN);
      rand_mat = matrix_rand_int_no_repeating(word_class->n, 1);
      possible = matrix_reshuffle_impossible(rand_mat, impossible);

      if (report==1) {
        printf("\ntemplate : %s\nother : ", dictionary_get_name(dict, i));
      }

      for (j = 0; j < word_class->n; j++) {
        // Aktualni slovo.
        feature = matrix_clone(dictionary_class_get_features(word_class, j));
        cell_set(trp, 0, id, element_alloc_matrix(feature));
        // Nahodne vybrane slovo ze stejne tridy.
        ri = (unsigned int) matrix_get(possible, j, 0);
        if (ri==j) {
          printf("Pozor : index RI (= %i) je stejny jako J (= %i), nekde je chyba !", ri, j);
          puts("rand_mat");
          matrix_print(rand_mat);
          puts("possible");
          matrix_print(possible);
          puts("impossible");
          matrix_print(impossible);
        }
        feature_same = matrix_clone(dictionary_class_get_features(word_class, ri));
        cell_set(trp, 1, id, element_alloc_matrix(feature_same));
        // Nahodne vybrane slovo ktere je odlisne.
        ri = KD_train_rand_int(matrix_size_n(diffwords) - 1, 0, 1);
        if (report==1) {
          printf("%s ", dictionary_get_name(dict, ri));
        }
        word_class_other = dictionary_get(dict, (unsigned int)matrix_get(diffwords, 0, ri));
        ri = KD_train_rand_int(word_class_other->n - 1, 0, 1);
        feature_different = matrix_clone(dictionary_class_get_features(word_class_other, ri));
        cell_set(trp, 2, id, element_alloc_matrix(feature_different));
        id++;
      }

      matrix_free(possible);
      matrix_free(rand_mat);
      matrix_free(impossible);
      matrix_free(diffwords);

    }


  }

  if (report != 0) {
    printf("\nCelkem bylo vytvoreno %i treningovych dvojic, ktere tvori %i slovnich trid.\n", cell_size_n(trp), words_count);
  }
  
  return trp;
}


void KD_train_train_pairs_transform(cell *pairs, mlp *net, matrix *avarge, matrix *stdev)
{

  unsigned int i, j, k;
  element *a, *b, *c;
  matrix *ma, *mb, *mc;

  if (net!=NULL) { // Pokud se maji priznaky transformovat pomoci neuronove site NET

    for (i = 0; i<cell_size_n(pairs); i++) {

      a = cell_get(pairs, 0, i);
      b = cell_get(pairs, 1, i);
      c = cell_get(pairs, 2, i);

      ma = KD_train_transform(element_get_matrix(a), net);
      cell_set(pairs, 0, i, element_alloc_matrix(ma));
      element_free(a);


      mb = KD_train_transform(element_get_matrix(b), net);
      cell_set(pairs, 1, i, element_alloc_matrix(mb));
      element_free(b);

      mc = KD_train_transform(element_get_matrix(c), net);
      cell_set(pairs, 2, i, element_alloc_matrix(mc));
      element_free(c);

    }
    
  }

  if ((avarge!=NULL)&(stdev!=NULL)) { // Pokud se ma aplikovat stat_norm na priznaky

    for (i = 0; i<cell_size_n(pairs); i++) {

      // Jednotlive matice, ktere tvori trenovaci dvojice (MA s MB a MA s MC)
      ma = element_get_matrix(cell_get(pairs, 0, i));
      mb = element_get_matrix(cell_get(pairs, 1, i));
      mc = element_get_matrix(cell_get(pairs, 2, i));

      // Aplikace stat_norm
      sp_stat_norm_aply(ma, avarge, stdev);
      sp_stat_norm_aply(mb, avarge, stdev);
      sp_stat_norm_aply(mc, avarge, stdev);

    }

  }

}


dictionary *KD_train_create_dictionary(cell *dict)
{
  unsigned int i, j, L;
  char *name;
  cell *wcl;
  dictionary_class *dcl;
  dictionary *d = dictionary_alloc(cell_size_n(dict));
  //cell_print_info(dict);

  for (i = 0; i<cell_size_n(dict); i++) {
    L = strlen(element_get_string(cell_get(dict, 0, i)))+1;
    name = malloc(sizeof(char)*L);
    memcpy(name, element_get_string(cell_get(dict, 0, i)), L);
    wcl = element_get_cell(cell_get(dict, 1, i));
    dcl = dictionary_class_alloc(cell_size_m(wcl));
    dictionary_class_set_phonemes(dcl, cell_clone(element_get_cell(cell_get(dict, 2, i))));
    dictionary_class_set_name(dcl, name);
    for (j = 0; j<cell_size_m(wcl); j++) {
      dictionary_class_set_features(dcl, j, matrix_clone(element_get_matrix(cell_get(wcl, j, 0))));
    }
    dictionary_set(d, i, dcl);
  }
  return d;
}

double KD_train_auc(cell *pairs, mlp *net) {
  unsigned int i, I, sum_I;
  matrix *template, *candidate_belong, *candidate_not_belong;
  double DTW_positive, DTW_negative;

  // Celkova metricka vzdalenost (metrika definovana AUC)
  sum_I = 0;

  for (i = 0; i < cell_size_n(pairs); i++) {

    // Vzor
    template = element_get_matrix(cell_get(pairs, 0, i));
    template = KD_train_transform(template, net);
    // Stejne slovo jako vzor
    candidate_belong = element_get_matrix(cell_get(pairs, 1, i));
    candidate_belong = KD_train_transform(candidate_belong, net);
    // Jine slovo nez vzor.
    candidate_not_belong = element_get_matrix(cell_get(pairs, 2, i));
    candidate_not_belong = KD_train_transform(candidate_not_belong, net);

    // Vypocet dtw (TEMPLATE, CANDIDATE_BELONG) a dtw (TEMPLATE, CANDIDATE_NOT_BELONG)
    DTW_negative = dtw(template, candidate_belong);
    DTW_positive = dtw(template, candidate_not_belong);

    // Srovnavaci metrika.
    if (DTW_positive > DTW_negative) { // Pokud je vzdalenost stejnych slov od sebe vetsi nez ruznych.
      I = 1; // vzdalenost = 1
    } else {
      I = 0; // vzdalenost = 0
    }

    // Inkrementace celkove vzdalenosti.
    sum_I += I; //printf("+dtw = %f , -dtw = %f , I = %i\n", dtw_positive, dtw_negative, I);

    // Uvolneni matic alokovanych funkci KD_train_transform(...)
    matrix_free(template);
    matrix_free(candidate_belong);
    matrix_free(candidate_not_belong);

  }



  // % uspesnosti.
  return ((double) sum_I) / ((double) cell_size_n(pairs));

}


double KD_train_auc2(cell *pairs) {
  unsigned int i, I, sum_I;
  matrix *template, *candidate_belong, *candidate_not_belong;
  double DTW_positive, DTW_negative;

  // Celkova metricka vzdalenost (metrika definovana AUC)
  sum_I = 0;

  for (i = 0; i < cell_size_n(pairs); i++) {

    // Vzor
    template = element_get_matrix(cell_get(pairs, 0, i));
    // Stejne slovo jako vzor
    candidate_belong = element_get_matrix(cell_get(pairs, 1, i));
    // Jine slovo nez vzor.
    candidate_not_belong = element_get_matrix(cell_get(pairs, 2, i));

    // Vypocet dtw (TEMPLATE, CANDIDATE_BELONG) a dtw (TEMPLATE, CANDIDATE_NOT_BELONG)
    DTW_negative = dtw(template, candidate_belong);
    DTW_positive = dtw(template, candidate_not_belong);

    // Srovnavaci metrika.
    if (DTW_positive > DTW_negative) { // Pokud je vzdalenost stejnych slov od sebe vetsi nez ruznych.
      I = 1; // vzdalenost = 1
    } else {
      I = 0; // vzdalenost = 0
    }

    // Inkrementace celkove vzdalenosti.
    sum_I += I; //printf("+dtw = %f , -dtw = %f , I = %i\n", dtw_positive, dtw_negative, I);

  }



  // % uspesnosti.
  return ((double) sum_I) / ((double) cell_size_n(pairs));

}


double KD_train_energy(cell *pairs, mlp *net, double b, unsigned int start, unsigned int end) {
  unsigned int i;
  matrix *template, *candidate_belong, *candidate_not_belong;
  double DTW_positive, DTW_negative,  I, sum_I;

  // Celkova metricka vzdalenost (metrika definovana AUC)
  sum_I = 0;
  //printf("%i -> %i\n", start, end-1);

  for (i = start; i < end; i++) {
    //printf("%i, ", i);
    // Vzor
    template = element_get_matrix(cell_get(pairs, 0, i));
    template = KD_train_transform(template, net);
    // Stejne slovo jako vzor
    candidate_belong = element_get_matrix(cell_get(pairs, 1, i));
    candidate_belong = KD_train_transform(candidate_belong, net);
    // Jine slovo nez vzor.
    candidate_not_belong = element_get_matrix(cell_get(pairs, 2, i));
    candidate_not_belong = KD_train_transform(candidate_not_belong, net);

    // Vypocet dtw (TEMPLATE, CANDIDATE_BELONG) a dtw (TEMPLATE, CANDIDATE_NOT_BELONG)
    DTW_negative = dtw(template, candidate_belong);
    DTW_positive = dtw(template, candidate_not_belong);

    // Srovnavaci metrika.
    I = KD_train_lk2(DTW_negative, DTW_positive, b);

    // Inkrementace celkove vzdalenosti.
    sum_I += I; //printf("+dtw = %f , -dtw = %f , I = %i\n", dtw_positive, dtw_negative, I);

    // Uvolneni matic alokovanych funkci KD_train_transform(...)
    matrix_free(template);
    matrix_free(candidate_belong);
    matrix_free(candidate_not_belong);

  }
  //printf("sum_I = %f\n", sum_I);
  // % uspesnosti.
  return (sum_I);

}


double KD_train_energy_paralel (cell *pairs, mlp *net, double b)
{

  unsigned int i, from[NUM_PROC][1], to[NUM_PROC][1];
  double e[NUM_PROC], B[NUM_PROC], E;

  // Rozdeleni treningove mnoziny na NUM_PROC casti
  from[0][0] = 0;
  for (i = 0; i<NUM_PROC-1; i++) {
    to[i][0] = (unsigned int) floor(((double)cell_size_n(pairs))*(i+1)/NUM_PROC);
    from[i+1][0] = to[i][0];
  }
  to[i][0] = cell_size_n(pairs);

  mlp *clone_net[NUM_PROC];
  for (i = 0; i<NUM_PROC; i++) {
    clone_net[i] = mlp_clone(net);
    B[i] = b;
  }

  void ***arg = malloc(sizeof(void*)*NUM_PROC);
  void **subarg;

  for (i = 0; i<NUM_PROC; i++) {

    subarg = malloc(sizeof(void*)*6);
    subarg[0] = pairs;
    subarg[1] = clone_net[i];
    subarg[2] = &from[i];
    subarg[3] = &to[i];
    subarg[4] = &e[i];
    subarg[5] = &B[i];

    arg[i] = subarg;

  }

  pthread_t thread[NUM_PROC];

  for (i = 0; i<NUM_PROC; i++) {

    pthread_create( &thread[i], NULL, KD_train_energy_thread, (void *) arg[i]);

  }

  /* Wait for the threads to finish */
  for (i = 0; i<NUM_PROC; i++) {

    pthread_join(thread[i], NULL);

  }

  //printf("e1 = %f , e1 = %f , sum e = %f\n", e1[0], e2[0], e1[0]+e2[0]);
  E = 0;
  for (i = 0; i<NUM_PROC; i++) {

    E += e[i];
    mlp_free(clone_net[i]);
    free(arg[i]);

  }

  free(arg);

  return E;

}


void *KD_train_energy_thread(void *ptr)
{
  void *out;
  void **arg = (void**) ptr;
  cell *pairs = (cell *) arg[0];
  mlp *net = (mlp *) arg[1];
  unsigned int from = * ((unsigned int *) arg[2]);
  unsigned int to = * ((unsigned int *) arg[3]);
  unsigned int i;
  matrix *word, *same, *different;
  double d_ws, d_wd, *e, b;
  e = (double *) arg[4];
  b = * ((double *) arg[5]);

  e[0] = KD_train_energy(pairs, net, b, from, to);

  return NULL;

}
