#include "ADF.h"
#include "ccmiopost.h"
#include "ccmiocore.h"
#include <string.h>
#include <vector>
#include <iostream>

using namespace std;

class IOException : public std::exception
{
 public:
    IOException( const char *str ) : std::exception(), description(str) {};
    virtual ~IOException() throw() {};
    virtual const char* what() const throw()
	{ return(this->description.c_str()); };

 private:
    std::basic_string<char> description;
};

struct BoundaryData {
    int id;
    float priority;
    float value;

    BoundaryData() : id(0), priority(0), value(0.0) {};
};

class PostField {
 public:
    typedef std::vector<BoundaryData> BoundaryArray;
    
    std::basic_string<char> name;
    vector<float> data;
    BoundaryArray boundaryData;

    PostField( const char *_name, int size )
	: name(_name), data(size), dims(0)
	{};
    int GetDims() { return(this->dims); };

 protected:
    int dims;
};

struct PostScalarField : public PostField {
    PostScalarField( const char *name, int size)
	: PostField(name, size)
	{ this->dims = 1; };
};

struct PostVectorField : public PostField {
    PostVectorField( const char *name, int size)
	: PostField(name, 3 * size)
	{ this->dims = 2; };
};

class PostData {
 public:
    PostData() {};
    virtual ~PostData()
	{
	    for (unsigned int i = 0;  i < this->fields.size();  ++i)
		delete this->fields[i];
	}

    bool WriteCCMIO( const char *filename );
 protected:
    typedef std::vector<int> Cellmap;
    typedef std::vector<PostField *> Fields;

    Fields fields;
    Cellmap cellmap;
};

class PostDataNPost : public PostData {
 public:
    PostDataNPost::PostDataNPost( const char *filename );

 private:
    double rootID;

    bool PostDataNPost::ReadCellmap();
    PostVectorField* ReadVectorData( double variableID, char *variableName );
    PostScalarField* ReadScalarData( double variableID, char *variableName );
};

bool PostData::WriteCCMIO( const char *filename )
{
    CCMIONode root;
    CCMIOPost post;
    CCMIOPostData pdata;
    CCMIOError err;

    err = CCMIOOpen(filename, &root);
    if (err)	throw(IOException("Can't open CCMIO file"));

    // First check that the mesh has some chance of corresponding to the
    // NPOST file
    int nCells;
    CCMIOMesh mesh;
    err = CCMIOMeshFileOpen(filename, kCCMIORead, &mesh);
    CCMIOMeshGetEntitySize(&err, mesh, kCCMIOMeshCellData, &nCells, NULL);
    CCMIOMeshClose(&err, mesh);
    if ((unsigned int)nCells != this->cellmap.size())
	throw(IOException("Mesh in CCMIO file has different number of cells than the Post data does!"));

    CCMIOPostOpen(&err, (CCMIOSubDomain)&root, kCCMIOWrite, &post);

    Fields::iterator f;
    Cellmap::iterator c;

    for (f = this->fields.begin();  f != this->fields.end();  ++f)
    {
	int index = 0, cellID;

	// NPost only has celldata, so we don't need to write vertex or
	// face data.
	CCMIOPostWriteDataOpen(&err, post, kCCMIOMeshCellData, kCCMIOFloat32,
			(*f)->name.c_str(), 0, (*f)->GetDims(), &pdata);
	for (c = this->cellmap.begin();  c != this->cellmap.end(); ++c, ++index)
	{
//	    cellID = *c - 1;	/* *c is in Fortran indexing */
	    cellID = *c;
	    if ((*f)->GetDims() == 1)
		CCMIOPostDataWriteScalarf(&err, pdata, cellID, (*f)->data[index]);
	    else
	    {
		CCMIOPostDataWriteVectorf(&err, pdata, cellID, 0,
					(*f)->data[3 * index]);
		CCMIOPostDataWriteVectorf(&err, pdata, cellID, 1,
					(*f)->data[3 * index + 1]);
		CCMIOPostDataWriteVectorf(&err, pdata, cellID, 2,
					(*f)->data[3 * index + 2]);
	    }
	}
	if ((*f)->boundaryData.size())
	{
	    PostField::BoundaryArray::iterator b;

	    for (b=(*f)->boundaryData.begin(); b!=(*f)->boundaryData.end(); ++b)
		CCMIOPostSetNextBoundaryPriority(&err, pdata, b->id,
					       (int)b->priority);
	}
	CCMIOPostDataClose(&err, pdata);
    }

    CCMIOPostClose(&err, post);
    CCMIOClose(root);
    
    if (err != kCCMIONoErr)
	throw(IOException("Error writing to CCMIO file"));
    return(true);
}

// global scope
static bool check_error( int error ) { return(error != -1); };

bool PostDataNPost::ReadCellmap()
{
    int nCells, error;
    double cellmapID;

    ADF_Get_Node_ID(rootID, "cellmap", &cellmapID, &error);	
    if (check_error(error)) return(false);
    
    ADF_Get_Dimension_Values(cellmapID, &nCells, &error);
    if (check_error(error)) return(false);
    
    this->cellmap.resize(nCells);
    ADF_Read_All_Data(cellmapID, (char *)&this->cellmap[0], &error);

    return(!check_error(error));
}

PostDataNPost::PostDataNPost( const char *filename )
// Mostly stolen from the meshserver reader
{
    int i, j, k, error;
    PostField *field;

    // open file
    ADF_Database_Open(filename, "READ_ONLY", "", &this->rootID, &error);
    if (check_error(error)) throw(IOException("Can't open NPOST file"));

    // extract number of variables and their names
    int num_variables;
    ADF_Number_of_Children(this->rootID, &num_variables, &error);
    if (check_error(error)) throw(IOException("Corrupt NPOST file"));

    char *variable_names = new char[num_variables * 33];
    int num_ret;
    ADF_Children_Names(this->rootID, 1, num_variables, 32, &num_ret, 
		       variable_names, &error);
    if (check_error(error)) throw(IOException("Corrupt NPOST file"));
    
    if (!ReadCellmap())
	throw(IOException("Can't read cellmap"));
    
    for(i=0; i<num_variables; i++)
    {
	char *variableName = variable_names + 33*i;
	if (strcmp(variableName, "cellmap") == 0)
	    continue;
	
	// get the node id for the variable
	double variableID;
	ADF_Get_Node_ID(rootID, variableName, &variableID, &error);
	if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	
	// get the type
	double type_id;
	char type[33];
	ADF_Get_Node_ID(variableID, "type", &type_id, &error);
	if (check_error(error)) throw(IOException("Corrupt NPOST file"));


	ADF_Get_Label(type_id, type, &error);
	if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	
	if (strcmp(type,"vector") == 0 || strcmp(type,"VECTOR") == 0)
	    field = ReadVectorData(variableID, variableName);
	else if (strcmp(type,"scalar") == 0 || strcmp(type,"SCALAR") == 0)
	    field = ReadScalarData(variableID, variableName);
	    
	// read region information
	double regions_id;
	ADF_Get_Node_ID(variableID, "regions", &regions_id, &error);
	if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	

	// extract number of variables and their names
	int num_regions;
	ADF_Number_of_Children(regions_id, &num_regions, &error);
	if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	
	vector<char> region_names(num_regions * 5);
	if (num_regions > 0)
	{
	    ADF_Children_Names(regions_id, 1, num_regions, 4, 
			       &num_ret, &region_names[0], &error);
	    if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	}
	
	for(j = 0; j<num_regions; j++)
	{
	    char *region_name = &region_names[5*j];
	    BoundaryData boundData;
	    
	    // get the region node
	    double region_id;
	    ADF_Get_Node_ID(regions_id, region_name, &region_id, &error);
	    if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	    
	    // get the boundary data for the region
	    boundData.id = atoi(region_name);
	    
	    // read flags
	    int num_flags;
	    ADF_Number_of_Children(region_id, &num_flags, &error);
	    if (check_error(error)) throw(IOException("Corrupt NPOST file"));
		
	    vector<char> flag_names(num_flags * 33);
	    if (num_flags > 0)
	    {
		ADF_Children_Names(region_id, 1, num_flags, 32, 
				   &num_ret, &flag_names[0], &error);
		if (check_error(error)) throw(IOException("Corrupt NPOST file"));
	    }
	    for(k=0; k<num_flags; k++)
	    {
		char *flag_name = &flag_names[33*k];
		
		// get the flag node
		double flag_id;
		ADF_Get_Node_ID(region_id, flag_name, &flag_id, &error);
		if (check_error(error)) throw(IOException("Corrupt NPOST file"));
		
		if (strcmp(flag_name, "_priority") == 0)
		{
		    ADF_Read_All_Data(flag_id, (char *)&boundData.priority,
				      &error);
		    if (check_error(error))
			throw(IOException("Bad read of boundary priority"));
		}
		else if (strcmp(flag_name, "_value") == 0)
		{
		    // read and set the data value
		    ADF_Read_All_Data(flag_id, (char *)&boundData.value,
				      &error);
		    if (check_error(error))
			throw(IOException("Bad read of boundary value"));
		}
		else
		{
/*		    char flag[32];
		    ADF_Get_Label(flag_id, flag, &error);
		    if (check_error(error))
		    return error;
			
		    sbd.setFlag(flag_name, flag); */
		}
	    }
	    cout << boundData.id << " " << boundData.priority << endl;
	    field->boundaryData.push_back(boundData);
	}
	this->fields.push_back(field);
    }

    delete[] variable_names;
    
    ADF_Database_Close(rootID, &error);
    if (check_error(error)) throw(IOException("Failed to close NPOST file!"));
}

PostVectorField* PostDataNPost::ReadVectorData( double variableID,
						char *variableName )
{
    int error;
    double celldataID;
    PostVectorField *field = new PostVectorField(variableName,
						 this->cellmap.size());
	
    // get cell data node
    ADF_Get_Node_ID(variableID, "celldata", &celldataID, &error);
    if (check_error(error)) return(NULL);
	
    // read cell data
    ADF_Read_All_Data(celldataID, (char *)&field->data[0], &error);
    if (check_error(error)) return(NULL);
	
    return(field);
}

PostScalarField* PostDataNPost::ReadScalarData( double variableID,
						char *variableName )
{
    int error;
    double celldataID;
    PostScalarField *field = new PostScalarField(variableName,
						 this->cellmap.size());

    // get cell data node
    ADF_Get_Node_ID(variableID, "celldata", &celldataID, &error);
    if (check_error(error)) return(NULL);
    
    // read cell data
    ADF_Read_All_Data(celldataID, (char *)&field->data[0], &error);
    if (check_error(error)) return(NULL);

    return(field);
}

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
	cout << "Usage: " << endl << "\t" << argv[0] << " file.ccm data.npost"
	     << endl;
	return(1);
    }

    try
    {
	PostDataNPost data(argv[2]);
	data.WriteCCMIO(argv[1]);
    }
    catch(IOException& e)
    {
	cerr << e.what() << endl;
    }

    return(0);
}
