#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
typedef unsigned char BYTE;
using namespace std;

const int DATAPACKAGE = 64;
const int NUMBYTESOFDATAPAIR = 4;
const int NUMBITSOFNEURON = 16;
bool reachOverflowCheckpoint = false;
int overflowCounter = 0;


vector<BYTE> readFile(ifstream &file)
{
	std::vector<BYTE> data(DATAPACKAGE);
	file.read((char*) &data[0], DATAPACKAGE);
	if(file.eof())
	{
		return {1};
	}
	if(file.fail())
	{
		cerr << "ERROR: Can not read from File.\n";
		exit(1);
	}
	return data;
}

void sortData(vector<BYTE> data, int numBytesOfDataPair, int numBitsOfNeuron, map<int, vector<int>> &store, int numNodes, int nodeId)
{
	
	
	int overflowBorder = pow(2, numBytesOfDataPair*8-numBitsOfNeuron)-1;
	
	vector<short> bits(numBytesOfDataPair * 8);
	for(int i=0; i<DATAPACKAGE/numBytesOfDataPair; ++i)
	{
		for(int j=0; j<numBytesOfDataPair; ++j)
		{
			int dat = data[i*numBytesOfDataPair+j];
			for(int bitCnt=0; bitCnt<8; ++bitCnt)
			{
				bits[bitCnt+j*8] = dat >> bitCnt & 1;
			}
		}
		int timeStep = 0;
		int neuron =0;
		for(int j = 0; j<numBitsOfNeuron; ++j)
		{
			neuron += (bits[j] << j);
		}
		for(int j = numBitsOfNeuron; j<numBytesOfDataPair*8; ++j)
		{
			timeStep += (bits[j] << (j-numBitsOfNeuron));
		}
		
		if(reachOverflowCheckpoint == 0 && timeStep > overflowBorder*0.7){
			reachOverflowCheckpoint = true;
			
		}
		if(reachOverflowCheckpoint && timeStep < overflowBorder*0.5)
		{
			++overflowCounter;
			reachOverflowCheckpoint = false;
		}
		//cout << "Numnodes: " << numNodes << ", nodeid: " << nodeId << "\n";
		//cout << "Neuron: " << neuron << ", time: " << timeStep << "\n";
		timeStep = timeStep+(overflowBorder*overflowCounter);
		neuron = neuron * numNodes + nodeId;
		
		//cout << "NeuronId: " << neuron << ", Time: " << timeStep << "\n";
		
		
		if(store.count(timeStep) == 0)
		{
			store[timeStep] = {};
		}
		store[timeStep].push_back(neuron);
	}
}

void writeData(ofstream &file, map<int, vector<int>> &data)
{
	
	for(auto it=data.begin(); it!=data.end(); ++it)
	{
		sort(it->second.begin(), it->second.end());
		for(auto neuron=it->second.begin(); neuron!=it->second.end(); ++neuron)
		{
			file.write(reinterpret_cast<const char *>(&(*neuron)), 4);
			file.write(reinterpret_cast<const char *>(&it->first), 4);
		}
	}
}

/*
int merge(char* argv)
{
	
		//Spit argv to get filenames
		const char delimeter = ';';
		std::vector<char*> files;
		char* char_array = strtok(argv, &delimeter);
		
		while(char_array)
		{
			files.push_back(char_array);
			char_array = strtok(NULL, &delimeter);
		}
		
		
		map<int, vector<int>> orderedData;
	
	for(int i=0; i<files.size(); ++i)
	{
		ifstream file(files[i], ios::in|ios::binary);
		do
		{
			vector<BYTE> data = readFile(file);
			if(data.size() <= 1)
				break;
			sortData(data, NUMBYTESOFDATAPAIR, NUMBITSOFNEURON, orderedData, files.size(), i);
		} while(true);
	
		file.close();
	}
	
	ofstream output("read_spikes_node.bin", ios::out|ios::binary);
	writeData(output, orderedData);
	return 0;
}
*/

int main(int argc, char* argv[])
{
	
	if(argc < 4)
	{
		cout << "merger.exe <file_names> -o <output_directory>";
		//cerr << "ERROR: No filename was commited.\n";
		return 1;
	}
	
	string outputDir = "./spikes.bin";
	
	map<int, vector<int>> orderedData;
	
	for(int i=1; i<argc; ++i)
	{
		if(string(argv[i])=="-o")
		{
			if(i+1 >= argc)
			{
				break;
			}
			outputDir=string(argv[++i]).append("/spikes.bin");
			break;
		}
		cout << argv[i] << "\n";
		ifstream file(argv[i], ios::in|ios::binary);
		if(file.fail())
		{
			cerr << "FileError: Can't open file.";
			return 1;
		}
		do
		{
			vector<BYTE> data = readFile(file);
			if(data.size() <= 1)
				break;
			sortData(data, 4, 16, orderedData, argc-3, i-1);
		} while(true);
	
		file.close();
		reachOverflowCheckpoint = false;
		overflowCounter = 0;
	}
	
	ofstream output(outputDir, ios::trunc|ios::binary);
	if(output.fail())
	{
		cerr << "FileError: Can't open output File" << endl;
		return 1;
	}
	writeData(output, orderedData);
	output.close();
	return 0;
}
