#include "Mesh.h"
#include "MeshCCMIO.h"
#include <iostream>
#include <sstream>

using namespace std;

//const char kDefaultState[] = "default";

#define PERR(err,msg) if (err != kCCMIONoErr) {cout << msg << endl; return;}
#define PERR2(err,msg1,msg2) if (err != kCCMIONoErr) {cout << msg1 << msg2 << endl; return;}


MeshCCMIO::MeshCCMIO( const char* filename ) : Mesh()
{
    CCMIOID root;
    CCMIOOpenFile(&err, filename, kCCMIORead, &root);
    PERR2(err, "Error opening default state ", filename);

    ////- Get state named kDefaultState
    //CCMIOID problem, state;
    //CCMIOGetState(&err, root, kDefaultState, &problem, &state);
    //PERR(err, "Error opening default state");

    //- Get first state
    CCMIOID state;
    int stateI = 0;
    CCMIONextEntity(&err, root, kCCMIOState, &stateI, &state);
    PERR2(err, "Error opening state ", stateI);
    
    CCMIOID processor;
    int i = 0;
    CCMIONextEntity(&err, state, kCCMIOProcessor, &i, &processor);
    PERR(err, "Error stepping to processor0 information");

    CCMIOID vertices, topology;
    CCMIOReadProcessor(&err, processor, &vertices, &topology, NULL, NULL);
    PERR(err, "Error finding default state and processor");

    Allocate(vertices, topology);
    ReadMesh(vertices, topology);

    // Read boundary region information
    CCMIOID problem;
    int problemI = 0;
    CCMIONextEntity(&err, root, kCCMIOProblemDescription, &problemI, &problem);
    PERR(err, "Error stepping to first problem description");

    if (CCMIOIsValidEntity(problem))   // if we have a problem description...
    {
        CCMIOID boundary;
        int k = 0;
        while (CCMIONextEntity(NULL, problem, kCCMIOBoundaryRegion, &k, &boundary)==kCCMIONoErr)
        {
            boundaryRegion boundaryDescription;

            // Read prostar id.

            int prostarId;
	    CCMIOGetEntityIndex(&err, boundary, &prostarId);
            boundaryDescription.prostarRegion = prostarId;


            // Read boundary type

            int size;
            CCMIOReadOptstr(&err, boundary, "BoundaryType", &size, NULL);
            PERR(err, "Error reading opt string 'BoundaryType'");
	    {
	        char* type = new char[size + 1];
	        CCMIOReadOptstr(&err, boundary, "BoundaryType", &size, type);
                type[size] = '\0';

                boundaryDescription.boundaryType = type;

	        delete [] type;
	    }


            // Read boundary name:
            // - from BoundaryName field (Prostar)
            // - from 'Label' field (ccm+?)
            // - make up one from prostar id.

            if (CCMIOReadOptstr(&err, boundary, "BoundaryName", &size, NULL) == kCCMIONoErr)
	    {
	        char* name = new char[size + 1];
	        CCMIOReadOptstr(&err, boundary, "BoundaryName", &size, name);
                name[size] = '\0';

                boundaryDescription.label = name;

	        delete [] name;
	    }
            else if (CCMIOReadOptstr(NULL, boundary, "Label", &size, NULL) == kCCMIONoErr)
	    {
	        char* name = new char[size + 1];
	        CCMIOReadOptstr(&err, boundary, "Label", &size, name);
                name[size] = '\0';

                boundaryDescription.label = name;

	        delete [] name;
	    }
            else
            {
                std::ostringstream osBuffer;
                osBuffer << boundaryDescription.prostarRegion;

                string name = boundaryDescription.boundaryType + osBuffer.str();

                boundaryDescription.label = name;
            }

            boundaryRegionInfo.push_back(boundaryDescription);
        }
    }

    CCMIOCloseFile(&err, vertices);
    CCMIOCloseFile(&err, topology);
    CCMIOCloseFile(&err, root);
    this->filename = filename;
}

MeshCCMIO::MeshCCMIO( const char *filename, CCMIOID& vertices, CCMIOID& topology)
    : Mesh()
{
    this->err = kCCMIONoErr;
    Allocate(vertices, topology);
    ReadMesh(vertices, topology);
    this->filename = filename;
}

// Needed because large meshes may cause memory fragmentation
void MeshCCMIO::Allocate( CCMIOID& vertices, CCMIOID& topology )
{
    int i = 0;
    unsigned int nVertices, maxVertex = 0, nCells, maxCell = 0, nIFaces = 0;
    unsigned int nBFaces =0;
    CCMIOID id;
    CCMIOError *err = &this->err;

    CCMIOEntitySize(err, vertices, &nVertices, &maxVertex);
    CCMIOGetEntity(err, topology, kCCMIOCells, 0, &id);
    CCMIOEntitySize(err, id, &nCells, &maxCell);
    CCMIOGetEntity(err, topology, kCCMIOInternalFaces, 0, &id);
    CCMIOEntitySize(err, id, &nIFaces, NULL);
    while (CCMIONextEntity(NULL, topology, kCCMIOBoundaryFaces, &i, &id) == kCCMIONoErr)
    {
	unsigned int size;
	CCMIOEntitySize(err, id, &size, NULL);
	nBFaces += size;
    }
    cout << "Vertices:  number: " << nVertices << "   max: " << maxVertex
	 << endl;
    cout << "Cells:  number: " << nCells << "   max: " << maxCell
	 << endl;
    this->vertices.resize(maxVertex + 1);
    this->packedVerts.resize(nVertices);
    this->cells.resize(maxCell + 1);
    this->internalFaces.reserve(nIFaces);
    this->boundaryFaces.reserve(nBFaces);
    cout << "Faces:  internal: " << nIFaces << "  boundary: " << nBFaces
	 << endl;
}

void MeshCCMIO::ReadMesh( CCMIOID& vertices, CCMIOID& topology )
{
    int dims;
    unsigned int max, n, size;
    float scale;
    CCMIOID mapID, id;
    CCMIOError *err = &this->err;

    // Read vertices
    {
    CCMIOEntitySize(err, vertices, &n, &max);
    vector<float> xyz(3 * n, 0);
    CCMIOReadVerticesf(err, vertices, &dims, &scale, &mapID, &xyz[0],
		       kCCMIOStart, kCCMIOEnd);
    if (dims != 3)
	throw(IOException("Can only handle 3-dimensional vertices"));
    vector<int> ids(n, 0);
    CCMIOReadMap(err, mapID, &ids[0], kCCMIOStart, kCCMIOEnd);
    for (unsigned int i = 0;  i < n;  ++i)
    {
	this->packedVerts[i].coord[0] = scale * xyz[3 * i];
	this->packedVerts[i].coord[1] = scale * xyz[3 * i + 1];
	this->packedVerts[i].coord[2] = scale * xyz[3 * i + 2];
	this->vertices[ids[i]] = this->packedVerts[i];
    }
    }
    if (*err != kCCMIONoErr)
	throw(IOException("Error reading vertices."));
    this->nVertices = n;

    // Read cells
    CCMIOGetEntity(err, topology, kCCMIOCells, 0, &id);
    CCMIOEntitySize(err, id, &n, &max);
    {
    vector<int> ids(n, 0), c(n, 0);
    CCMIOReadCells(err, id, &mapID, &c[0], kCCMIOStart, kCCMIOEnd);
    CCMIOReadMap(err, mapID, &ids[0], kCCMIOStart, kCCMIOEnd);
    for (unsigned int i = 0;  i < n;  ++i)
	this->cells[ids[i]].type = c[i];
    }
    this->nCells = n;
    if (*err != kCCMIONoErr)
	throw(IOException("Error reading cells"));

    // Read interfaces
    CCMIOID interfaces;
    if (CCMIOGetEntity(NULL, id, kCCMIOInterfaces, 0, &interfaces)==kCCMIONoErr)
    {
	unsigned int nn;
	CCMIOGetOptInfo(err, interfaces, "FaceIds", NULL, &nn, NULL, NULL);
	this->interfaces.resize(nn);
	CCMIOReadOpt2i(err, interfaces, "FaceIds", (int*)&this->interfaces[0],
		       kCCMIOStart, kCCMIOEnd);
    }

    // Read internal faces
    CCMIOGetEntity(err, topology, kCCMIOInternalFaces, 0, &id);
    CCMIOEntitySize(err, id, &n, NULL);
    {
    vector<int>::iterator iter;
    vector<int> faces, c(2 * n, 0), ids(n, 0);
    CCMIOReadFaces(err, id, kCCMIOInternalFaces, &mapID, &size, NULL,
		   kCCMIOStart, kCCMIOEnd);
    faces.resize(size);
    CCMIOReadFaces(err, id, kCCMIOInternalFaces, NULL, NULL, &faces[0],
		   kCCMIOStart, kCCMIOEnd);
    CCMIOReadFaceCells(err, id, kCCMIOInternalFaces, &c[0], kCCMIOStart,
		       kCCMIOEnd);
    CCMIOReadMap(err, mapID, &ids[0], kCCMIOStart, kCCMIOEnd);
    iter = faces.begin();
    for (unsigned int i = 0;  i < n;  ++i)
    {
        MeshFace face;

	face.id = ids[i];
        int nVerts = *iter;
        ++iter;

        face.verts.reserve(nVerts);
	for (int j = 0;  j < nVerts;  ++j, ++iter)
        {
            face.verts.push_back(*iter);
        }

	face.isBoundary = false;
	face.cells[0] = c[2 * i];
	face.cells[1] = c[2 * i + 1];

	internalFaces.push_back(face);
    }
    }
    if (*err != kCCMIONoErr)
	throw(IOException("Error reading internal faces"));

    int k = 0;
    this->nBoundaries = 0;
    while (CCMIONextEntity(NULL, topology, kCCMIOBoundaryFaces, &k, &id)==kCCMIONoErr)
    {
	CCMIOEntitySize(err, id, &n, NULL);

	vector<int>::iterator iter;
	vector<int> faces, c(2 * n, 0), ids(n, 0);

	int boundary;
	CCMIOGetEntityIndex(err, id, &boundary);

	int prostarRegion;
	if (CCMIOReadOpti(err, id, "ProstarRegionNumber", &prostarRegion)  != kCCMIONoErr)
        {
            prostarRegion = -1;
        }
        cout<< "boundary:" << boundary << " ProstarRegionNumber:"
            << prostarRegion << std::endl;

	CCMIOReadFaces(err, id, kCCMIOBoundaryFaces, &mapID, &size, NULL,
		       kCCMIOStart, kCCMIOEnd);
	faces.resize(size);
	CCMIOReadFaces(err, id, kCCMIOBoundaryFaces, NULL, NULL, &faces[0],
		       kCCMIOStart, kCCMIOEnd);
	CCMIOReadFaceCells(err, id, kCCMIOBoundaryFaces, &c[0], kCCMIOStart,
			   kCCMIOEnd);
	CCMIOReadMap(err, mapID, &ids[0], kCCMIOStart, kCCMIOEnd);
	iter = faces.begin();
	for (unsigned int i = 0;  i < n;  ++i)
	{
    	    MeshFace face;

	    face.id = ids[i];
            int nVerts = *iter;
            ++iter;

            face.verts.reserve(nVerts);
	    for (int j = 0;  j < nVerts;  ++j, ++iter)
            {
                face.verts.push_back(*iter);
            }

	    face.isBoundary = true;
	    face.cells[0] = c[i];
	    face.cells[1] = -9999;
	    face.boundary = boundary;
            face.prostarRegion = prostarRegion;
	    this->boundaryFaces.push_back(face);
	}
	this->nBoundaries++;
    }

    if (*err != kCCMIONoErr)
	throw(IOException("Error reading boundary faces"));
}

void MeshCCMIO::PrintInterfaces()
{
    unsigned int size = this->interfaces.size();

    for (unsigned int i = 0;  i < size;  ++i)
    {
	FaceArray::iterator fit;
	
	cout << "{ face " << this->interfaces[i].face1 << " (";
	if (this->FindFace(this->interfaces[i].face1, fit))
	    cout << "cells " << fit->cells[0] << " and " << fit->cells[1];
	else
	    cout << "invalid face";
	cout << "), " << "face " << this->interfaces[i].face2 << " (";
	if (this->FindFace(this->interfaces[i].face2, fit))
	    cout << "cells " << fit->cells[0] << " and " << fit->cells[1];
	else
	    cout << "invalid face";
	cout << ") }" << endl;
    }
}
