/*
    ANTLand simulation
    (C) 2004 justin_sane

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    Contact: justinsanemsu@msu.edu

    FILE: network.cpp
    Authors: justin_sane
*/

#pragma hdrstop

#include <math.h>
#include <stdio.h>

#include "network.h"
#include "utility.h"
#include "ant.h"
#include "landsimmain.h"
#include "drawnetunit.h"

#pragma package(smart_init)


/* computes the nest vales for the neural networld */
void ant_class::process_network( void )
{
    // grab the sensor data, and put in into the network
    for (unsigned int i = 0; i < net.inputs.size(); i++)
        net.inputs[i].value = *net.inputs[i].source;

    // process the neurons
    for (unsigned int i = 0; i < net.nodes.size(); i++)
    {
        // set the bias
        float sum = net.nodes[i].bias + rval(0.001);

        // calculate the weighted sum of the inputs
        for (unsigned int t = 0; t < net.nodes[i].sources.size(); t++)
                sum += *net.nodes[i].sources[t] * net.nodes[i].weights[t];

        // apply the transfew function
        net.nodes[i].next_value = tanh(sum);
    }

    // propigate the values
    for (unsigned int i = 0; i < net.nodes.size(); i++)
        net.nodes[i].value = net.nodes[i].next_value;

    // latch the outputs
    // only latch is under self control
    if (self_control)
    for (unsigned int i = 0; i < net.outputs.size(); i++)
        *net.outputs[i].target = *net.outputs[i].source;
}
/* enf of process network */


/* create a new network for the ant */
void ant_class::setup_network( void )
{
    // add the inputs
    for (int i = 0; i < 6; i++)
    {
        input_class tmp;
        tmp.value = 0;

        tmp.source = f_warning[i].value_ptr();      // link to the input
        net.inputs.push_back(tmp);       // add to network

        tmp.source = f_pain[i].value_ptr();         // link to the input
        net.inputs.push_back(tmp);       // add to network

        tmp.source = r_warning[i].value_ptr();      // link to the input
        net.inputs.push_back(tmp);       // add to network

        tmp.source = r_pain[i].value_ptr();         // link to the input
        net.inputs.push_back(tmp);       // add to network
    }


    // make sure we have memory for all of the neurons
    // if the vector had to reallocate, all of the pointers would break

    node_class dummy;
    dummy.value = 0;
    net.nodes.resize(net_width * (net_layers) + 2, dummy);  // annding two for the teo output interfaces

    // create the first neuron layer
    for (unsigned int i = 0; i < net_width; i++)
    {
        net.nodes[i].value = 0;     // clear values
        net.nodes[i].next_value = 0;

        net.nodes[i].bias_range = 0.2; // random bias
        net.nodes[i].weight_range = 0.5; // random bias

        net.nodes[i].bias = rval(net.nodes[i].bias_range); // random bias

        // link each node to every input
        for (unsigned int t = 0; t < net.inputs.size(); t++)
        {
            net.nodes[i].sources.push_back(&net.inputs[t].value);
            // the pain sensors are always negative!

            if (i % 2) // odd (link points to a pain sensor
                net.nodes[i].weights.push_back(-rvalplus(0.25));
            else
                net.nodes[i].weights.push_back(-rvalplus(1));
        }

        // link back to self
        net.nodes[i].sources.push_back(&net.nodes[i].value);
        net.nodes[i].weights.push_back(rval(0.5));
    }

    // create more neuron layers
    for (int l = 0; l < net_layers - 1; l++)
    {
        for (unsigned int i = net_width * (l+1); i < net_width * (l+2); i++)
        {
            net.nodes[i].value = 0;

            net.nodes[i].bias_range = 0.2; // random bias
            net.nodes[i].weight_range = 0.5; // random bias

            net.nodes[i].bias = rval(net.nodes[i].bias_range); // random bias

            // link each node to every input
            for (unsigned int t = net_width * l; t < net_width * (l+1); t++)
            {
                net.nodes[i].sources.push_back(&net.nodes[t].value);
                net.nodes[i].weights.push_back(rval(0.5));
            }

            // link back to self
            net.nodes[i].sources.push_back(&net.nodes[i].value);
            net.nodes[i].weights.push_back(net.nodes[i].weight_range);
        }
    }

    // create the output interfaces
    for (unsigned int i = net_width * net_layers; i < net_width * net_layers + 2; i++)
    {
        net.nodes[i].value = 0;

        net.nodes[i].bias_range = 0.2; // random bias
        net.nodes[i].weight_range = 0.5; // random bias

        net.nodes[i].bias = rval(net.nodes[i].bias_range); // random bias


        // link each node to every input
        for (unsigned int t = net_width * (net_layers-1); t < net_width * (net_layers); t++)
        {
            net.nodes[i].sources.push_back(&net.nodes[t].value);
            net.nodes[i].weights.push_back(rval(net.nodes[i].weight_range));
        }
    }

    // create the output layer
    output_class tmp;

    tmp.source = &net.nodes[net_width * (net_layers)].value;
    tmp.target = &forward;
    net.outputs.push_back(tmp);

    tmp.source = &net.nodes[net_width * (net_layers) + 1].value;
    tmp.target = &turn;
    net.outputs.push_back(tmp);

    // feed the outputs back into the first layer
    // link each node to every input
    for (unsigned int t = 0; t < net_width; t++)
    {
          net.nodes[t].sources.push_back(net.outputs[0].source);
          net.nodes[t].weights.push_back(rval(0.1));

          net.nodes[t].sources.push_back(net.outputs[1].source);
          net.nodes[t].weights.push_back(rval(0.1));
    }
}
/* end of create network */


/* used to apply learning to the network */
void __fastcall ant_class::mutate_node( void )
{
//    if (!self_control) return;

    mutate_count++;

    // choose a random node
    int n = rand() % net.nodes.size();

    // one in 3 change of messing with a bias
    if (!(rand() % 3))
        net.nodes[n].bias = rval(net.nodes[n].bias_range);

    if (!net.nodes[n].weights.size()) return;

    int w = rand() % net.nodes[n].weights.size();

    // chec for pain links, they are always negative
    net.nodes[n].weights[w] = rval(net.nodes[n].weight_range);

}
/* end of mutatte node */




TColor get_color(float v)
{
    unsigned bright = fabs(v) * 1000 + 100;

    TColor c;

    if (bright > 255) bright = 255;

    if (v > 0)
        c = TColor(RGB(0, 0, bright));
    else
        c = TColor(RGB(bright, 0, 0));

    return(c);
}

/* draw the neural network display */
void __fastcall network_class::draw( void )
{
    float node_x_size = netdrawform->drawarea->Width / (float)net_width;
    float node_y_size = netdrawform->drawarea->Height / (float)(net_layers + 1);

    // create the off screen buffer
    Graphics::TBitmap *buffer = new Graphics::TBitmap;

    // set to same size as the drawing area
    buffer->Width  = netdrawform->drawarea->Width;
    buffer->Height = netdrawform->drawarea->Height;

    // clear the buffer
    buffer->Canvas->Brush->Color = clWhite;
    buffer->Canvas->FillRect(netdrawform->drawarea->ClientRect);

    // inital start position
    float x = node_x_size / 2.0,
          y = node_y_size / 2.0;

    netdrawform->Canvas->Pen->Width = 0;

    // draw all of the nodes
    for (unsigned int i = 0; i < nodes.size(); i++)
    {
        TColor c = get_color(nodes[i].value);

        buffer->Canvas->Brush->Color = c;
        buffer->Canvas->Pen->Color = c;

        // draw the neuron
        buffer->Canvas->Rectangle(TRect(x - node_x_size / 2, y - node_y_size / 2,
                                             x + node_x_size / 2, y + node_y_size / 2));

        x += node_x_size; // move to the next x position

        // move to next row?
        if (!((i+1) % net_width) && i > 0)
        {
            y += node_y_size;
            x = node_x_size / 2.0;
        }
    }

    // copy the buffer to the display
    netdrawform->drawarea->Canvas->Draw(0,0,buffer);

    // free the buffer
    delete buffer;
}
/* end of draw network */




