#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "ccmio.h"
#include "ccmiocore.h"
#include "ccmiobuffer.h"
#include "ccmioutility.h"

const char kFilename[] = "/tmp/ccmio.adf";

const int kVersion = 1234;
const char kTitle[] = "Test title";
const char kI32ScalarName[] = "scalar_i32";
const int kI32ScalarValue = 1234567890;
const char kF64ScalarName[] = "scalar_f64";
const double kF64ScalarValue = 1234567890.0987654321;
const char kArrayNodeName[] = "arrays";
const char kBufferedArrayNodeName[] = "buffered arrays";
const char kDirectArrayNodeName[] = "direct arrays";
const char k1DArrayNodeName[] = "1D array";
const char k1DArrayPath[] = "/arrays/1D array";
const char k1DBufferedArrayPath[] = "/buffered arrays/1D array";
const char k1DDirectArrayPath[] = "/direct arrays/1D array";
const char k2DArrayNodeName[] = "2D array";
#define k1DArraySize	10
const int k1DArrayValue[k1DArraySize] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const char k2DArrayPath[] = "/arrays/2D array";
const char k2DBufferedArrayPath[] = "/buffered arrays/2D array";
const char k2DDirectArrayPath[] = "/direct arrays/2D array";
const int k2DArraySize[2] = { 4, 5 };
#define k2DArraySize_Dim0	4
#define k2DArraySize_Dim1	5
const int k2DArrayValue[k2DArraySize_Dim0][k2DArraySize_Dim1] =
    { { 1, 2, 3, 4, 5 },
      { 6, 7, 8, 9, 10},
      { 11, 12, 13, 14, 15 },
      { 16, 17, 18, 19, 20 } };
const char kHierarchyNodeName[] = "hierarchy";
#define kHierarchyWidth		3
#define kHierarchyDepth		4
const char kHierarchyValue[kHierarchyDepth][kHierarchyWidth][16] = 
      { { "",		"grandfather",		"" },
	{ "father1",	"father2",		"father3" },
	{ "dead son",	"son2.1",		"son3.1" },
	{ "grandson",		"",		"" } };
struct {
    char name[16];
    int nChildren;
} kHierarchyTestTable[] =
{ { "grandfather", 3 }, { "father1", 0 }, { "father2", 1 }, { "father3", 1 },
  { "", 0 } };
const char kDeletedNodePath[] = "/hierarchy/grandfather/father1/dead son";
const char kDeletedChildPath[] = "/hierarchy/grandfather/father1/dead son/grandson";
const char kMovedNodeOriginalPath[] = "/hierarchy/grandfather/father2/father3";
const char kMovedNodePath[] = "/hierarchy/grandfather/father3";
const char kMovedChildPath[] = "/hierarchy/grandfather/father3/son3.1";
const int kBufferSize = 3;

int gErr = 0;

void PrintError( CCMIOError err, char *fmt, ... )
{
    va_list args;

    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
    printf("  (error %d)\n", (int)err);
}

void Test( CCMIOError err, int result, char *title )
{
    printf("Test '%s' ", title);
    if (err == kCCMIONoErr && result)
	printf ("passed");
    else if (err == kCCMIONoErr)
    {
	printf("FAILED (bad value)");
	gErr++;
    }
    else
    {
	printf("FAILED (error %d)", (int)err);
	gErr++;
    }
    printf(".\n");
}

int InHierarchy( char *name, int level )
{
    int i;

    for (i = 0;  i < kHierarchyWidth;  ++i)
    {
	if (strcmp(kHierarchyValue[level][i], name) == 0)
	    return(TRUE);
    }
    return(FALSE);
}

int HasRightChildren( char *name, CCMIONode node )
{
    int i, n;

    for (i = 0;  kHierarchyTestTable[i].name[0] != '\0';  ++i)
    {
	if (strcmp(kHierarchyTestTable[i].name, name) == 0)
	{
	    n = -1;	/* In case an error occurs and n isn't set, we'll know*/
	    CCMIOGetNumberOfChildren(NULL, node, &n);
	    if (n != kHierarchyTestTable[i].nChildren)
		return(FALSE);
	    return(TRUE);
	}
	    
    }
    return(FALSE);
}

void ReadTest( const char *filename )
{
    char *title, name[kCCMIOMaxStringLength + 1];
    int x, y, i, version, int32, val, badVals, nth;
    double float64;
    CCMIONode root, node, grandfather;
    CCMIOBuffer buff;
    CCMIOError err = kCCMIONoErr;

    if ((err = CCMIOOpen(filename, kCCMIORead, &root)))
	Test(err, TRUE, "CCMIOOpenFile:  existing file");
    if (err)
	return;

    /* Test title and version.  (Title also tests 1D arrays) */
    CCMIOGetVersion(&err, root, &version);
    Test(err, version == kVersion, "CCMIOGetVersion");
    err=0; CCMIOGetTitle(&err, root, &title);
    Test(err, strcmp(kTitle, title) == 0, "CCMIOGetTitle");
    free(title);

    /* Test scalar data reads */
    err=0; CCMIOGetNode(&err, root, kI32ScalarName, &node);
    Test(err, TRUE, "GetNode kI32ScalarName");
    err=0; CCMIOReadData(&err, node, (void *)&int32, kCCMIOInt32, 1);
    Test(err, int32 == kI32ScalarValue, "Reading Int32 value");
    
    err=0; CCMIOGetNode(&err, root, kF64ScalarName, &node);
    Test(err, TRUE, "GetNode kF64ScalarName");
    err=0; CCMIOReadData(&err, node, (void *)&float64, kCCMIOFloat64, 1);
    Test(err, float64 == kF64ScalarValue, "Reading Float64 value");

    /* Test 1D array read */
    err=0; CCMIOGetNode(&err, root, k1DArrayPath, &node);
    printf("Reading 1D array:  ");
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	CCMIOReadDataPoint(&err, node, (void *)&val, x, kCCMIOEndArgs);
	if (val == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += val != k1DArrayValue[x];
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 1D array");

    /* Test 2D array read */
    err=0; CCMIOGetNode(&err, root, k2DArrayPath, &node);
    printf("Reading 2D array:\n\t");
    badVals = 0;
    for (y = 0;  y < k2DArraySize_Dim1;  ++y)
    {
	for (x = 0;  x < k2DArraySize_Dim0;  ++x)
	{
	    CCMIOReadDataPoint(&err, node, (void *)&val, x, y, kCCMIOEndArgs);
	    if (val == k2DArrayValue[x][y])
		printf(". ");
	    else
		printf("X ");
	    badVals += val != k2DArrayValue[x][y];
	}
	printf("\n\t");
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 2D array");

    /* Test 1D buffered array read */
    CCMIOSetBufferSize(3);
    err=0; CCMIOGetNode(&err, root, k1DBufferedArrayPath, &node);
    CCMIOCreateBuffer(&err, node, kCCMIORead, &buff);
    printf("Reading buffered 1D array:  ");
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	CCMIOBufferReadDataPoint(&err, buff, (void *)&val, x, kCCMIOEndArgs);
	if (val == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += val != k1DArrayValue[x];
    }
    printf("\n");
    CCMIODestroyBuffer(&err, buff);
    Test(err, badVals == 0, "Reading 1D array");

    /* Test 2D buffered array read */
    err=0; CCMIOGetNode(&err, root, k2DBufferedArrayPath, &node);
    CCMIOCreateBuffer(&err, node, kCCMIORead, &buff);
    printf("Reading 2D array:\n\t");
    badVals = 0;
    for (y = 0;  y < k2DArraySize_Dim1;  ++y)
    {
	for (x = 0;  x < k2DArraySize_Dim0;  ++x)
	{
	    CCMIOBufferReadDataPoint(&err, buff, (void *)&val, x, y,
				     kCCMIOEndArgs);
	    if (val == k2DArrayValue[x][y])
		printf(". ");
	    else
		printf("X ");
	    badVals += val != k2DArrayValue[x][y];
	}
	printf("\n\t");
    }
    printf("\n");
    CCMIODestroyBuffer(&err, buff);
    Test(err, badVals == 0, "Reading 2D array");

    /* Test 1D direct read */
    printf("Reading 1D array (CCMIORead1d):  ");
    double *dary = malloc(sizeof(double) * k1DArraySize);
    err = 0; CCMIOGetNode(&err, root, k1DDirectArrayPath, &node);
    CCMIORead1d(&err, node, dary, kCCMIOStart, kCCMIOEnd);
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	if (dary[x] == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += dary[x] != k1DArrayValue[x];
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 1D array (CCMIORead1d)");
    free(dary);

    /* Test 2D direct read */
    printf("Reading 2D array (CCMIORead2d, C mode):\n\t");
    double **d2ary = (double **)malloc(k2DArraySize_Dim0 * sizeof(double *));
    d2ary[0] = (double *)malloc(k2DArraySize_Dim0 * k2DArraySize_Dim1* sizeof(double));
    for(x = 1; x < k2DArraySize_Dim0; x++)
	d2ary[x] = d2ary[0] + x * k2DArraySize_Dim1;
    err = 0; CCMIOGetNode(&err, root, k2DDirectArrayPath, &node);
    CCMIORead2d(&err, node, *d2ary, kCCMIOStart, kCCMIOEnd);
    badVals = 0;
    for (y = 0;  y < k2DArraySize_Dim1;  ++y)
    {
	for (x = 0;  x < k2DArraySize_Dim0;  ++x)
	{
	    if (d2ary[x][y] == k2DArrayValue[x][y])
		printf(". ");
	    else
		printf("X ");
	    badVals += d2ary[x][y] != k2DArrayValue[x][y];
	}
	printf("\n\t");
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 2D array (CCMIORead2d, C mode)");

    /* Test new buffered 1D read */
    printf("Reading buffered 1D array (CCMIORead1d, int -> float):  ");
    dary = malloc(sizeof(double) * k1DArraySize);
    err = 0; CCMIOGetNode(&err, root, k1DDirectArrayPath, &node);
    for (i = 0;  i < k1DArraySize;  i += 3)
	CCMIORead1d(&err, node, dary + i, i, i + 3);
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	if (dary[x] == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += dary[x] != k1DArrayValue[x];
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 1D array (CCMIORead1d, int -> float)");
    free(dary);

    /* Test new buffered 1D read, no conversion */
    printf("Reading buffered 1D array (CCMIORead1d):  ");
    int *iary = malloc(sizeof(int) * k1DArraySize);
    err = 0; CCMIOGetNode(&err, root, k1DDirectArrayPath, &node);
    for (i = 0;  i < k1DArraySize;  i += 3)
	CCMIORead1i(&err, node, iary + i, i, i + 3);
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	if (iary[x] == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += iary[x] != k1DArrayValue[x];
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 1D array (CCMIORead1d)");
    free(iary);

    /* Test direct 1D read, no conversion */
    printf("Reading buffered 1D array (CCMIORead1d):  ");
    iary = malloc(sizeof(int) * k1DArraySize);
    err = 0; CCMIOGetNode(&err, root, k1DDirectArrayPath, &node);
    CCMIORead1i(&err, node, iary, kCCMIOStart, kCCMIOEnd);
    badVals = 0;
    for (x = 0;  x < k1DArraySize;  ++x)
    {
	if (iary[x] == k1DArrayValue[x])
	    printf(". ");
	else
	    printf("X ");
	badVals += iary[x] != k1DArrayValue[x];
    }
    printf("\n");
    Test(err, badVals == 0, "Reading 1D array (CCMIORead1d)");
    free(iary);

    /* Test new buffered 2D read */
    printf("Reading new buffered 2D array (CCMIORead2d):\n\t");
    d2ary = (double **)malloc(k2DArraySize_Dim0 * sizeof(double *));
    d2ary[0] = (double *)malloc(k2DArraySize_Dim0 * k2DArraySize_Dim1* sizeof(double));
    for(x = 1; x < k2DArraySize_Dim0; x++)
	d2ary[x] = d2ary[0] + x * k2DArraySize_Dim1;
    err = 0; CCMIOGetNode(&err, root, k2DDirectArrayPath, &node);
    for (i = 0;  i < k2DArraySize_Dim1;  i += 2)
	CCMIORead2d(&err, node, *d2ary + i * k2DArraySize_Dim0, i, i + 2);
    badVals = 0;
    for (y = 0;  y < k2DArraySize_Dim1;  ++y)
    {
	for (x = 0;  x < k2DArraySize_Dim0;  ++x)
	{
	    if (d2ary[x][y] == k2DArrayValue[x][y])
		printf(". ");
	    else
		printf("X ");
	    badVals += d2ary[x][y] != k2DArrayValue[x][y];
	}
	printf("\n\t");
    }
    printf("\n");
    Test(err, badVals == 0, "Reading new buffered 2D array (CCMIORead2d)");

    /* Test deletion */
    err=0;  CCMIOGetNode(&err, root, kDeletedNodePath, &node);
    Test(kCCMIONoErr, err == kCCMIONoNodeErr, "CCMIODeleteNode verification");
    err=0;  CCMIOGetNode(&err, root, kDeletedChildPath, &node);
    Test(kCCMIONoErr, err == kCCMIONoNodeErr, "CCMIODeleteNode hierarchy deletion verification");

    /* Test moved node */
    err=0;  CCMIOGetNode(&err, root, kMovedNodePath, &node);
    Test(err, TRUE, "CCMIOMoveNode verification");
    err=0;  CCMIOGetNode(&err, root, kMovedChildPath, &node);
    Test(err, TRUE, "CCMIOMoveNode hierarchy move verification");

    /* Test hierarchy structure */
    err = 0;  badVals = 0;
    CCMIOGetNode(&err, root, kHierarchyNodeName, &node);
    CCMIOGetNumberOfChildren(&err, node, &val);
    badVals += !(val == 1);	// Top-level hierarchy is only grandfather
    nth = 0;
    CCMIOGetNextChild(&err, node, &nth, &grandfather);
    CCMIOGetName(&err, grandfather, name);
    badVals += !InHierarchy(name, 0);
    CCMIOGetNumberOfChildren(&err, grandfather, &val);
    badVals += !HasRightChildren(name, grandfather);
    for (x = 0; CCMIOGetNextChild(NULL, grandfather, &x, &node) == kCCMIONoErr; ++x)
    {
	CCMIOGetName(&err, node, name);
	badVals += !InHierarchy(name, 1);
	badVals += !HasRightChildren(name, node);
    }
    Test(err, badVals == 0, "Hierarchy verification");
    
    err = CCMIOClose(root);
    Test(err, TRUE, "CCMIOCloseFile:  existing file");
}

void WriteTest( const char *filename )
{
    int x, y;
    CCMIONode root, node, aryParent, parent, son, grandson;
    CCMIOBuffer buff;
    CCMIOError err = kCCMIONoErr;

    err = CCMIOOpen(filename, kCCMIOWrite, &root);
    Test(err, TRUE, "CCMIOOpenFile");

    err=0; CCMIOSetVersion(&err, root, kVersion);
    Test(err, TRUE, "CCMIOSetVersion");
    err=0; CCMIOSetTitle(&err, root, kTitle);
    Test(err, TRUE, "CCMIOSetTitle");

    /* Write scalar values */
    err=0; CCMIOCreateNode(&err, root, FALSE, kI32ScalarName, kI32ScalarName,
			 &node);
    Test(err, TRUE, "CCMIOCreateNode(kI32ScalarName)");
    CCMIOSetDataType(&err, node, kCCMIOInt32, 1, kCCMIOEndArgs);
    Test(err, TRUE, "CCMIOSetDataType(kCCMIOInt32, 1)");
    CCMIOWriteData(&err, node, (void *)&kI32ScalarValue);
    Test(err, TRUE, "CCMIOWriteDataPoint(kI32ScalarValue, 1)");

    err=0; CCMIOCreateNode(&err, root, FALSE, kF64ScalarName, kF64ScalarName,
			 &node);
    Test(err, TRUE, "CCMIOCreateNode(kF64ScalarName)");
    CCMIOSetDataType(&err, node, kCCMIOFloat64, 1, kCCMIOEndArgs);
    Test(err, TRUE, "CCMIOSetDataType(kCCMIOFloat64, 1)");
    CCMIOWriteData(&err, node, (void *)&kF64ScalarValue);
    Test(err, TRUE, "CCMIOWriteDataPoint(kF64ScalarValue, 1)");

    /* Write array nodes */
    err=0; CCMIOCreateNode(&err, root, FALSE, kArrayNodeName, kArrayNodeName,
			 &aryParent);
    Test(err, TRUE, "CCMIOCreateNode(kArrayNodeName)");

    /* 1D array */
    err=0; CCMIOCreateNode(&err, aryParent, FALSE, k1DArrayNodeName,
			 k1DArrayNodeName, &node);
    CCMIOSetDataType(&err, node, kCCMIOInt32, k1DArraySize, kCCMIOEndArgs);
    for (x = 0;  x < k1DArraySize;  ++x)
	CCMIOWriteDataPoint(&err, node, (void *)&k1DArrayValue[x], x,
			    kCCMIOEndArgs);
    Test(err, TRUE, "Create 1D array");

    /* 2D array */
    err=0; CCMIOCreateNode(&err, aryParent, FALSE, k2DArrayNodeName,
			 k2DArrayNodeName, &node);
    CCMIOSetDataType(&err, node, kCCMIOInt32, k2DArraySize_Dim0,
		     k2DArraySize_Dim1, kCCMIOEndArgs);
    for (x = 0;  x < k2DArraySize_Dim0;  ++x)
    {
	for (y = 0;  y < k2DArraySize_Dim1;  ++y)
	    CCMIOWriteDataPoint(&err, node, (void *)&k2DArrayValue[x][y],
			      x, y, kCCMIOEndArgs);
    }
    Test(err, TRUE, "Create 2D array");

    /* 1D buffered array */
    err=0; CCMIOCreateNode(&err, root, FALSE, kBufferedArrayNodeName,
			 kBufferedArrayNodeName, &aryParent);
    CCMIOCreateNode(&err, aryParent, FALSE, k1DArrayNodeName, k1DArrayNodeName,
		  &node);
    CCMIOSetDataType(&err, node, kCCMIOInt32, k1DArraySize, kCCMIOEndArgs);
    CCMIOCreateBuffer(&err, node, kCCMIOWrite, &buff);
    for (x = 0;  x < k1DArraySize;  ++x)
	CCMIOBufferWriteDataPoint(&err, buff, (void *)&k1DArrayValue[x],
				  x, kCCMIOEndArgs);
    CCMIODestroyBuffer(&err, buff);
    Test(err, TRUE, "Create 1D buffered array");

    /* 2D buffered array */
    err=0; CCMIOCreateNode(&err, aryParent, FALSE, k2DArrayNodeName,
			 k2DArrayNodeName, &node);
    CCMIOSetDataType(&err, node, kCCMIOInt32, k2DArraySize_Dim0,
		     k2DArraySize_Dim1, kCCMIOEndArgs);
    CCMIOCreateBuffer(&err, node, kCCMIOWrite, &buff);
    for (x = 0;  x < k2DArraySize_Dim0;  ++x)
    {
	for (y = 0;  y < k2DArraySize_Dim1;  ++y)
	    CCMIOBufferWriteDataPoint(&err, buff, (void *)&k2DArrayValue[x][y],
				      x, y, kCCMIOEndArgs);
    }
    CCMIODestroyBuffer(&err, buff);
    Test(err, TRUE, "Create 2D buffered array");

    /* Create array nodes directly */
    err=0; CCMIOCreateNode(&err, root, FALSE, kDirectArrayNodeName,
			 kDirectArrayNodeName, &aryParent);
    CCMIOCreateNode(&err, aryParent, FALSE, k1DArrayNodeName,
		  k1DArrayNodeName, &node);
    CCMIOWrite1i(&err, node, k1DArraySize, k1DArrayValue, kCCMIOStart,
		 kCCMIOEnd);
    Test(err, TRUE, "Create 1D array (CCMIOWrite1i)");
    err=0; CCMIOCreateNode(&err, aryParent, FALSE, k2DArrayNodeName,
			 k2DArrayNodeName, &node);
    int **d2ary = (int **)malloc(k2DArraySize_Dim0 * sizeof(int *));
    d2ary[0] = (int *)malloc(k2DArraySize_Dim0 * k2DArraySize_Dim1*sizeof(int));
    for(x = 1; x < k2DArraySize_Dim0; x++)
	d2ary[x] = d2ary[0] + x * k2DArraySize_Dim1;
    for (x = 0;  x < k2DArraySize_Dim0;  ++x)
    {
	for (y = 0;  y < k2DArraySize_Dim1;  ++y)
	    d2ary[x][y] = k2DArrayValue[x][y];
    }
    CCMIOWrite2i(&err, node, x, y, *d2ary, kCCMIOStart, kCCMIOEnd);
    Test(err, TRUE, "Create 2D array (CCMIOWrite2i)");

    /* Create hierarchy test */
    err=0;
    CCMIOCreateNode(&err, root, FALSE, kHierarchyNodeName, NULL, &node);
    CCMIOCreateNode(&err, node, FALSE, kHierarchyValue[0][1], NULL, &parent);
    CCMIOCreateNode(&err, parent, FALSE, kHierarchyValue[1][0], NULL, &son);
    CCMIOCreateNode(&err, son, FALSE, kHierarchyValue[2][0], NULL, &grandson);
    CCMIOCreateNode(&err, grandson, FALSE, kHierarchyValue[3][0], NULL, NULL);
    CCMIOCreateNode(&err, parent, FALSE, kHierarchyValue[1][1], NULL, &son);
    CCMIOCreateNode(&err, son, FALSE, kHierarchyValue[2][1], NULL, NULL);
    CCMIOCreateNode(&err, son, FALSE, kHierarchyValue[1][2], NULL, &grandson);
    CCMIOCreateNode(&err, grandson, FALSE, kHierarchyValue[2][2], NULL, NULL);
    Test(err, TRUE, "Hierarchy creation (before move and delete)");
    CCMIOGetNode(&err, root, kDeletedNodePath, &node);
    err=0;  CCMIODeleteNode(&err, node);
    Test(err, TRUE, "CCMIODeleteNode");
    err=0;  CCMIOGetNode(&err, root, kMovedNodeOriginalPath, &node);
    CCMIOMoveNode(&err, node, parent);
    Test(err, TRUE, "CCMIOMoveNode");

    err = CCMIOClose(root);
    Test(err, TRUE, "CCMIOCloseFile");
}

int main(int argc, char *argv[])
{
    if (remove(kFilename) != 0)
    	{ printf("Could not remove '%s'!\n", kFilename);  return(1); }

    WriteTest(kFilename);
    ReadTest(kFilename);

    if (gErr != 1)
	printf("There were %d errors.", gErr);
    else
	printf("There was 1 error.");
    printf("  Tests ");
    if (!gErr)
	printf("passed");
    else
	printf("FAILED");
    printf(".\n");

    return(gErr);
}
