// - - - - - - - - - - - - - - - - - -
// File: creature.cpp                 |
// Purpose: implements class Creature |
// Author: Taivo Lints, Estonia       |
// Date: May, 2003                    |
// Copyright: see copyright.txt       |
// - - - - - - - - - - - - - - - - - -

#include "creature.h"
#include "ann/neuralnetwork.h"
#include "allegro.h"
using namespace std;

// - - - - - Class Creature - - - - -

// ******************************
// * Construction & Destruction *
// ******************************

// - Creature constructor - -
// Needs a pointer to the bitmap this creature will "live" on.
// Also needs the palette colors for inner and outer circles of sensors,
// start of light's color gradient on palette, and a configuration file
// for neural network.
Creature::Creature(BITMAP* inp_pArena, int c_of_icircle, int c_of_ocircle,
                     int s_of_gradient, char* conf_file) {

  pArena = inp_pArena;

  x = pArena->w / 2;
  y = pArena->h / 2;

  speed_x = 0;
  speed_y = 0;

  speedup_factor = 3;
  
  color_of_icircle = c_of_icircle;
  color_of_ocircle = c_of_ocircle;

  start_of_gradient = s_of_gradient;

  // Currently the number of inputs (sensors) MUST be 3:
  // lower left, upper and lower right sensor (in that order).
  v_dbl_inps.assign(3, 0);

  // Sets the relative positions of these three sensors.
  Position* pPos = new Position;
  pPos->x = -4;
  pPos->y = 3;
  vPositions.push_back(*pPos);
  delete pPos;

  pPos = new Position;
  pPos->x = 0;
  pPos->y = -5;
  vPositions.push_back(*pPos);
  delete pPos;

  pPos = new Position;
  pPos->x = 4;
  pPos->y = 3;
  vPositions.push_back(*pPos);
  delete pPos;
  
  // Outputs are: speed_left, speed_right, speed_up, speed_down (in this order).
  // Speed_x is calculated as speed_right - speed_left. Similar formula for y.
  v_dbl_outputs.assign(4, 0);

  // Creates network and checks its correctness.
  pNet = new NeuralNetwork(conf_file);

  if( (pNet->get_number_of_inputs() != 3) or 
      (pNet->get_number_of_outputs() != 4) )
    flag_incor_net = true;
  else
    flag_incor_net = false;

}

// - Creature copy-constructor - -
Creature::Creature(const Creature& rC) {

  // Copy of creature can currently live only on the same bitmap as original.
  pArena = rC.pArena;

  x = rC.x;
  y = rC.y;

  speed_x = rC.speed_x;
  speed_y = rC.speed_y;

  speedup_factor = rC.speedup_factor;
  
  color_of_icircle = rC.color_of_icircle;
  color_of_ocircle = rC.color_of_ocircle;

  start_of_gradient = rC.start_of_gradient;

  v_dbl_inps = rC.v_dbl_inps;

  vPositions = rC.vPositions;

  v_dbl_outputs = rC.v_dbl_outputs;

  // Must have its own network.
  pNet = new NeuralNetwork(NULL);
  *pNet = *(rC.pNet);  // Fortunately I wrote operator= overloading for
                       // NeuralNetwork class :) (otherwise it wouldn't
                       // work correctly).

  // It's safer to check the correctness again than just copy flag_incor_net.
  if( (pNet->get_number_of_inputs() != 3) or 
      (pNet->get_number_of_outputs() != 4) )
    flag_incor_net = true;
  else
    flag_incor_net = false;

}

// - Operator= overloading - -
Creature& Creature::operator=(const Creature& rC) {

  if(&rC != this) {   // Check for self-assignment.

    pArena = rC.pArena;

    x = rC.x;
    y = rC.y;

    speed_x = rC.speed_x;
    speed_y = rC.speed_y;

    speedup_factor = rC.speedup_factor;
    
    color_of_icircle = rC.color_of_icircle;
    color_of_ocircle = rC.color_of_ocircle;

    start_of_gradient = rC.start_of_gradient;

    v_dbl_inps = rC.v_dbl_inps;

    vPositions = rC.vPositions;

    v_dbl_outputs = rC.v_dbl_outputs;

    // Must have its own network.
    pNet = new NeuralNetwork(NULL);
    *pNet = *(rC.pNet);  // Fortunately I wrote operator= overloading for
                         // NeuralNetwork class :) (otherwise it wouldn't
                         // work correctly).

    // It's safer to check the correctness again than just copy flag_incor_net.
    if( (pNet->get_number_of_inputs() != 3) or 
        (pNet->get_number_of_outputs() != 4) )
      flag_incor_net = true;
    else
      flag_incor_net = false;

  }

  return *this;

}

// - Creature destructor - -
Creature::~Creature() {

  // Deletes network (to prevent memory leak).
  delete pNet;

}


// *************
// * Functions *
// *************

// - Function: update - -
// Updates creature position.
void Creature::update() {

  // Updates only when neural network is correct.
  if(not flag_incor_net) {

    int ix = static_cast<int>(x);
    int iy = static_cast<int>(y);

    // Reads and normalizes sensor information.
    for(int i = 0; i < 3; i++) {
      int light_intensity = getpixel(pArena,
               ix + vPositions[i].x, iy + vPositions[i].y) - start_of_gradient;

      if(light_intensity < 0)         
        light_intensity = 0;

      if(light_intensity > 64)         
        light_intensity = 64;

      v_dbl_inps[i] = static_cast<double>(light_intensity) / 64;
    }

    // Uses network.
    pNet->set_inputs(&v_dbl_inps);
    pNet->update();
    pNet->get_outputs(&v_dbl_outputs);


    // Updates speed and position.
    speed_x = (v_dbl_outputs[1] - v_dbl_outputs[0]) * speedup_factor;
    speed_y = (v_dbl_outputs[3] - v_dbl_outputs[2]) * speedup_factor;

    x += speed_x;
    y += speed_y;

     // Keep creature on screen
    if(x + vPositions[0].x < 0)
      x = 0 - vPositions[0].x;

    if(x + vPositions[2].x > pArena->w - 1)
      x = pArena->w - 1 - vPositions[2].x;

    if(y + vPositions[1].y < 0)
      y = 0 - vPositions[1].y;

    if(y + vPositions[0].y > pArena->h - 1)
      y = pArena->h - 1 - vPositions[0].y;  
  }

}

// - Function: draw - -
// Draws creature on the bitmap.
void Creature::draw() {

  int ix = static_cast<int>(x);
  int iy = static_cast<int>(y);

  circle(pArena, ix + vPositions[0].x, iy + vPositions[0].y, 1, color_of_icircle);
  circle(pArena, ix + vPositions[0].x, iy + vPositions[0].y, 2, color_of_ocircle);

  circle(pArena, ix + vPositions[1].x, iy + vPositions[1].y, 1, color_of_icircle);
  circle(pArena, ix + vPositions[1].x, iy + vPositions[1].y, 2, color_of_ocircle);

  circle(pArena, ix + vPositions[2].x, iy + vPositions[2].y, 1, color_of_icircle);
  circle(pArena, ix + vPositions[2].x, iy + vPositions[2].y, 2, color_of_ocircle);

}

// - Function: load_network - -
// Loads a new network into creature.
void Creature::load_network(char* config_file) {

  delete pNet;

  pNet = new NeuralNetwork(config_file);

  // Checks correctness.
  if( (pNet->get_number_of_inputs() != 3) or 
      (pNet->get_number_of_outputs() != 4) )
    flag_incor_net = true;
  else
    flag_incor_net = false;

}