/*  TA3D, a remake of Total Annihilation
    Copyright (C) 2005  Roland BROCHARD

    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA*/

/*-----------------------------------------------------------------------------\
|                                      ia.h                                    |
|       Ce module est responsable de l'intelligence artificielle               |
|                                                                              |
\-----------------------------------------------------------------------------*/

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

typedef unsigned char byte;

struct NEURON
{
	float	var;			// Variable pour les opérations
	float	*weight;		// Poids des différents neurones sources
};

class BRAIN		// Réseau de NEURON à n entrées et p sorties
{
public:
	int		nb_neuron;		// Nombre de NEURON
	NEURON	*neuron;		// Tableau de NEURON
	int		n;				// Nombre d'entrées dans le réseau
	int		p;				// Nombre de sorties du réseau
	int		q;				// Taille des rangs
	float	*n_out;			// Tableau de sortie

	inline void init()
	{
		nb_neuron=0;
		neuron=NULL;
		n=p=q=0;
		n_out=NULL;
	}

	inline void destroy()
	{
		if(nb_neuron>0 && neuron) {
			for(int i=0;i<nb_neuron;i++)
				if(neuron[i].weight!=NULL)
					free(neuron[i].weight);
			free(neuron);
			}
		if(n_out)	free(n_out);
		init();
	}

	BRAIN()
	{
		init();
	}

	~BRAIN()
	{
		destroy();
	}

	void build(int nb,int nb_in,int nb_out,int rg)				// Crée le réseau de neurones
	{
		destroy();
		if(nb<=0)	return;
		q=rg>nb_in ? rg : nb_in;
		nb_neuron=nb*q+nb_in+nb_out;		// Nombre de couches x nombre d'entrées + nombre de sorties
		n=nb_in;
		p=nb_out;
		neuron=(NEURON*) malloc(sizeof(NEURON)*nb_neuron);
		n_out=(float*) malloc(sizeof(float)*nb_out);
		for(int i=0;i<nb_neuron;i++) {
			neuron[i].var=0.0f;
			neuron[i].weight=NULL;
			if(i<nb_out)
				n_out[i]=0.0f;
			if(i>=n) {
				neuron[i].weight=(float*) malloc(sizeof(float)*q);
				for(int e=0;e<q;e++)
					neuron[i].weight[e]=0.0f;
				}
			}
	}

	inline void active_neuron(int i)
	{
		if(neuron[i].weight==NULL)	return;
		neuron[i].var=0.0f;
		int k=((i-n)/q)*q;
		if(i<n && n!=q)	k=0;
		int l= (k==0) ? n : q;
		for(int e=0;e<l;e++)
			neuron[i].var+=neuron[k+e].var*neuron[i].weight[e];
		neuron[i].var=1.0f/(1.0f+exp(-neuron[i].var));
	}

	float *work(float entry[],bool seuil=false)			// Fait bosser un peu le réseau de NEURON et renvoie les valeurs calculées par un NEURON
	{
		if(nb_neuron<0)	return NULL;		// Pas de NEURON à faire bosser
		int i;
		for(i=0;i<n;i++)		// Prépare le réseau au calcul
			neuron[i].var=entry[i];
		for(i=n;i<nb_neuron;i++)
			active_neuron(i);
		if(!seuil)
			for(i=0;i<p;i++)		// Récupère le résultat du calcul
				n_out[i]=neuron[nb_neuron-p+i-1].var;
		else
			for(i=0;i<p;i++)		// Récupère le résultat du calcul
				n_out[i]=neuron[nb_neuron-p+i-1].var>=0.5f ? 1.0f : 0.0f;
		return n_out;
	}

	void mutation()			// Déclenche une mutation dans le réseau de neurones
	{
		int index=(rand()%(nb_neuron-n))+n;
		int mod_w=rand()%q;
		neuron[index].weight[mod_w]+=((rand()%200001)-100000)*0.00001f;
	}

	void learn(float *result,float coef=1.0f)		// Corrige les défauts
	{
		for(int i=0;i<p;i++)
			n_out[i]=(result[i]-n_out[i])*n_out[i]*(1.0f-n_out[i]);

		float diff[q];
		float err[q];
		for(int i=0;i<q;i++)
			diff[i]=err[i]=0.0f;

		for(int i=0;i<p;i++)		// Neurones de sortie
			for(int e=0;e<q;e++) {
				diff[e]+=n_out[i]*neuron[nb_neuron-p+i-1].weight[e]*neuron[nb_neuron-p-q-1+e].var*(1.0f-neuron[nb_neuron-p-q-1+e].var);
				neuron[nb_neuron-p+i-1].weight[e]+=coef*n_out[i]*neuron[nb_neuron-p-q-1+e].var;
				}

									// Neurones des couches intermédiaires
		for(int k=1;k<(nb_neuron-p-n)/q;k++) {
			for(int i=0;i<q;i++) {
				err[i]=diff[i];
				diff[i]=0.0f;
				}
			int d=0;
			if(nb_neuron-p-k*q-1<0)
				d=1+k*q+p-nb_neuron;
			for(int i=0;i<q;i++)
				for(int e=d;e<q;e++) {
					diff[e-d]+=err[i]*neuron[nb_neuron-p-k*q+i-1].weight[e-d]*neuron[nb_neuron-p-(k+1)*q-1+e].var*(1.0f-neuron[nb_neuron-p-(k+1)*q-1+e].var);
					neuron[nb_neuron-p-k*q+i-1].weight[e-d]+=coef*err[i]*neuron[nb_neuron-p-(k+1)*q-1+e].var;
					}
			}
	}
};

BRAIN *copy_brain(BRAIN *brain,BRAIN *dst=NULL)			// Copie un réseau de neurones
{
	BRAIN *copy=dst;
	if(copy==NULL)
		copy=(BRAIN*) malloc(sizeof(BRAIN));
	copy->init();
	copy->nb_neuron=brain->nb_neuron;
	copy->n=brain->n;
	copy->p=brain->p;
	copy->q=brain->q;
	copy->neuron=(NEURON*) malloc(sizeof(NEURON)*copy->nb_neuron);
	copy->n_out=(float*) malloc(sizeof(float)*copy->p);
	for(int i=0;i<brain->nb_neuron;i++) {
		if(i<brain->p)
			copy->n_out[i]=0.0f;
		if(brain->neuron[i].weight==NULL)
			copy->neuron[i].weight=NULL;
		else {
			copy->neuron[i].weight=(float*) malloc(sizeof(float)*brain->q);
			for(int e=0;e<brain->q;e++)
				copy->neuron[i].weight[e]=brain->neuron[i].weight[e];
			}
		}
	return copy;
}
