#include "ralans-propagation-loss-model.h"
#include "ns3/mobility-model.h"
#include "ns3/log.h"
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <iterator>
#include <string>
#include <math.h>
#include "ns3/string.h"

//Defines for Header numbers
#define POINT 0	
#define LINE 1	
#define AREA 2	
#define CUBIG 3
#define LIST 4	

//Defines for Header Lengths
#define LENPOINTHEAD 3
#define LENLINEHEAD 9
#define LENAREAHEAD 7
#define LENCUBIGHEAD 9

#define LENPOINT 3
#define THRESHOLD 400.0

//Defines for axis
#define XAXIS 0
#define YAXIS 1
#define ZAXIS 2


NS_LOG_COMPONENT_DEFINE ("RaLaNSPropagationLossModel");

namespace ns3 {

	/* ... */

	NS_OBJECT_ENSURE_REGISTERED (RaLaNSPropagationLossModel);

	RaLaNSPropagationLossModel::RaLaNSPropagationLossModel () : PropagationLossModel ()
	{
		//ns3::LogComponentEnable("RaLaNSPropagationLossModel",ns3::LOG_ERROR);
		//ns3::LogComponentEnable("RaLaNSPropagationLossModel",ns3::LOG_DEBUG);
	}

	RaLaNSPropagationLossModel::~RaLaNSPropagationLossModel ()
	{

	}

	TypeId RaLaNSPropagationLossModel::GetTypeId (void) {
		static TypeId tid = TypeId ("ns3::RaLaNSPropagationLossModel")
			.SetParent<PropagationLossModel> ()
			.AddConstructor<RaLaNSPropagationLossModel> ()
			.AddAttribute ( "MapFile",
					"Path to the file that contains the propagation information.",
					StringValue ("coverage/example.txt"),
					MakeStringAccessor (&RaLaNSPropagationLossModel::SetMap),
					MakeStringChecker ());
		return tid;
	}

	std::vector<double> sum (const std::vector<double> vec1, const std::vector<double> vec2) {

		std::vector<double> result(LENPOINT);	

		for(std::vector<double>::size_type i = 0; i < vec1.size(); i++){
			result[i] = vec1[i] + vec2[i];
		}

		return result;
	}

	double vecLen(const std::vector<double> vec){
		return sqrt(vec[XAXIS] * vec[XAXIS]+ vec[YAXIS] * vec[YAXIS] + vec[ZAXIS] * vec[ZAXIS]);
	}

	std::vector<double> cVecLen(const std::vector<double> vec, double newLength) {

		std::vector<double> result(LENPOINT);	

		double vecLength = vecLen(vec);

		for(int i = 0; i < LENPOINT; i++){
			result[i] = (vec[i] / vecLength ) * newLength;
		}

		return result;
	}

	std::vector<double> substract (const std::vector<double> vec1, const std::vector<double> vec2) {

		std::vector<double> result(LENPOINT);	

		for(std::vector<double>::size_type i = 0, size = vec1.size(); i < size; i++){
			result[i] = vec1[i] - vec2[i];
		}

		return result;
	}


	void RaLaNSPropagationLossModel::setPoint (std::vector<double> pointVec, std::vector< std::vector<double> > &header) {

		if(pointVec.size() == LENPOINTHEAD){
			this->yLen = 1;
			this->zLen = 1;

			header.push_back(pointVec);
		} else{
			NS_LOG_ERROR("The number of arguments for the point header is not 3: " << pointVec.size());
		}
	}

	void RaLaNSPropagationLossModel::setLine (std::vector<double> lineVec, std::vector< std::vector<double> > &header) {

		if(lineVec.size() == LENLINEHEAD){

			/*
			 * set the number of elements in x and y dimension, 
			 * they are 1, because a line Vector is written in 1 line in the file
			 */
			this->yLen = 1;
			this->zLen = 1;

			/*
			 * read the beginning vector for the line
			 */
			std::vector<double>::const_iterator first = lineVec.begin();
			std::vector<double>::const_iterator last = lineVec.begin() + LENPOINT;
			std::vector<double> beginLineVec(first, last);

			/*
			 * read the ending vector for the line
			 */
			first = lineVec.begin()+ LENPOINT;
			last = lineVec.begin()+ LENPOINT + LENPOINT;
			std::vector<double> endLineVec(first, last);

			/*
			 * read the stepSize for the changing of each point for the line,
			 * calculate the direction vector from the beginning of the line to the end
			 * calculate the length of that vector to compare it in the loop 
			 * set the length of the direction vector to stepSize
			 */
			double stepSize = lineVec[6];
			std::vector<double> vecFromBeginToEnd = substract(endLineVec, beginLineVec);
			double lenBeginToEnd = vecLen(vecFromBeginToEnd);
			std::vector<double> stepVec = cVecLen(vecFromBeginToEnd, stepSize);

			std::vector<double> newInterPointVec(beginLineVec);

			/*
			 * if the length of (the current Vector - the beginning Vector) > the total length from begin to end,
			 * then all the points are set.
			 */
			while(vecLen(substract(newInterPointVec, beginLineVec)) < lenBeginToEnd){

				header.push_back(newInterPointVec);
				newInterPointVec = sum(newInterPointVec, stepVec);
			}
		} else{

			NS_LOG_ERROR("The number of arguments for the line header is not LENLINEHEAD: " << lineVec.size());
		}
	}

	void RaLaNSPropagationLossModel::setArea (std::vector<double> areaVec, std::vector< std::vector<double> > &header) {

		if(areaVec.size() == LENAREAHEAD){

			/*
			 * read the beginning vector for the area
			 */
			std::vector<double> beginAreaVec(3);
			beginAreaVec[XAXIS] = areaVec[0];
			beginAreaVec[YAXIS] = areaVec[1];	
			beginAreaVec[ZAXIS] = areaVec[4];	

			/*
			 * read the ending vector for the area
			 */
			std::vector<double> endAreaVec(3);
			endAreaVec[XAXIS] = areaVec[2];
			endAreaVec[YAXIS] = areaVec[3];	
			endAreaVec[ZAXIS] = areaVec[4];	

			/*
			 * read the step size for the area
			 */
			double xStep = areaVec[5];
			double yStep = areaVec[6];


			/*
			 * calculate the number of elements in the x, y and z dimension
			 */
			yLen = ((int) ((endAreaVec[YAXIS] - beginAreaVec[YAXIS]) / yStep)) + 1;
			zLen = 1;
			int xLen = ( (int) ((endAreaVec[XAXIS] - beginAreaVec[XAXIS]) / xStep)) + 1; 
			std::vector<double> newInterPointVec(beginAreaVec);

			/*
			 * fill the header with all points of the area,
			 */
			for(int i = 0; i < yLen; i++){
				for(int j = 0; j < xLen; j++){
					header.push_back(newInterPointVec);
					newInterPointVec[XAXIS] += xStep;
				}
				newInterPointVec[YAXIS] += yStep;
				newInterPointVec[XAXIS] = beginAreaVec[XAXIS];
			}
		}

		else{
			NS_LOG_ERROR("The number of arguments for the area header is not LENAREAHEAD: " << areaVec.size());
		}	

	}

	void RaLaNSPropagationLossModel::setList (std::vector<double> listVec, std::vector< std::vector<double> > &header) {

		if((listVec.size() - 1) % 3 == 0){

			zLen = 1;	
			yLen = 1;	

			std::vector<double> pointVec(3);	

			/*
			 * fill header with all points of the list in the order of the list
			 */
			for(std::vector<double>::size_type i = 1; i < listVec.size(); i += 3){

				pointVec[XAXIS] = listVec[i + XAXIS];
				pointVec[YAXIS] = listVec[i + YAXIS];
				pointVec[ZAXIS] = listVec[i + ZAXIS];

				header.push_back(pointVec);
			}	

		} else{
			NS_LOG_ERROR("The number of arguments for the list header is not dividable by 3, (listVec.size() - 1) % 3 == 0 : " << listVec.size());
		}

		NS_LOG_DEBUG("Setted in List!!! Size: "<< header.size());

	}

	void RaLaNSPropagationLossModel::setCubic (std::vector<double> cubicVec, std::vector< std::vector<double> > &header) {

		if(cubicVec.size() == LENCUBIGHEAD){

			std::vector<double> beginCubicVec(3);
			beginCubicVec[XAXIS] = cubicVec[0];
			beginCubicVec[YAXIS] = cubicVec[1];	
			beginCubicVec[ZAXIS] = cubicVec[2];	

			std::vector<double> endCubicVec(3);
			endCubicVec[XAXIS] = cubicVec[3];
			endCubicVec[YAXIS] = cubicVec[4];	
			endCubicVec[ZAXIS] = cubicVec[5];	

			double xStep = cubicVec[6];
			double yStep = cubicVec[7];
			double zStep = cubicVec[8];

			yLen = ((int) ((endCubicVec[YAXIS] - beginCubicVec[YAXIS]) / yStep)) + 1;
			zLen = ((int) ((endCubicVec[ZAXIS] - beginCubicVec[ZAXIS]) / zStep)) + 1;
			int xLen = ((int) ((endCubicVec[XAXIS] - beginCubicVec[XAXIS]) / xStep)) + 1;

			std::vector<double> newInterPointVec(beginCubicVec);

			/*
			 * fill the header with all points of the cubic structure,
			 * put all the values behind each other in the order all x for every y for every z,
			 * because that order is used for the signals between the transmitters and receivers
			 */
			for(int k = 0; k < zLen; k++){
				for(int i = 0; i < yLen; i++){
					for(int j = 0; j < xLen; j++){
						header.push_back(newInterPointVec);
						newInterPointVec[XAXIS] += xStep;
					}
					newInterPointVec[YAXIS] += yStep;
					newInterPointVec[XAXIS] = beginCubicVec[XAXIS];
				}
				newInterPointVec[ZAXIS] += zStep;
				newInterPointVec[YAXIS] = beginCubicVec[YAXIS];
			}
		} else{
			NS_LOG_ERROR("The number of arguments for the cubic header is not LENCUBIGHEAD: " << cubicVec.size());
		}

	}

	void RaLaNSPropagationLossModel::setHeader (std::string headerStringWithSpaces, std::vector< std::vector<double> > &header) {

		std::istringstream buffer(headerStringWithSpaces);

		std::vector<double> headerVec;
		double d;
		std::string tmp;

		while (getline( buffer, tmp, ' ' )) {
			d = atof(tmp.c_str());
			headerVec.push_back(d);
		}

		double type = headerVec[0];
		headerVec.erase(headerVec.begin());

		if(type == POINT){
			NS_LOG_DEBUG("set Point");

			setPoint(headerVec, header);							
		} else if(type == LINE){
			NS_LOG_DEBUG("set Line");

			setLine(headerVec, header);					
		} else if(type == AREA){
			NS_LOG_DEBUG("set Area");

			setArea(headerVec, header);			
		} else if(type == CUBIG){
			NS_LOG_DEBUG("set Cubic");

			setCubic(headerVec, header);				
		} else if(type == LIST){
			NS_LOG_DEBUG("set List");

			setList(headerVec, header);				
		} else{
			NS_LOG_ERROR("The value of the type in one of the headers is not valid: " << type);
		}
		NS_LOG_DEBUG("Setted in Header!!! Size: "<< header.size());
	}

	void RaLaNSPropagationLossModel::printTransmitters(){
		for(std::vector<double>::size_type i = 0; i < receivers.size(); i++){
			NS_LOG_DEBUG("Recv. : "<< receivers[i][XAXIS] <<", "<< receivers[i][YAXIS] <<", "<< receivers[i][ZAXIS]);
		}
	}

	void RaLaNSPropagationLossModel::printReceivers(){
		for(std::vector<double>::size_type i = 0; i < transmitters.size(); i++){
			NS_LOG_DEBUG("Trans. : "<< transmitters[i][XAXIS] <<", "<< transmitters[i][YAXIS] <<", "<< transmitters[i][ZAXIS]);
		}
	}



	//TODO read zip files too
	void RaLaNSPropagationLossModel::SetMap (std::string filename) {

		//set filename
		this->filename=filename;

		//open file
		std::ifstream infile;
		infile.open (filename.c_str());
		if (!infile.good()) {
			NS_LOG_ERROR("Unable to read file: " << filename);
		}

		std::string headerLine;

		NS_LOG_DEBUG("read Transmitters headerLine String");
		std::getline(infile, headerLine);
		NS_LOG_DEBUG("set Transmitters");
		setHeader(headerLine, transmitters);
		NS_LOG_DEBUG("Number of setted Transmitters: "<< transmitters.size());

		NS_LOG_DEBUG("read Receivers from headerLine String");
		std::getline(infile, headerLine);
		NS_LOG_DEBUG("set Receivers");
		setHeader(headerLine, receivers);

		NS_LOG_DEBUG("Number of setted Receivers: "<< receivers.size());

		std::string temp, tmp;

		int lineCounter = 1;
		std::vector<double> numbers;

		NS_LOG_DEBUG("yLen: " << yLen);
		NS_LOG_DEBUG("zLen: " << zLen);

		NS_LOG_DEBUG("now iterate over remaining file");
		while(std::getline(infile, temp)) {

			/*
			 * read the next line and store it in the line vector
			 */
			std::istringstream buffer(temp);
			std::vector<double> line;
			double d;
			while (getline( buffer, tmp, ' ' )) {
				d = atof(tmp.c_str());
				line.push_back(d);
			}

			/*
			 * concatenate the vectors, new values after old values
			 */
			numbers.insert(numbers.end(), line.begin(), line.end());

			/*
			 * yLen * zLen stands for the number of receivers each tranmitters has,
			 * so if that case happens all receiver signals for that tranmitter are already in the numbers vector
			 * so then all that signals will be added to the signals vector and the process goes again until the 
			 * lineCounter has has the value of yLen * zLen, so that all signals are read for that tranmitter
			 */
			if (lineCounter == yLen * zLen ) {
				signals.push_back(numbers);
				numbers.clear();
				lineCounter = 1;
			} else {
				lineCounter++;
			}

		}
		infile.close();

		NS_LOG_DEBUG("finished parsing " << signals.size() << " signals");
	}

	std::string RaLaNSPropagationLossModel::GetMap() {
		return filename;
	}

	double RaLaNSPropagationLossModel::DoCalcRxPower (double txPowerDbm,
			Ptr<MobilityModel> a,
			Ptr<MobilityModel> b) const
	{

		int transmitterPos = -1;
		double minDst=1e20;
		//TODO: switch to direct indexing for improved performance
		for(std::vector<double>::size_type t=0, size = transmitters.size(); t < size; t++) {
			double sqDst = std::pow(a->GetPosition().x - transmitters[t][XAXIS],2) + std::pow(a->GetPosition().y -  transmitters[t][YAXIS],2)+std::pow(a->GetPosition().z - transmitters[t][ZAXIS],2);
			if (sqDst < minDst) {
				minDst = sqDst;
				transmitterPos=t;
			}
		}
		
		if (minDst > THRESHOLD){
			NS_LOG_ERROR("Transmitter far away from match! " << a->GetPosition().x << " " << a->GetPosition().y << " "<< a->GetPosition().z <<  "|" << transmitters[transmitterPos][XAXIS] << " "  << transmitters[transmitterPos][YAXIS] << " " << transmitters[transmitterPos][ZAXIS] << "  distance: " << minDst);
		}

		int receiverPos = -1;
		minDst=1e20;

		for(std::vector<double>::size_type t=0, size = receivers.size(); t < size; t++) {
			double sqDst = std::pow(b->GetPosition().x - receivers[t][XAXIS],2) + std::pow(b->GetPosition().y -  receivers[t][YAXIS],2)+std::pow(b->GetPosition().z - receivers[t][ZAXIS],2);
			if (sqDst < minDst) {
				minDst = sqDst;
				receiverPos=t;
			}
		}

		if (minDst > THRESHOLD){
			NS_LOG_ERROR("Receiver far away from match! " << b->GetPosition().x << " " << b->GetPosition().y << " "<< b->GetPosition().z <<  "|" << receivers[transmitterPos][XAXIS] << " "  << receivers[transmitterPos][YAXIS] << " " << receivers[transmitterPos][ZAXIS] << "  distance: " << minDst);
		}	

		NS_LOG_DEBUG("position of transmitter in vector: " << transmitterPos);
		NS_LOG_DEBUG("position of receiver in vector: " << receiverPos);
		NS_LOG_DEBUG("signal loss from nearest transmitter to nearest receiver: " << signals[transmitterPos][receiverPos]);

		double result = txPowerDbm + signals[transmitterPos][receiverPos];

		NS_LOG_DEBUG(a->GetPosition().x << " " << a->GetPosition().y << " "<< a->GetPosition().z << " -> " << b->GetPosition().x << " " << b->GetPosition().y << " "<< b->GetPosition().z << " : " << result);

		return result;
	}

	int64_t RaLaNSPropagationLossModel::DoAssignStreams (int64_t stream)
	{
		return 0;
	}


}
