//////////////////////////////////////////////////////////////////////////////
//
// File: neunet.cc
//
// Purpose: Implementation of NeuNet class.
//
// Authors:
//   txe  Travis Emmitt
//
// Modifications:
//   16-APR-1998  txe  Initial creation (skeleton)
//   17-APR-1998  txe  Started implementing
//   18-APR-1998  txe  Added LoadWeights(), SetHiddenLayers()
//   19-APR-1998  txe  Moved a lot to the Arch class
//   20-APR-1998  txe  Using arch->Copy(), Purifying...
//   21-APR-1998  txe  Added Load(), Save()
//   22-APR-1998  txe  Debugging, made Load() and Save() more robust
//   23-APR-1998  txe  Debugging...
//
//////////////////////////////////////////////////////////////////////////////

#include <fstream.h>
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include "nn_layer.h"
#include "nn_link.h"
#include "nn_arch.h"
#include "neunet.h"

//////////////////////////////////////////////////////////////////////////////

NeuNet::NeuNet (char *name, Arch *a, int num_inputs, int num_outputs)
      : Debug  (name) {

  if ((arch = new Arch ("NeuNet's arch")) == NULL) {
    ERR << name << " couldn't create this->arch; out of memory!\n";
    exit (-1);
  }

  ASSERT (a->num_layers >= 2);

  arch->Copy (a);
  arch->num_nodes[0]                  = num_inputs;
  arch->num_nodes[arch->num_layers-1] = num_outputs;
  arch->Print (1);

  ASSERT (arch->Valid());

  if ((layers = new Layer* [arch->num_layers + 1]) == NULL) {
    ERR << name << " couldn't create layers[]; out of memory!\n";
    ERR << "  (array of size " << arch->num_layers + 1 << ")\n";
    exit (-1);
  }

  layers[arch->num_layers] = NULL;

  for (int i = arch->num_layers - 1; i >= 0; i--) {
    char temp[NAME_LEN+1];
    sprintf (temp, "%d", i);
    int num_in = (i ? arch->num_nodes[i-1] : 0);

    if ((layers[i] =
	new Layer (temp, arch, arch->num_nodes[i], num_in, layers[i+1]))
	 == NULL) {
      ERR << name << " couldn't create layers[" << i << "]; out of memory!\n";
      exit (-1);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

NeuNet::~NeuNet () {
  ASSERT (arch->num_layers > 1);
  ASSERT (layers != NULL);
  
  DEBUG(DEL) << "Destroying " << name << "'s layers (there are "
	     << arch->num_layers << ")...\n";

  for (int i = 0; i < arch->num_layers; i++) {
    delete layers[i];
  }

  DEBUG(DEL) << "Destroying " << name << "'s layers[]\n";
  DELETE_ARRAY layers;

  DEBUG(DEL) << "Destroying " << name << "'s arch\n";
  delete arch;

  DEBUG(DEL) << "Destroying " << name << "\n";
}

//////////////////////////////////////////////////////////////////////////////

float NeuNet::GetOutputLevel (int output_node) {
  ASSERT (output_node >= 0);
  ASSERT (output_node < arch->num_nodes[arch->num_layers-1]);

  float level = layers[arch->num_layers-1]->nodes[output_node]->level;

  DEBUG(3) << "  output[" << output_node << "] = " << level << "\n";
  return level;
}

//////////////////////////////////////////////////////////////////////////////

int NeuNet::Load (char *filename) {
  ASSERT (filename != NULL);

  DEBUG(1) << name << " loading weights from '" << filename << "'...\n";

  float weight, adjust;
  FILE *fp;
  char line[LINE_LEN+1];
  int num;

  if ((fp = fopen (filename, "rt")) == NULL) {
    ERR << name << " couldn't read from weights file '" << filename << "'\n";
    return 0;
  }

  // Read in number of layers and compare to architecture for verification //

  SKIP_COMMENTS (fp, line);
  sscanf (line, "%d", &(num=0));

  if (num != arch->num_layers) {
    ERR << "Number of layers (" << num << ") disagrees with architecture ("
	<< arch->num_layers << "), aborting Load\n";
    fclose (fp);
    return 0;
  }

  // Loop through all but the last layer (which has no out_links) //
 
  for (int i = 0; i < arch->num_layers - 1; i++) {
    ASSERT (layers    != NULL);
    ASSERT (layers[i] != NULL);

    // Read and compare number of nodes in this layer //

    SKIP_COMMENTS (fp, line);
    sscanf (line, "%d", &(num=0));

    if (num != layers[i]->num_nodes) {
      ERR << "Number of nodes (" << num << ") disagrees with architecture ("
	  << layers[i]->num_nodes << "), aborting Load\n";
      fclose (fp);
      return 0;
    }

    // Loop through each node in the layer //

    for (int j = 0; j < layers[i]->num_nodes; j++) {
      ASSERT (layers[i]->nodes    != NULL);
      ASSERT (layers[i]->nodes[j] != NULL);

    // Read and compare number of out_links from this node //

      SKIP_COMMENTS (fp, line);
      sscanf (line, "%d", &(num=0));
      
      if (num != layers[i]->nodes[j]->num_out_links) {
	ERR << "Number of out_links (" << num
	    << ") disagrees with architecture ("
	    << layers[i]->nodes[j]->num_out_links << "), aborting Load\n";
	fclose (fp);
	return 0;
      }

      for (int k = 0; k < layers[i]->nodes[j]->num_out_links; k++) {
        ASSERT (layers[i]->nodes[j]->out_links    != NULL);
        ASSERT (layers[i]->nodes[j]->out_links[k] != NULL);

        SKIP_COMMENTS (fp, line);

        if (sscanf (line, "%f %f", &weight, &adjust) < 2) {
          ERR << "Invalid links format (want %f %f): " << line << "\n";
	  fclose (fp);
          return 0;
        }

	layers[i]->nodes[j]->out_links[k]->weight = weight;
	layers[i]->nodes[j]->out_links[k]->adjust = adjust;
      }
    }
  }

  fclose (fp);
  return 1;
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::Peek () {
  char line[MAX_LEN+1] = "";
  int layer, node;

  while (line[0] != 'Q') {
    cout << "\n###############################################################\n";
    cout << "\nPEEK: " << name << "'s current state:\n\n";
    Print ();
    
    cout << "\nChoose a layer and a node - I'll show you the node's links.\n";
    
    do {
      cout << "  Enter layer [0-" << (arch->num_layers - 1)
	   << "] or 'q' to quit: ";
      gets (line);
      if (line[0] == 'q') {
	return;
      }
    } while (!sscanf (line, "%d", &layer)
	     || layer < 0 || layer >= arch->num_layers);
    
    ASSERT (layers[layer]->nodes != NULL);
    
    while (strchr ("qQ\n", line[0]) == NULL) {
      cout << "  Enter node [0-" << (layers[layer]->num_nodes - 1)
	   << "] or 'q' to quit: ";
      gets (line);
      if (sscanf (line, "%d", &node)
	  && node >= 0 && node < layers[layer]->num_nodes) {
	cout << "\n";
	layers[layer]->nodes[node]->Print ();
      }
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::Print () {
  int i, j, max_nodes = 0;
  char buffer[20];

  cout << "            L A Y E R S\n"
       << "  Node   ";
  
  for (i = 0; i < arch->num_layers; i++) {
    cout << "  " << i << "   ";
    max_nodes = MAX (max_nodes, layers[i]->num_nodes);
  }
  cout << "\n  ----   ";
  for (i = 0; i < arch->num_layers; i++) {
    cout << "----- ";
  }
  
  for (i = 0; i < max_nodes; i++) {
    sprintf (buffer, "\n   %-2d    ", i);
    cout << buffer;
    
    for (j = 0; j < arch->num_layers; j++) {
      if (i < layers[j]->num_nodes) {
	sprintf (buffer, "%5.2f ", layers[j]->nodes[i]->level);
	cout << buffer;
      }
      else {
	cout << "      ";
      }
    }
  }
  cout << "\n";
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::Print (int level) {
  if (debug >= level) {
	Print ();
  }
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::RandomizeWeights () {
  DEBUG(3) << name << " setting link weights to random values\n";
  float range, weight;

  for (int i = 0; i < arch->num_layers; i++) {
    for (int j = 0; j < layers[i]->num_nodes; j++) {
      for (int k = 0; k < layers[i]->nodes[j]->num_out_links; k++) {
	ASSERT (layers[i]->nodes[j]->out_links[k] != NULL);
	range  = arch->max_weight - arch->min_weight;
	weight = F_random (range) + arch->min_weight;
	layers[i]->nodes[j]->out_links[k]->weight = weight;
	layers[i]->nodes[j]->out_links[k]->adjust = 0;
      }
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::Reinforce () {
  DEBUG(3) << "Reinforcing " << name << "...\n";

  for (int i = arch->num_layers - 1; i > 0; i--) {
    for (int j = 0; j < layers[i]->num_nodes; j++) {
      for (int k = 0; k < layers[i]->nodes[j]->num_in_links; k++) {
	layers[i]->nodes[j]->in_links[k]->Train ();
      }
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::Run () {
  DEBUG(2) << "Running " << name << "...\n";

  int i, j;

  for (i = 1; i < arch->num_layers; i++) {
    ASSERT (layers[i]          != NULL);
    ASSERT (layers[i]->nodes   != NULL);
    ASSERT (layers[i-1]->nodes != NULL);
    
    for (j = 0; j < layers[i]->num_nodes; j++) {
      ASSERT (layers[i]->nodes[j] != NULL);
      layers[i]->nodes[j]->ComputeLevel ();
    }
    
    for (j = 0; j < layers[i-1]->num_nodes; j++) {
      ASSERT (layers[i-1]->nodes[j] != NULL);
      layers[i-1]->nodes[j]->SetError (0);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

int NeuNet::Save (char *filename, float loss_rate) {
  ASSERT (filename != NULL);
  ASSERT (arch     != NULL);

  char line[LINE_LEN+1];

  DEBUG(1) << name << " saving weights to '" << filename << "'...\n";

  ofstream out;
  out.open (filename);
  if (!out) {
    ERR << name << " couldn't write to weights file '" << filename << "'\n";
    return 0;
  }

  out << "//////////////////////////////////////////////////////////////////\n"
      << "//\n"
      << "// " << filename << "    (loss rate was " << (int)loss_rate << "%)\n"
      << "//\n"
      << "// NeuNet state file, generated automatically.\n"
      << "//\n"
      << "//////////////////////////////////////////////////////////////////\n"
      << "\n\n// Number of layers (verification) //\n\n" << arch->num_layers;

  for (int i = 0; i < arch->num_layers - 1; i++) {
    ASSERT (layers    != NULL);
    ASSERT (layers[i] != NULL);

    out << "\n\n// Number of nodes in Layer " << i
	<< " //\n\n" << layers[i]->num_nodes;

    for (int j = 0; j < layers[i]->num_nodes; j++) {
      ASSERT (layers[i]->nodes    != NULL);
      ASSERT (layers[i]->nodes[j] != NULL);

      out << "\n\n// Number of links from Node " << i << "." << j
	  << " //\n\n" << layers[i]->nodes[j]->num_out_links
	  << "\n\n// Links (weight, adjustment, name) //\n";

      for (int k = 0; k < layers[i]->nodes[j]->num_out_links; k++) {
        ASSERT (layers[i]->nodes[j]->out_links    != NULL);
        ASSERT (layers[i]->nodes[j]->out_links[k] != NULL);

        sprintf (line, "%10.5f %10.5f\t%s",
		 layers[i]->nodes[j]->out_links[k]->weight,
		 layers[i]->nodes[j]->out_links[k]->adjust,
		 layers[i]->nodes[j]->out_links[k]->name);
	
        out << "\n" << line;
      }
    }
  }

  out << "\n\n// No links from layer " << arch->num_layers - 1
      << ", end of file //\n";

  out.close ();
  return 1;
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::SetInputLevel (int input_node, float level) {
  ASSERT (input_node >= 0 && input_node < arch->num_nodes[0]);
  DEBUG(3) << "  setting input[" << input_node << "] to " << level << "...\n";
  layers[0]->nodes[input_node]->SetLevel (level);
}

//////////////////////////////////////////////////////////////////////////////

void NeuNet::SetOutputGoal (int output_node, float goal) {
  ASSERT (output_node >= 0);
  ASSERT (output_node < arch->num_nodes[arch->num_layers-1]);

  float error;
  error = MIN (goal - layers[arch->num_layers-1]->nodes[output_node]->level,
	       arch->encouragement);

  DEBUG(3) << "output[" << output_node << "]'s goal is " << goal
	   << ", error = " << error << "\n";

  layers[arch->num_layers-1]->nodes[output_node]->SetError (error);
}

//////////////////////////////////////////////////////////////////////////////

