/*
  ==============================================================================

    Layer.hpp
    Created: 21 Sep 2014 1:55:15pm
    Author:  jens

  ==============================================================================
*/

#ifndef LAYER_HPP
#define LAYER_HPP
#include <stdint.h>
#include <iostream>
#include <Eigen/Dense>
#include "noise.h"
#include "Weights.hpp"

using namespace Eigen;

class Layer
{
public:
	Layer(uint32_t numUnits = 0, const RowVectorXd *pStatesInit = nullptr)
	: m_numUnits(numUnits)
	, m_probs(numUnits)
	, m_states(numUnits)
	{
		Noise_Init(&m_noise, 0x12345677);
		setNumUnits(numUnits);

		if (pStatesInit && (pStatesInit->size() == numUnits))
		{
			m_states = *pStatesInit;
		}
	}

	virtual ~Layer()
	{
		setNumUnits(0);
		Noise_Free(&m_noise);
	}

	void setNumUnits(uint32_t numUnits)
	{
		if (m_numUnits == numUnits)
		{
			return;
		}
		m_numUnits = numUnits;
		m_probs.resize(numUnits);
		m_states.resize(numUnits);

		probsInit(0);
		statesInit(0);
	}

	void probsInit(const double &value)
	{
		m_probs.fill(value);
	}

	void statesInit(const double &value)
	{
		m_states.fill(value);
	}

	void probsUpdateLogistic(Layer &layer, Weights &weights, double lambda, double sigma)
	{
		uint32_t i;
		double var = sigma*sigma;

		for (i=0; i < m_numUnits; i++)
		{
			m_probs(i) = lambda/var*accum(layer, weights, i);
		}
		logSigmoid(m_probs);
	}

	void probsUpdateGaussian(Layer &layer, Weights &weights, double lambda, double sigma)
	{
		uint32_t i;
		for (i=0; i < m_numUnits; i++)
		{
			m_probs(i) = lambda*accum(layer, weights, i);
		}
		gaussProb(m_probs, sigma);
	}

	void statesUpdateStochastic()
	{
		uint32_t i;
		double sample;

		for (i=0; i < m_numUnits; i++)
		{
			sample = Noise_Uniform(&m_noise);
			m_states(i) = (double)(sample <= m_probs(i));
		}
	}

	void sampleGaussian(Layer &layer, Weights &weights, double lambda, double sigma)
	{
		uint32_t i;

		for (i=0; i < m_numUnits; i++)
		{
			m_states(i) = sigma*Noise_Gaussian(&m_noise) + lambda*accum(layer, weights, i);
		}
	}

	RowVectorXd& probs()
	{
		return m_probs;
	}

	RowVectorXd& states()
	{
		return m_states;
	}

	uint32_t getNumUnits() const
	{
		return m_numUnits;
	}

private:
	noise_gen_t m_noise;

protected:
	uint32_t m_numUnits;
	RowVectorXd m_probs;
	RowVectorXd m_states;

	virtual double accum(Layer &layer, Weights &weights, uint32_t index) = 0;
	void logSigmoid(const RowVectorXd &x)
	{
		uint32_t i;

		for (i=0; i < m_numUnits; i++)
		{
			m_probs[i] = 1./(1 + exp(-(double)x[i]));
		}
	}

	void gaussProb(const RowVectorXd &mu, double sigma)
	{
		uint32_t i;
		double var = sigma*sigma;
		double k = 1.0/sqrt(2*3.14159265359*var);

		for (i=0; i < m_numUnits; i++)
		{
			double x2 = (1-(double)mu[i]);
			m_probs[i] = exp(-0.5*x2*x2/var);
		}
	}
};

#endif  // LAYER_HPP
