#include "mlp.h"

mlp * mlp_alloc(unsigned int layers, unsigned int * n) {
  if (layers > 0) {
    unsigned int i;
    mlp * net = malloc(sizeof (mlp));
    (* net).layers = layers;
    (* net).neurons = malloc(sizeof (unsigned int) *layers);
    for (i = 0; i < layers; i++) {
      (* net).neurons[i] = n[i];
    }
    // Alokuju pamet pro vahy.
    (* net).weights = mlp_weights_alloc(net);
    // Vytvorim v pameti misto pro hodnoty neuronu prvni az
    // predposledni vrstvy. Posledni vrstva se nealokuje, protoze jeji vysup se
    // bude ukladat primo do vytvoreneho pole.
    (* net).vals = malloc(sizeof (double *) * (layers - 2));
    for (i = 0; i < layers - 2; i++) {
      (* net).vals[i] = malloc(sizeof (double) * (n[i + 1] + 1));
      //printf ("vals[%i][%i] = 0\n", i, n[i + 1]);
      (* net).vals[i][n[i + 1]] = 1;
    }
    return net;
  } else {
    printf("%s:%s: Parametr L musi byt vetsi nebo rovno 2", (const char *) __FILE__, (const char *) __LINE__);
    return NULL;
  }
}


double * * * mlp_weights_alloc(mlp * net) {
  unsigned int i, j;
  double * * * weights;
  // Alokuju pamet pro vahy.
  weights = malloc(sizeof (double * *) * ((* net).layers - 1));
  // Alokuju vahy pro zbyle vrstvy, ve kterych se nechazeji neurony.
  for (i = 0; i < (* net).layers - 1; i++) {
    weights[i] = malloc(sizeof (double *) * ((* net).neurons[i] + 1));
    for (j = 0; j < (* net).neurons[i] + 1; j++) {
      //printf ("w[%i][%i] = 0..%i\n", i, j, (* net).neurons[i+1]);
      weights[i][j] = malloc(sizeof (double) * (* net).neurons[i + 1]);
    }
  }
  return weights;
}


void mlp_weights_free(double * * * weights, mlp * net) {
  unsigned int i, j;
  // Uvolnim váhy.
  for (i = 0; i < (* net).layers - 1; i++) {
    for (j = 0; j < (* net).neurons[i] + 1; j++) {
      free(weights[i][j]);
    }
    free(weights[i]);
  }
  free(weights);
}


void mlp_free(mlp * net) {
  int i;
  // Uvonim vahy.
  double * * * w = (*net).weights;
  mlp_weights_free(w, net);
  // Uvolnim hodnoty neuronu.
  for (i = 0; i < (*net).layers - 2; i++) {
    free((*net).vals[i]);
  }
  free((*net).vals);
  // Uvolnim pole poctu neuronu.
  free((*net).neurons);
}


mlp *mlp_clone(mlp *net) {
  unsigned int i, j, k;
  // Vytvorim identickou neuronovou sit
  unsigned int l = mlp_get_num_layers(net);
  unsigned int *layers = malloc(sizeof (unsigned int) *l);
  for (i = 0; i < l; i++) {
    layers[i] = net->neurons[i];
  }
  mlp *clone = mlp_alloc(l, layers);

  // Nastavim vahy podle NET
  for (i = 0; i < (* net).layers - 1; i++) {
    for (j = 0; j < (* net).neurons[i] + 1; j++) {
      for (k = 0; k < (* net).neurons[i + 1]; k++) {
        clone->weights[i][j][k] = net->weights[i][j][k];
      }
    }
  }

  // Vysledek
  return clone;
}


unsigned int mlp_get_num_inputs(mlp * net) {
  return (* net).neurons[0];
}


unsigned int mlp_get_num_outputs(mlp * net) {
  return (* net).neurons[(* net).layers - 1];
}

unsigned int mlp_get_neurons(mlp * net, unsigned int i) {
  return (* net).neurons[i];
}


unsigned int mlp_get_num_layers(mlp * net) {
  return (* net).layers;
}


void mlp_set_weight(mlp * net, unsigned int l, unsigned int i, unsigned int j, double value) {
  (* net).weights[l][i][j] = value;
}


double mlp_get_weight(mlp * net, unsigned int l, unsigned int i, unsigned int j) {
  return (* net).weights[l][i][j];
}


void mlp_zeros_weights(mlp * net, double * * * weights) {
  unsigned int i, j, k;
  for (i = 0; i < (* net).layers - 1; i++) {
    for (j = 0; j < (* net).neurons[i] + 1; j++) {
      for (k = 0; k < (* net).neurons[i + 1]; k++) {
        //printf ("%i %i %i\n", i, j, k);
        //mlp_set_weight(net, i, j, k, 0);
        weights[i][j][k] = 0;
      }
    }
  }
}

// Nahodne nastavi vahy.

void mlp_weight_init(mlp * net, double d) {
  unsigned int i, j, k;
  for (i = 0; i < (*net).layers - 1; i++) {
    for (j = 0; j < (*net).neurons[i] + 1; j++) {
      for (k = 0; k < (*net).neurons[i + 1]; k++) {
        (*net).weights[i][j][k] = 2 * d * (((double) rand() / ((double) RAND_MAX)) - 0.5);
      }
    }
  }
}


void mlp_run(mlp * net, double * x, double * y) {
  int i, j, k, * n, l;
  n = (* net).neurons;
  l = (* net).layers;
  double * * v;
  v = (* net).vals;

  // Vypoitam prvni skrytou vrstvu
  for (j = 0; j < n[1]; j++) {
    v[0][j] = 0;
    for (k = 0; k < n[0]; k++) {
      v[0][j] += x[k] * mlp_get_weight(net, 0, k, j);
    }
    v[0][j] += 1 * mlp_get_weight(net, 0, n[0], j);
    v[0][j] = mlp_sigmoid(v[0][j]);
  }

  // Vypocitam druhou az predposledni vrstvu.
  for (i = 1; i < l - 3; i++) {
    printf("l = %i\n", l);
    for (j = 0; j < n[i + 1]; j++) {
      v[i][j] = 0;
      for (k = 0; k < n[i] + 1; k++) {
        v[i][j] += v[i - 1][k] * mlp_get_weight(net, i, k, j);
      }
      v[i][j] = mlp_sigmoid(v[i][j]);
    }
  }
  // Vypocitam vystup z posledni vrstvy a ulozim ho do vektoru OUT.
  if (l > 2) {
    for (j = 0; j < n[l - 1]; j++) {
      y[j] = 0;
      for (k = 0; k < n[l - 2] + 1; k++) {
        y[j] += v[l - 3][k] * mlp_get_weight(net, l - 2, k, j);
      }
      y[j] += 1 * mlp_get_weight(net, l - 2, n[l - 2], j);
      y[j] = mlp_sigmoid(y[j]);
    }
  } else {
    for (j = 0; j < n[1]; j++) {
      y[j] = 0;
      for (k = 0; k < n[0] + 1; k++) {
        y[j] += x[k] * mlp_get_weight(net, 0, k, j);
      }
      y[j] += 1 * mlp_get_weight(net, 0, n[0], j);
      y[j] = mlp_sigmoid(y[j]);
    }
  }

}


// Vrati hodnotu logicke sigmoidy.

double mlp_sigmoid(double x) {
  return 1 / (1 + exp(-x));
}


void mlp_print_weight(mlp * net) {
  int i, j, k;
  for (i = 0; i < (* net).layers - 1; i++) {
    for (j = 0; j < (* net).neurons[i] + 1; j++) {
      for (k = 0; k < (* net).neurons[i + 1]; k++) {
        printf("from neuron %i to neuron %i in %i layer : w = %f\n", j, k, i, mlp_get_weight(net, i, j, k));
      }
    }
  }
}

unsigned long mlp_get_total_connections(mlp * net) {
  unsigned int i;
  unsigned long conn;
  conn = 0;
  for (i = 0; i < mlp_get_num_layers(net) - 1; i++) {
    conn += (mlp_get_neurons(net, i) + 1) * mlp_get_neurons(net, i + 1);
  }
  return conn;
}

int mlp_save(mlp * net, const char * file) {
  unsigned long n;
  int i, j, k, saved;
  FILE * output;
  char number[100];
  n = mlp_get_total_connections(net)*(10 + 1 + 10 + 1 + 21 + 1)+(mlp_get_num_layers(net) + 1)*(10 + 1);
  char * txt = malloc(sizeof (char) *n);
  // Ulozeni hlavicky site (pocet neuronu v jednotlivych vrstvach a pocet celkovych spojeni);
  sprintf(number, "%i", mlp_get_neurons(net, 0));
  strcpy(txt, (const char *) number);
  for (i = 1; i < mlp_get_num_layers(net); i++) {
    sprintf(number, " %i", mlp_get_neurons(net, i));
    strcat(txt, number);
  }
  strcat(txt, "\n");
  // Ulozeni synaptickych vah.
  for (i = 0; i < mlp_get_num_layers(net) - 1; i++) {
    for (j = 0; j < mlp_get_neurons(net, i) + 1; j++) {
      for (k = 0; k < mlp_get_neurons(net, i + 1); k++) {
        sprintf(number, "%i %i %i %f\n", i, j, k, mlp_get_weight(net, i, j, k));
        strcat(txt, number);
      }
    }
  }
  // ulozi sit a uvolni pomocne promene.
  output = fopen(file, "w");
  saved = fputs(txt, output);
  free(txt);
  fclose(output);
  return saved;
}

mlp * mlp_load(const char * file) {
  FILE * pf = fopen(file, "r");
  if (!pf) {
    printf("%s:%i: Nepodarilo se nahrat soubor \"%s\", protoze neexistuje.\n", __FILE__, __LINE__, file);
    return NULL;
  }

  long int n, N, nconn;
  unsigned int i, l, from, to;
  unsigned int * neurons;
  float w;
  char * str;
  char * pch;

  N = 0;
  while (getc(pf) != EOF) {
    N++;
  }
  str = malloc(sizeof (char) *(N));
  rewind(pf);
  // Nacte prvni radek.
  fgets(str, N, pf);
  // Zjisti pocet vrstve.
  n = 0;
  pch = strtok(str, " ");
  while (pch != NULL) {
    n++;
    pch = strtok(NULL, " ");
  }
  // Ulozi jednotlive vrstvy do pole neuronu.
  neurons = malloc(sizeof (unsigned int) *n);
  rewind(pf);
  fgets(str, N, pf);
  pch = strtok(str, " ");
  i = 0;
  while (pch != NULL) {
    sscanf(pch, "%i", &neurons[i++]);
    pch = strtok(NULL, " ");
  }
  // Alokuje sit na zaklade pole neuronu a poctu vrstev.
  mlp * net = mlp_alloc(n, neurons);
  nconn = mlp_get_total_connections(net);
  // Nacte ostatni radky ve kterych jsou ulozeny jednotliva spojeni.
  while (fgets(str, N, pf) != NULL) {
    pch = strtok(str, " ");
    sscanf(pch, "%i", &l);
    pch = strtok(NULL, " ");
    sscanf(pch, "%i", &from);
    pch = strtok(NULL, " ");
    sscanf(pch, "%i", &to);
    pch = strtok(NULL, " ");
    sscanf(pch, "%f", &w);
    // Ulozi dane spojeni.
    mlp_set_weight(net, l, from, to, w);
    //printf("w[%i][%i][%i] = %f\n", l, from, to, w);
  }
  // Uvlonim pomocne promene.
  free(neurons);
  free(str);
  fclose(pf);
  // Vrati ukazatel na neuronovou sit vytvorenou ze souboru.
  return net;

}