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

using namespace std;

const char kDefaultState[] = "default";

MeshCCMIO::MeshCCMIO( const char* filename ) : Mesh()
{
    int i = 0;
    CCMIOID root, state, processor, vertices, topology;

    this->err = CCMIOOpenFile(NULL, filename, kCCMIORead, &root);
    CCMIOGetState(&this->err, root, kDefaultState, NULL, &state);
    CCMIONextEntity(&this->err, state, kCCMIOProcessor, &i, &processor);
    CCMIOReadProcessor(&this->err, processor, &vertices, &topology, NULL, NULL);

    if (this->err != kCCMIONoErr)
	cout << "Error finding default state and processor!" << endl;
    else
    {
	Allocate(vertices, topology);
	ReadMesh(vertices, topology);
    }

    CCMIOCloseFile(&this->err, vertices);
    CCMIOCloseFile(&this->err, topology);
    CCMIOCloseFile(&this->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);
}

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);
    MeshFace face;
    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)
    {
	face.id = ids[i];
	face.nVerts = *iter;	++iter;
	for (int j = 0;  j < face.nVerts;  ++j, ++iter)
	    face.verts[j] = *iter;
	face.isBoundary = false;
	face.cells[0] = c[2 * i];
	face.cells[1] = c[2 * i + 1];
	this->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);

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

	CCMIOGetEntityIndex(err, id, &boundary);

	MeshFace face;
	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)
	{
	    face.id = ids[i];
	    face.nVerts = *iter;	++iter;
	    for (int j = 0;  j < face.nVerts;  ++j, ++iter)
		face.verts[j] = *iter;
	    face.isBoundary = true;
	    face.cells[0] = c[i];
	    face.cells[1] = -9999;
	    face.boundary = boundary;
	    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;
    }
}
