#include <iostream>
#include <fstream>
#include <streambuf>
#include <cstdio>
#include <cmath>
#include <string>
#include <armadillo>
#include <jsoncpp/json/json.h>
#include "Rbm.hpp"
#include "Layer.hpp"
#include "Stack.hpp"

using namespace std;
class RbmListener : public Rbm::IListener
{
	public:
	RbmListener() {}
	virtual ~RbmListener() {}
		
	bool onProgress(const Rbm::Status &status)
	{
		std::cout << "Progress :          " << status.progress << " %" << std::endl;
		std::cout << "error (per mini batch) = " << status.err << std::endl;
		std::cout << "error (total)          = " << status.err_total << std::endl;
		std::cout << "L1                     = " << status.L1 << std::endl;
		std::cout << "L2                     = " << status.L2 << std::endl;
		
		return true;
	}
};

arma::mat loadTraining(const string &filename)
{
	uint32_t numTraining = 0;
	uint32_t numVisible = 0;
	FILE *pFile;

	pFile = fopen(filename.c_str(), "r");

	if (!pFile)
	{
		std::cout << "Could not open " << filename << "!" << std::endl;
		return 0;
	}

	int result = fscanf(pFile, "%d\n", &numTraining);
	if (result < 0)
	{
		return 0;
	}
	result = fscanf(pFile, "%d\n", &numVisible);
	if (result < 0)
	{
		return 0;
	}
	arma::mat data = arma::zeros(numTraining, numVisible);

	uint32_t i, j;
	for (i=0; i < numTraining; i++)
	{
		for (j=0; j < numVisible; j++)
		{
			float v;
			int result = fscanf(pFile, "%f", &v);
			if (result > 0)
			{
				data(i, j) = v;
			}
		}
	}
	fclose(pFile);
	return data;
	
}

int main()
{
	printf("Hallo, Welt!\n");

	const string project("test");
	
	RbmListener statusDisplay;
	Stack stack(project);

	stack.loadTraining();

	printf("Loaded %d training samples\n", (int)stack.trainingData().n_rows);

	stack.addTraining(stack.trainingData().row(1));
	printf("Loaded %d training samples\n", (int)stack.trainingData().n_rows);
	
	stack.delTraining(0);
	printf("Loaded %d training samples\n", (int)stack.trainingData().n_rows);

#if 1
	const int numLayers = 4;
	int i = 0;
	Layer *lowerLayer = new Layer("Layer", i, 16, 16, 8);
	stack.addLayer(lowerLayer);
	
	for (++i; i < numLayers; i++)
	{
		Layer *layer = new Layer("Layer", i, lowerLayer->bh().n_elem, 1, lowerLayer->bh().n_elem >> 1);
		layer->params().learningRate = lowerLayer->params().learningRate/2;
		layer->params().numEpochs = lowerLayer->params().numEpochs/2;
		lowerLayer = layer;
		stack.addLayer(layer);
	}

	// Save project
	stack.save();

	// Shake weights
	stack.weightsInit(0.01);

	// Save weights
	stack.saveWeights();

#else	
	// Load project
	stack.load();
	
	// Load weights
	stack.loadWeights();
#endif	
	
	// Train stack
	stack.train(&statusDisplay);
	
	// Save weights
	stack.saveWeights();

	Layer *layer = stack.getLayer(0);
	arma::mat v = arma::randu(stack.trainingData().n_rows, layer->bv().n_elem);
	arma::mat h = layer->toHiddenProbs(v);
	arma::mat r = layer->toVisibleProbs(h);
	return 0;
}
