#include "neuron.h"

#include <cmath>
#include <cstdlib>
#include <iostream>


using namespace std;

double Neuron::eta = 0.0015;
double Neuron::alpha = 0.5;


Neuron::Neuron(int number_of_outputs, int index)
:index(index){
	//create outputs
	for(int i=0;i<number_of_outputs;i++){
		output_weights.push_back(rand() / double(RAND_MAX));
		delta_weights.push_back(rand() / double(RAND_MAX));
	}
}

void Neuron::set_output_value(double value){
	output_value = value;
}

double Neuron::get_output_value(void) const{
	return output_value;
}

void Neuron::feed_forward(const Layer &previous_layer){
	double sum = 0.0;
	// Sum up all the outputs of the previous layer and add the bias node.
	for (unsigned i=0; i< previous_layer.size(); i++) {
		sum += previous_layer[i]->get_output_value() *
				previous_layer[i]->output_weights[index];
	}

	output_value = transfer_function(sum);
}

double Neuron::transfer_function(double in){
	return tanh(in);
//	return exp(-in);
//	return in;
}

double Neuron::transfer_function_derivative(double in){
	return 1.0 - (tanh(in)*tanh(in));
//	return -exp(in);
//	return in*100;
}

void Neuron::calculate_output_gradients(double expected_value){
	double delta = expected_value - output_value;
	gradient = delta * transfer_function_derivative(output_value);
}

void Neuron::calculate_hidden_gradients(const Layer &next_layer){
	double sum = 0.0;
	for (unsigned i = 0; i < next_layer.size() - 1; i++) {
		sum += output_weights[i] * next_layer[i]->gradient;
	}
	gradient = sum * transfer_function_derivative(output_value);
}


void Neuron::update_input_weights(Layer &previous_layer){
	// The weights to be updated are in the Connection container
	// in the neurons in the preceding layer

	for(unsigned i=0;i<previous_layer.size();i++) {
//		Neuron &neuron = previous_layer[i];
		double old_delta_weight = previous_layer[i]->delta_weights[index];

		double new_delta_weight =
				// Individual input, magnified by the gradient and train rate:
				eta
				* previous_layer[i]->get_output_value()
				* gradient
				// Also add momentum = a fraction of the previous delta weight;
				+ alpha
				* old_delta_weight;

		previous_layer[i]->delta_weights[index] = new_delta_weight;
		previous_layer[i]->output_weights[index] += new_delta_weight;
	}
}
