/**
 * GmshMesh.cpp
 * previously gmesh.cpp
 *
 *  Created on: May 9, 2017
 *      Author: vadims
 */

#include <boost/filesystem.hpp>
#include <string>
#include <chrono>
#include <stdexcept>

#include "Gmsh_IO.h"
#include "Mesh.h"
#include "Element.hpp"
#include "MeshToVtkWriter.hpp"
#include "cenos_exception.h"

using json = nlohmann::json;

Gmsh_IO::Gmsh_IO() : Mesh_IO()
{
}


void Gmsh_IO::readMesh(std::string& filename, Mesh* mesh)
{
	std::ifstream mesh_file_stream;
	mesh_file_stream.open(filename.c_str());
	int lnumb =0;
	if (!mesh_file_stream.is_open())
	{
		throw cenos_exception("Mesh file cannot be opened");
	}

    while (mesh_file_stream.good())
    {
		std::string line;
		getline(mesh_file_stream,line);
		std::istringstream lString(line);
		lnumb++;

        if (line.substr(0,11) == "$MeshFormat")
        {
            readMeshFormat(mesh_file_stream, mesh);
        }
        else if (line.substr(0,6) == "$Nodes")
        {
			readNodes(mesh_file_stream, mesh);
        }
        else if (line.substr(0,14) == "$PhysicalNames")
        {
			readPhysicalEntities(mesh_file_stream, mesh);
        }
        else if (line.substr(0,9) == "$Elements")
        {
			readElements(mesh_file_stream, mesh);
        }
        else
        {

        }
    }

	mesh_file_stream.close();
}

void Gmsh_IO::writeMesh(std::string& filename, Mesh* mesh)
{
	if (flags & WRITE_BINARY) writeBinaryMesh(filename, mesh);
	else writeASCIIMesh(filename, mesh);
}

void Gmsh_IO::writeASCIIMesh(std::string& filename, Mesh* mesh)
{
	std::ofstream mesh_file_stream;
	mesh_file_stream.open(filename.c_str());
	if (!mesh_file_stream.is_open())
	{
		throw cenos_exception("Mesh file cannot be opened");
	}

	mesh_file_stream << "$MeshFormat" << "\n";
	mesh_file_stream << "2 0 8" << "\n";
	mesh_file_stream << "$EndMeshFormat" << "\n";

	//******************
    // WRITE NAMES

	mesh_file_stream << "$PhysicalNames" << "\n";
	mesh_file_stream << mesh->getSizeEntities() << "\n";
	for (auto ent : mesh->getEntities())
	{
		mesh_file_stream << ent->getId() << " " << ent->getName() << "\n";
	}
	mesh_file_stream << "$EndPhysicalNames\n";
	// END WRITE NAMES
    //******************



	//******************
	// WRITE NODES
	mesh_file_stream << "$Nodes\n" ;
	mesh_file_stream << mesh->getSizeNodes() << "\n";
	double scale = mesh->getMeshScale();
	if (flags & WRITE_NON_SCALED) scale = 1;
	for (auto nd : mesh->getNodes())
	{
		mesh_file_stream << nd->getId() << " " << nd->x() * scale << " " << nd->y() * scale << " " << nd->z() * scale << "\n";
	}
	mesh_file_stream << "$EndNodes\n";
	// END WRITE NODES
	//******************


	//******************
	// WRITE ELEMENTS
	mesh_file_stream << "$Elements\n" ;
	mesh_file_stream << mesh->getSizeElements() << "\n";

	for (auto el : mesh->getElements())
	{
		mesh_file_stream << el->getId() << " ";
		mesh_file_stream << el->getElementType() << " ";
		mesh_file_stream << 2 << " ";  //nr of tags
		int physTag = el->getOwnerEntity()->getId();
		mesh_file_stream << physTag << " "; //physical entity tag
		mesh_file_stream << physTag << " "; //elementary entity tag
		// gmsh needs both physical and elementary tag, usually can be the same id

		for (auto nd: el->getNodes())
		{
			mesh_file_stream << nd->getId() << " ";
		}
		mesh_file_stream << "\n";
	}
	mesh_file_stream << "$EndElements";
	// END WRITE ELEMENTS
	//******************

	mesh_file_stream.close();
}

void Gmsh_IO::writeBinaryMesh(std::string& filename, Mesh* mesh)
{
	FILE* mesh_file_stream;
	mesh_file_stream = fopen(filename.c_str(), "wb");

	float scale = mesh->getMeshScale();
	fprintf(mesh_file_stream, "$MeshFormat\n");
	fprintf(mesh_file_stream, "2 1 8\n");

	int one = 1;
	fwrite(&one, sizeof(int), 1, mesh_file_stream);
	fprintf(mesh_file_stream, "\n");

	fprintf(mesh_file_stream, "$EndMeshFormat\n");

	fprintf(mesh_file_stream, "$PhysicalNames\n");
	fprintf(mesh_file_stream, "%d \n", static_cast<int>(mesh->getSizeEntities()));

	for (auto ent : mesh->getEntities())
	{
		fprintf(mesh_file_stream, "%d %s\n", ent->getId(), ent->getName());
	}
	fprintf(mesh_file_stream, "$EndPhysicalNames\n");
	fprintf(mesh_file_stream, "$Nodes\n");
	fprintf(mesh_file_stream, "%d \n", static_cast<int>(mesh->getSizeNodes()));

	for (auto nd : mesh->getNodes())
	{
		int ndId = nd->getId();
		fwrite(&ndId, sizeof(int), 1, mesh_file_stream);
		double xyz[3] = { nd->x() * scale,  nd->y() * scale, nd->z()*scale };
		fwrite(xyz, sizeof(double), 3, mesh_file_stream);
	}
	fprintf(mesh_file_stream, "$EndNodes\n");


	fprintf(mesh_file_stream, "$Elements\n");
	fprintf(mesh_file_stream, "%d \n", static_cast<int>(mesh->getSizeElements()));

	std::map<ElementTypes, std::vector<std::shared_ptr<Element>>> elementTypeMap;
	for (auto el : mesh->getElements())
	{
		elementTypeMap[static_cast<ElementTypes>(el->getElementType())].push_back(el);
	}

	for (auto elmsOfType : elementTypeMap)
	{
		// element type, number of elements to follow, number of tags
		int header[3] = { elmsOfType.first, elmsOfType.second.size(), 2 }; 
		fwrite(header, sizeof(int), 3, mesh_file_stream);
		for (auto el : elmsOfType.second)
		{
			std::vector<int> data;
			data.push_back(el->getId());
			data.push_back(el->getOwnerEntity()->getId());
			data.push_back(el->getOwnerEntity()->getId());
			for (auto nd : el->getNodes())
			{
				data.push_back(nd->getId());
			}
			fwrite(&data[0], sizeof(int), data.size(), mesh_file_stream);
		}
	}
	fprintf(mesh_file_stream, "$EndElements\n");
	fclose(mesh_file_stream);
}


void Gmsh_IO::readMeshFormat(std::ifstream & meshFile, Mesh* mesh)
{

	std::string line;
	getline(meshFile,line);
	std::istringstream lString(line);

	double vers;
	int asciiMark;
	lString >> vers >> asciiMark;

	if (asciiMark != 0)
	{
		std::cerr << "Gmsh file has binary format. Please provide Gmsh file in ASCII format";
		return;
	}
	mesh->version = trunc(vers);
}

void Gmsh_IO::readNodes(std::ifstream & meshFile, Mesh* mesh)
{
	std::string line;
	getline(meshFile,line);
	std::istringstream lString(line);

	int nNodes;
	lString >> nNodes;

	// reserve memory for nodes vector
	mesh->reserveNodesSize(nNodes);

    for (int i = 0; i < nNodes; i++)
    {
		int id;
		float xCoord = 0, yCoord = 0, zCoord = 0;
		std::string line;
		getline(meshFile,line);
		std::istringstream lString(line);
		lString >> id >> xCoord >> yCoord >> zCoord;
		std::shared_ptr<Node> n0 = std::make_shared<Node>(id, xCoord, yCoord, zCoord);

		mesh->addNode(n0);
    }

    if (nNodes==mesh->getSizeNodes()) {
       LOG(INFO) << "Nodes read successful!" << std::endl;
    }
    else {
        std::cerr<< "Node read error!" << std::endl;
    }
}



void Gmsh_IO::readPhysicalEntities(std::ifstream & meshFile, Mesh* mesh)
{
	std::string line;
	getline(meshFile,line);
	std::istringstream lString(line);

	int nEntities;
	lString >> nEntities;

	for (int i = 0; i < nEntities; i++)
    {

		int number, nDim;
		std::string entName;
		std::string line;


		getline(meshFile, line);
		std::istringstream lString(line);

		if (mesh->version < 2.1)
		{
			lString >> number >> entName;
			std::shared_ptr<MeshEntity> e0 = std::make_shared<MeshEntity>( entName, number);
			mesh->addEntity(e0);
		}
		else {
			lString >> nDim >> number >> entName;
			std::shared_ptr<MeshEntity> e0 = std::make_shared<MeshEntity>( entName, number);
			mesh->addEntity(e0);
		}

    }


    if (nEntities==mesh->getSizeEntities()) {
		LOG(INFO)  << "Entities read successful!";
    }
    else {
		LOG(ERROR) << "Entity read error!";
    }
}


void Gmsh_IO::readElements(std::ifstream & meshFile, Mesh* mesh)
{
	std::string line;
	getline(meshFile,line);
	std::istringstream lString0(line);
	//std::vector<int> myvector;
	//std::vector<int>::iterator it;

	int nElems;
	lString0 >> nElems;

	// reserve memory for nodes vector
	mesh->reserveElementsSize(nElems);

	int tag, nodeId;
	int number;
   	int elType;
	int nrTags;
	int physicalTag;


	for (int ipar = 0; ipar < nElems; ipar++)
	{
		std::vector<int> tags;
		std::vector<std::shared_ptr<Node>> nodeList;
		getline(meshFile, line);

		std::istringstream lStringStream(line);
/*		std::vector<int> lineData;
		int data;

		while (lStringStream >> data)
		{
			lineData.push_back(data);
		}*/

		lStringStream >> number;
		lStringStream >> elType;
		lStringStream >> nrTags;
		lStringStream >> physicalTag;
		std::string tag;
		for (int i = 0; i < nrTags - 1; i++)
		{
			lStringStream >> tag;
		}

		std::shared_ptr<Element> e0 = Element::createElement(static_cast<ElementTypes>(elType));

		for (int j = 0; j < e0->getTypeNodeCount(); j++)
		{
			lStringStream >> nodeId;
			nodeList.push_back(mesh->getNode(nodeId));
		}

		e0->setNodes(nodeList);

		e0->setId(number);

		// lineData[4] contains physical entity nr, starting numbering from 1
		// entities start numbering from 0
		std::shared_ptr<MeshEntity> ent = mesh->getEntityById(physicalTag);

		e0->setEntity(ent);

		mesh->addElement(e0);
	}

    if (nElems==mesh->getSizeElements()) {
		LOG(INFO)  << "Elements read successful!" << std::endl;
    }
    else {
        std::cerr<< "Element read error!" << std::endl;
    }
}





