/*****************************************************************************

    TRAVIS - Trajectory Analyzer and Visualizer

    http://www.travis-analyzer.de/

    Copyright (c) 2009-2020 Martin Brehm
                  2012-2020 Martin Thomas
                  2016-2020 Sascha Gehrke

    Please cite:  J. Chem. Phys. 2020, 152 (16), 164105.         (DOI 10.1063/5.0005078 )
                  J. Chem. Inf. Model. 2011, 51 (8), 2007-2023.  (DOI 10.1021/ci200217w )

    This file was written by Martin Brehm.

    ---------------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*****************************************************************************/


// This must always be the first include directive
#include "config.h"


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <string>
#include <vector>
#include <algorithm>


#include "tools.h"
#include "2df.h"
#include "globalvar.h"



const char *GetRevisionInfo_cubetool(unsigned int len) {
	static char buf[256];
	GET_REVISION_INFO( buf, len );
	return buf;
}


const char *GetSourceVersion_cubetool() {
	static char buf[256];
	GET_SOURCE_VERSION( buf );
	return buf;
}



#define LEN_BOHR2ANGSTROM (0.52917721092)




class CAtomPos {
public:
	int m_iOrd;
	double m_fPos[3];
};



class CCube {
public:

	CCube() : m_pBin(NULL) {
		m_iRes[0] = 0;
		m_iRes[1] = 0;
		m_iRes[2] = 0;
		m_fStrideA[0] = 0;
		m_fStrideA[1] = 0;
		m_fStrideA[2] = 0;
		m_fStrideB[0] = 0;
		m_fStrideB[1] = 0;
		m_fStrideB[2] = 0;
		m_fStrideC[0] = 0;
		m_fStrideC[1] = 0;
		m_fStrideC[2] = 0;
		m_fCenter[0] = 0;
		m_fCenter[1] = 0;
		m_fCenter[2] = 0;
		m_iAtomCount = 0;
	}


	~CCube() {
		int z;
		if (m_pBin != NULL) {
			delete[] m_pBin;
			m_pBin = NULL;
		}
		for (z=0;z<(int)m_oaAtoms.size();z++)
			delete m_oaAtoms[z];
		m_oaAtoms.clear();
	}


	void CopyFrom( const CCube *cube ) {
		int z;
		CAtomPos *ap;
		if (m_pBin != NULL)
			delete[] m_pBin;
		for (z=0;z<(int)m_oaAtoms.size();z++)
			delete m_oaAtoms[z];
		m_oaAtoms.clear();
		m_iRes[0] = cube->m_iRes[0];
		m_iRes[1] = cube->m_iRes[1];
		m_iRes[2] = cube->m_iRes[2];
		m_fStrideA[0] = cube->m_fStrideA[0];
		m_fStrideA[1] = cube->m_fStrideA[1];
		m_fStrideA[2] = cube->m_fStrideA[2];
		m_fStrideB[0] = cube->m_fStrideB[0];
		m_fStrideB[1] = cube->m_fStrideB[1];
		m_fStrideB[2] = cube->m_fStrideB[2];
		m_fStrideC[0] = cube->m_fStrideC[0];
		m_fStrideC[1] = cube->m_fStrideC[1];
		m_fStrideC[2] = cube->m_fStrideC[2];
		m_fCenter[0] = cube->m_fCenter[0];
		m_fCenter[1] = cube->m_fCenter[1];
		m_fCenter[2] = cube->m_fCenter[2];
		m_iAtomCount = cube->m_iAtomCount;
		m_pBin = new double[m_iRes[0]*m_iRes[1]*m_iRes[2]];
		for (z=0;z<m_iRes[0]*m_iRes[1]*m_iRes[2];z++)
			m_pBin[z] = cube->m_pBin[z];
		for (z=0;z<(int)cube->m_oaAtoms.size();z++) {
			ap = new CAtomPos();
			ap->m_iOrd = cube->m_oaAtoms[z]->m_iOrd;
			ap->m_fPos[0] = cube->m_oaAtoms[z]->m_fPos[0];
			ap->m_fPos[1] = cube->m_oaAtoms[z]->m_fPos[1];
			ap->m_fPos[2] = cube->m_oaAtoms[z]->m_fPos[2];
			m_oaAtoms.push_back(ap);
		}
	}


	bool ReadCube(const char *s);

	bool WriteCube(const char *s);

	bool AddCube( CCube *tcu );

	bool SubtractCube( CCube *tcu );

	void Add( double f );

	void Multiply( double f );

	int m_iAtomCount;
	int m_iRes[3];

	std::vector<CAtomPos*> m_oaAtoms;

	double *m_pBin;

	double m_fCenter[3];

	double m_fStrideA[3];
	double m_fStrideB[3];
	double m_fStrideC[3];
};




CCube *g_pCubeWork;
CCube *g_pVectorField[3];
int g_iCutNormal;
int g_iCutSliceFrom;
int g_iCutSliceTo;
bool g_bShowAtoms;
double g_fAtomRadius;
double g_fVectorScale;
double g_fVectorHeadSize;
double g_fValueRangeMin, g_fValueRangeMax;
double g_fVectorRange;
bool g_bCustomValueRange;
int g_iVectorStride;
int g_iPlaneX1, g_iPlaneY1, g_iPlaneX2, g_iPlaneY2;





bool CCube::AddCube( CCube *tcu ) {

	int z;

	if ((tcu->m_iRes[0] != m_iRes[0]) || (tcu->m_iRes[1] != m_iRes[1]) || (tcu->m_iRes[2] != m_iRes[2])) {
		eprintf("CCube::AddCube(): Error: Resolution mismatch (%d x %d x %d vs. %d x %d x %d).\n",m_iRes[0],m_iRes[1],m_iRes[2],tcu->m_iRes[0],tcu->m_iRes[1],tcu->m_iRes[2]);
		return false;
	}

	for (z=0;z<m_iRes[0]*m_iRes[1]*m_iRes[2];z++)
		m_pBin[z] += tcu->m_pBin[z];

	return true;
}



bool CCube::SubtractCube( CCube *tcu ) {

	int z;

	if ((tcu->m_iRes[0] != m_iRes[0]) || (tcu->m_iRes[1] != m_iRes[1]) || (tcu->m_iRes[2] != m_iRes[2])) {
		eprintf("CCube::SubtractCube(): Error: Resolution mismatch (%d x %d x %d vs. %d x %d x %d).\n",m_iRes[0],m_iRes[1],m_iRes[2],tcu->m_iRes[0],tcu->m_iRes[1],tcu->m_iRes[2]);
		return false;
	}

	for (z=0;z<m_iRes[0]*m_iRes[1]*m_iRes[2];z++)
		m_pBin[z] -= tcu->m_pBin[z];

	return true;
}



void CCube::Add( double f ) {

	int z;

	for (z=0;z<m_iRes[0]*m_iRes[1]*m_iRes[2];z++)
		m_pBin[z] += f;
}



void CCube::Multiply( double f ) {

	int z;

	for (z=0;z<m_iRes[0]*m_iRes[1]*m_iRes[2];z++)
		m_pBin[z] *= f;
}



bool CCube::ReadCube(const char *s) {

	FILE *a;
	int z, ix, iy, iz;
	char buf[256], *p, *q;
	double tf;
	double mi, ma;
	CAtomPos *ap;


	mprintf(WHITE,"    ------------------------------------------------------\n");
	mprintf("        CCube::ReadCube(): Reading cube file \"%s\"...\n",s);
	a = fopen(s,"rt");
	if (a == NULL) {
		eprintf("CCube::ReadCube(): Error: Could not open \"%s\" for reading.\n",s);
		return false;
	}

	fgets(buf,256,a);
	p = &buf[strlen(buf)-1];
	while ((*p == '\r') || (*p == '\n'))
		p--;
	p++;
	*p = 0;
	mprintf("        Comment line 1:  \"%s\"\n",buf);
	fgets(buf,256,a);
	p = &buf[strlen(buf)-1];
	while ((*p == '\r') || (*p == '\n'))
		p--;
	p++;
	*p = 0;
	mprintf("        Comment line 2:  \"%s\"\n",buf);


	fgets(buf,256,a);
	p = buf;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_iAtomCount = atoi(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fCenter[0] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fCenter[1] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while ((*q != ' ') && (*q != '\r') && (*q != '\n') && (*q != 0))
		q++;
	*q = 0;
	m_fCenter[2] = atof(p);


	fgets(buf,256,a);
	p = buf;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_iRes[0] = atoi(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideA[0] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideA[1] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while ((*q != ' ') && (*q != '\r') && (*q != '\n') && (*q != 0))
		q++;
	*q = 0;
	m_fStrideA[2] = atof(p);


	fgets(buf,256,a);
	p = buf;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_iRes[1] = atoi(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideB[0] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideB[1] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while ((*q != ' ') && (*q != '\r') && (*q != '\n') && (*q != 0))
		q++;
	*q = 0;
	m_fStrideB[2] = atof(p);


	fgets(buf,256,a);
	p = buf;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_iRes[2] = atoi(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideC[0] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while (*q != ' ')
		q++;
	*q = 0;
	m_fStrideC[1] = atof(p);
	p = q+1;
	while (*p == ' ')
		p++;
	q = p;
	while ((*q != ' ') && (*q != '\r') && (*q != '\n') && (*q != 0))
		q++;
	*q = 0;
	m_fStrideC[2] = atof(p);

	for (z=0;z<3;z++) {
		m_fCenter[z]  *= LEN_BOHR2ANGSTROM;
		m_fStrideA[z] *= LEN_BOHR2ANGSTROM;
		m_fStrideB[z] *= LEN_BOHR2ANGSTROM;
		m_fStrideC[z] *= LEN_BOHR2ANGSTROM;
	}


	mprintf("        Resolution:  %d x %d x %d\n",m_iRes[0],m_iRes[1],m_iRes[2]);
	mprintf("        Center:      %10.6f  %10.6f  %10.6f\n",m_fCenter[0],m_fCenter[1],m_fCenter[2]);
	mprintf("        Stride A:    %10.6f  %10.6f  %10.6f\n",m_fStrideA[0],m_fStrideA[1],m_fStrideA[2]);
	mprintf("        Stride B:    %10.6f  %10.6f  %10.6f\n",m_fStrideB[0],m_fStrideB[1],m_fStrideB[2]);
	mprintf("        Stride C:    %10.6f  %10.6f  %10.6f\n",m_fStrideC[0],m_fStrideC[1],m_fStrideC[2]);
	mprintf("        Vector A:    %10.6f  %10.6f  %10.6f\n",m_fStrideA[0]*m_iRes[0],m_fStrideA[1]*m_iRes[0],m_fStrideA[2]*m_iRes[0]);
	mprintf("        Vector B:    %10.6f  %10.6f  %10.6f\n",m_fStrideB[0]*m_iRes[1],m_fStrideB[1]*m_iRes[1],m_fStrideB[2]*m_iRes[1]);
	mprintf("        Vector C:    %10.6f  %10.6f  %10.6f\n",m_fStrideC[0]*m_iRes[2],m_fStrideC[1]*m_iRes[2],m_fStrideC[2]*m_iRes[2]);
	mprintf("        Atom count:  %d\n",m_iAtomCount);

	mprintf("        Reading atom positions...\n");

	for (z=0;z<m_iAtomCount;z++) {

		ap = new CAtomPos();

		fgets(buf,256,a);
		p = buf;
		while (*p == ' ')
			p++;
		q = p;
		while (*q != ' ')
			q++;
		*q = 0;
		ap->m_iOrd = atoi(p);
		p = q+1;
		while (*p == ' ')
			p++;
		q = p;
		while (*q != ' ')
			q++;
		*q = 0;
		p = q+1;
		while (*p == ' ')
			p++;
		q = p;
		while (*q != ' ')
			q++;
		*q = 0;
		ap->m_fPos[0] = atof(p) * LEN_BOHR2ANGSTROM;
		p = q+1;
		while (*p == ' ')
			p++;
		q = p;
		while (*q != ' ')
			q++;
		*q = 0;
		ap->m_fPos[1] = atof(p) * LEN_BOHR2ANGSTROM;
		p = q+1;
		while (*p == ' ')
			p++;
		q = p;
		while ((*q != ' ') && (*q != '\r') && (*q != '\n') && (*q != 0))
			q++;
		*q = 0;
		ap->m_fPos[2] = atof(p) * LEN_BOHR2ANGSTROM;

		m_oaAtoms.push_back(ap);
	}

	if (feof(a)) {
		eprintf("CCube::ReadCube(): Error: Unexpected end of cube file.\n");
		return false;
	}



	m_pBin = new double[m_iRes[0]*m_iRes[1]*m_iRes[2]];

	mprintf("        Reading volumetric data...\n");
	mprintf(WHITE,"          [");

	ix = 0;
	iy = 0;
	iz = 0;

	mi = 1.0e30;
	ma = -1.0e30;

	while (!feof(a)) {
_read:
		fgets(buf,256,a);
		if (feof(a))
			break;
		p = buf;

_next:
		while (*p == ' ')
			p++;
		q = p;
		while ((*p != ' ') && (*p != '\n') && (*p != 0))
			p++;
		if ((p-q) < 5)
			goto _read;

		*p = 0;

		#ifdef FAST_ATOF
			tf = fast_atof(q);
		#else
			tf = atof(q);
		#endif

		if (tf < mi)
			mi = tf;
		if (tf > ma)
			ma = tf;

		m_pBin[iz*m_iRes[0]*m_iRes[1]+iy*m_iRes[0]+ix] = tf;

		iz++;
		if (iz >= m_iRes[2]) {
			iz = 0;
			iy++;
			if (iy >= m_iRes[1]) {
				iy = 0;
				ix++;
				if (fmod(ix,m_iRes[0]/60.0) < 1.0)
					mprintf(WHITE,"#");
			}
		}

		if (ix == m_iRes[0])
			break;

		p++;
		goto _next;
	}

	if (feof(a)) {
		eprintf("CCube::ReadCube(): Error: Unexpected end of cube file stream.\n");
		return false;
	}

	mprintf(WHITE,"]\n");

	mprintf("        Finished reading cube file.\n");
	mprintf("        The value range is %.6f .. %.6f\n",mi,ma);
	mprintf(WHITE,"    ------------------------------------------------------\n");

	return true;
}



bool CCube::WriteCube(const char *s) {

	FILE *a;
	int z, ix, iy, iz;
	double tfs;


	a = fopen(s,"wt");
	if (a == NULL) {
		eprintf("CCube::WriteCube(): Error: Could not open \"%s\" for writing.\n",s);
		return false;
	}

	fprintf(a,"Gaussian Cube File\n");
	fprintf(a,"Written by CubeTool, (c) Martin Brehm, 2020\n");


	fprintf(
		a,
		"%10lu  %10.6f  %10.6f  %10.6f\n",
		m_oaAtoms.size(),
		m_fCenter[0] / LEN_BOHR2ANGSTROM,
		m_fCenter[1] / LEN_BOHR2ANGSTROM,
		m_fCenter[2] / LEN_BOHR2ANGSTROM
	);

	fprintf(
		a,
		"%10d  %10.6f  %10.6f  %10.6f\n",
		m_iRes[0],
		m_fStrideA[0] / LEN_BOHR2ANGSTROM,
		m_fStrideA[1] / LEN_BOHR2ANGSTROM,
		m_fStrideA[2] / LEN_BOHR2ANGSTROM
	);

	fprintf(
		a,
		"%10d  %10.6f  %10.6f  %10.6f\n",
		m_iRes[1],
		m_fStrideB[0] / LEN_BOHR2ANGSTROM,
		m_fStrideB[1] / LEN_BOHR2ANGSTROM,
		m_fStrideB[2] / LEN_BOHR2ANGSTROM
	);

	fprintf(
		a,
		"%10d  %10.6f  %10.6f  %10.6f\n",
		m_iRes[2],
		m_fStrideC[0] / LEN_BOHR2ANGSTROM,
		m_fStrideC[1] / LEN_BOHR2ANGSTROM,
		m_fStrideC[2] / LEN_BOHR2ANGSTROM
	);


	for (z=0;z<(int)m_oaAtoms.size();z++)
		fprintf(
			a,
			"%10d  0.00  %10.6f  %10.6f  %10.6f\n",
			m_oaAtoms[z]->m_iOrd,
			m_oaAtoms[z]->m_fPos[0] / LEN_BOHR2ANGSTROM,
			m_oaAtoms[z]->m_fPos[1] / LEN_BOHR2ANGSTROM,
			m_oaAtoms[z]->m_fPos[2] / LEN_BOHR2ANGSTROM
		);


	mprintf(WHITE,"      [");

	tfs = (m_iRes[0]*m_iRes[1]*m_iRes[2])/60.0;
	z = 0;
	for (ix=0;ix<m_iRes[0];ix++) {
		for (iy=0;iy<m_iRes[1];iy++) {
			for (iz=0;iz<m_iRes[2];iz++) {
				fprintf(
					a,
					"  %14.6E",
					m_pBin[iz*m_iRes[0]*m_iRes[1]+iy*m_iRes[0]+ix]
				);
				z++;
				if ((z%6) == 0)
					fprintf(a,"\n");
				if (fmod(z,tfs) < 1.0)
					mprintf(WHITE,"#");
			}
		}
	}

	mprintf(WHITE,"] Done.\n");

	return true;
}



bool SORT_AtomsX(CAtomPos *a1, CAtomPos *a2) {
	return (a1->m_fPos[0] > a2->m_fPos[0]);
}



bool SORT_AtomsY(CAtomPos *a1, CAtomPos *a2) {
	return (a1->m_fPos[1] > a2->m_fPos[1]);
}



bool SORT_AtomsZ(CAtomPos *a1, CAtomPos *a2) {
	return (a1->m_fPos[2] > a2->m_fPos[2]);
}



bool WriteSlice( CCube *voldat, int normal, int slice1, int slice2, const char *s ) {

	C2DF *df;
	int z, z2, ix, iy, iz;
	double rad, cr, cg, cb, px, py, fac, mi, ma;


	mprintf(WHITE,"    ------------------------------------------------------\n");
	mprintf("      WriteSlice(): Creating cut plane.\n");
	df = new C2DF();

	mi = 1.0e30;
	ma = -1.0e30;

	switch( normal ) {
		case 1: // X
			if (g_iPlaneX1 == -1) {
				g_iPlaneX1 = 0;
				g_iPlaneY1 = 0;
				g_iPlaneX2 = voldat->m_iRes[1] - 1;
				g_iPlaneY2 = voldat->m_iRes[2] - 1;
			}
			df->m_iRes[0] = g_iPlaneX2 - g_iPlaneX1 + 1;
			df->m_iRes[1] = g_iPlaneY2 - g_iPlaneY1 + 1;
			df->m_fMinVal[0] = floor(100.0*voldat->m_fStrideB[1]*(g_iPlaneX1-voldat->m_iRes[1]/2)+0.5);
			df->m_fMaxVal[0] = floor(100.0*voldat->m_fStrideB[1]*(g_iPlaneX2-voldat->m_iRes[1]/2)+0.5);
			df->m_fMinVal[1] = floor(100.0*voldat->m_fStrideC[2]*(g_iPlaneY1-voldat->m_iRes[2]/2)+0.5);
			df->m_fMaxVal[1] = floor(100.0*voldat->m_fStrideC[2]*(g_iPlaneY2-voldat->m_iRes[2]/2)+0.5);
			df->SetLabelX("Y Position / pm");
			df->SetLabelY("Z Position / pm");
			df->SetLabelZ("Value");
			df->Create();
			for (ix=slice1;ix<=slice2;ix++)
				for (iy=g_iPlaneX1;iy<=g_iPlaneX2;iy++)
					for (iz=g_iPlaneY1;iz<=g_iPlaneY2;iz++)
						df->m_pBin[(iz-g_iPlaneY1)*df->m_iRes[0]+iy-g_iPlaneX1] += voldat->m_pBin[iz*voldat->m_iRes[0]*voldat->m_iRes[1]+iy*voldat->m_iRes[0]+ix];
			for (z=0;z<df->m_iRes[0]*df->m_iRes[1];z++) {
			 	df->m_pBin[z] /= (double)(slice2-slice1+1);
				if (df->m_pBin[z] < mi)
					mi = df->m_pBin[z];
				if (df->m_pBin[z] > ma)
					ma = df->m_pBin[z];
			}
			break;

		case 2: // Y
			if (g_iPlaneX1 == -1) {
				g_iPlaneX1 = 0;
				g_iPlaneY1 = 0;
				g_iPlaneX2 = voldat->m_iRes[0] - 1;
				g_iPlaneY2 = voldat->m_iRes[2] - 1;
			}
			df->m_iRes[0] = g_iPlaneX2 - g_iPlaneX1 + 1;
			df->m_iRes[1] = g_iPlaneY2 - g_iPlaneY1 + 1;
			df->m_fMinVal[0] = floor(100.0*voldat->m_fStrideA[0]*(g_iPlaneX1-voldat->m_iRes[0]/2)+0.5);
			df->m_fMaxVal[0] = floor(100.0*voldat->m_fStrideA[0]*(g_iPlaneX2-voldat->m_iRes[0]/2)+0.5);
			df->m_fMinVal[1] = floor(100.0*voldat->m_fStrideC[2]*(g_iPlaneY1-voldat->m_iRes[2]/2)+0.5);
			df->m_fMaxVal[1] = floor(100.0*voldat->m_fStrideC[2]*(g_iPlaneY2-voldat->m_iRes[2]/2)+0.5);
			df->SetLabelX("X Position / pm");
			df->SetLabelY("Z Position / pm");
			df->SetLabelZ("Value");
			df->Create();
			for (iy=slice1;iy<=slice2;iy++)
				for (ix=g_iPlaneX1;ix<=g_iPlaneX2;ix++)
					for (iz=g_iPlaneY1;iz<=g_iPlaneY2;iz++)
						df->m_pBin[(iz-g_iPlaneY1)*df->m_iRes[0]+ix-g_iPlaneX1] += voldat->m_pBin[iz*voldat->m_iRes[0]*voldat->m_iRes[1]+iy*voldat->m_iRes[0]+ix];
			for (z=0;z<df->m_iRes[0]*df->m_iRes[1];z++) {
			 	df->m_pBin[z] /= (double)(slice2-slice1+1);
				if (df->m_pBin[z] < mi)
					mi = df->m_pBin[z];
				if (df->m_pBin[z] > ma)
					ma = df->m_pBin[z];
			}
			break;

		case 3: // Z
			if (g_iPlaneX1 == -1) {
				g_iPlaneX1 = 0;
				g_iPlaneY1 = 0;
				g_iPlaneX2 = voldat->m_iRes[0] - 1;
				g_iPlaneY2 = voldat->m_iRes[1] - 1;
			}
			df->m_iRes[0] = g_iPlaneX2 - g_iPlaneX1 + 1;
			df->m_iRes[1] = g_iPlaneY2 - g_iPlaneY1 + 1;
			df->m_fMinVal[0] = floor(100.0*voldat->m_fStrideA[0]*(g_iPlaneX1-voldat->m_iRes[0]/2)+0.5);
			df->m_fMaxVal[0] = floor(100.0*voldat->m_fStrideA[0]*(g_iPlaneX2-(voldat->m_iRes[0]-1)/2)+0.5);
			df->m_fMinVal[1] = floor(100.0*voldat->m_fStrideB[1]*(g_iPlaneY1-voldat->m_iRes[1]/2)+0.5);
			df->m_fMaxVal[1] = floor(100.0*voldat->m_fStrideB[1]*(g_iPlaneY2-(voldat->m_iRes[1]-1)/2)+0.5);
			df->SetLabelX("X Position / pm");
			df->SetLabelY("Y Position / pm");
			df->SetLabelZ("Value");
			df->Create();
			for (iz=slice1;iz<=slice2;iz++)
				for (ix=g_iPlaneX1;ix<=g_iPlaneX2;ix++)
					for (iy=g_iPlaneY1;iy<=g_iPlaneY2;iy++)
						df->m_pBin[(iy-g_iPlaneY1)*df->m_iRes[0]+ix-g_iPlaneX1] += voldat->m_pBin[iz*voldat->m_iRes[0]*voldat->m_iRes[1]+iy*voldat->m_iRes[0]+ix];
			for (z=0;z<df->m_iRes[0]*df->m_iRes[1];z++) {
			 	df->m_pBin[z] /= (double)(slice2-slice1+1);
				if (df->m_pBin[z] < mi)
					mi = df->m_pBin[z];
				if (df->m_pBin[z] > ma)
					ma = df->m_pBin[z];
			}
			break;

		default:
			eprintf("WriteSlice): Error: Unknown normal setting %d.\n",normal);
			return false;
	}

	mprintf("      The value range in the cut plane is %.6f .. %.6f\n",mi,ma);


	if (g_bShowAtoms) {

		switch( normal ) {
			case 1: // X
				std::stable_sort( voldat->m_oaAtoms.begin(), voldat->m_oaAtoms.end(), SORT_AtomsX );
				break;
			case 2: // Y
				std::stable_sort( voldat->m_oaAtoms.begin(), voldat->m_oaAtoms.end(), SORT_AtomsY );
				break;
			case 3: // Z
				std::stable_sort( voldat->m_oaAtoms.begin(), voldat->m_oaAtoms.end(), SORT_AtomsZ );
				break;
		}


		for (z=0;z<(int)voldat->m_oaAtoms.size();z++) {

			px = 0;
			py = 0;

			switch( normal ) {
				case 1: // X
					px = (voldat->m_oaAtoms[z]->m_fPos[1] - voldat->m_fCenter[1]) * 100.0 - voldat->m_fStrideB[1]*voldat->m_iRes[1]*50.0;
					py = (voldat->m_oaAtoms[z]->m_fPos[2] - voldat->m_fCenter[2]) * 100.0 - voldat->m_fStrideC[2]*voldat->m_iRes[2]*50.0;
					break;
				case 2: // Y
					px = (voldat->m_oaAtoms[z]->m_fPos[0] - voldat->m_fCenter[0]) * 100.0 - voldat->m_fStrideA[0]*voldat->m_iRes[0]*50.0;
					py = (voldat->m_oaAtoms[z]->m_fPos[2] - voldat->m_fCenter[2]) * 100.0 - voldat->m_fStrideC[2]*voldat->m_iRes[2]*50.0;
					break;
				case 3: // Z
					px = (voldat->m_oaAtoms[z]->m_fPos[0] - voldat->m_fCenter[0]) * 100.0 - voldat->m_fStrideA[0]*voldat->m_iRes[0]*50.0;
					py = (voldat->m_oaAtoms[z]->m_fPos[1] - voldat->m_fCenter[1]) * 100.0 - voldat->m_fStrideB[1]*voldat->m_iRes[1]*50.0;
					break;
			}

			for (z2=0;z2<g_oaElements.GetSize();z2++)
				if (((voldat->m_oaAtoms[z]->m_iOrd == 1) && (strcmp(((CElement*)g_oaElements[z2])->m_sLabel,"H") == 0)) ||
					((voldat->m_oaAtoms[z]->m_iOrd > 1) && (((CElement*)g_oaElements[z2])->m_iOrd == voldat->m_oaAtoms[z]->m_iOrd))) {
					rad = ((CElement*)g_oaElements[z2])->m_fRadius * 0.6;
					cr = ((CElement*)g_oaElements[z2])->m_iColorR / 255.0;
					cg = ((CElement*)g_oaElements[z2])->m_iColorG / 255.0;
					cb = ((CElement*)g_oaElements[z2])->m_iColorB / 255.0;
					goto _elfound;
				}

			rad = 25.0;
			cr = 1.0;
			cg = 0.0;
			cb = 1.0;

_elfound:
			if (((px - rad*g_fAtomRadius) < df->m_fMinVal[0]) || ((px + rad*g_fAtomRadius) > df->m_fMaxVal[0]) || ((py - rad*g_fAtomRadius) < df->m_fMinVal[1]) || ((py + rad*g_fAtomRadius) > df->m_fMaxVal[1]))
				continue;

			df->AddCircle(
				px,
				py,
				rad * g_fAtomRadius,
				cr,
				cg,
				cb
			);
		}
	}


	if (g_pVectorField[0] != NULL) {

		df->m_bVectorField = true;

		mi = -1.0e30;
		ma = -1.0e30;

		switch( normal ) {

			case 1: // X
				df->m_iVectorRes[0] = df->m_iRes[0] / g_iVectorStride;
				df->m_iVectorRes[1] = df->m_iRes[1] / g_iVectorStride;
				df->m_faVectorData.resize(df->m_iVectorRes[0]*df->m_iVectorRes[1]*2);
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					df->m_faVectorData[z] = 0;
				for (ix=slice1;ix<=slice2;ix++) {
					for (iy=0;iy<df->m_iVectorRes[0];iy++) {
						for (iz=0;iz<df->m_iVectorRes[1];iz++) {
							df->m_faVectorData[(iz*df->m_iVectorRes[0]+iy)*2]   += g_pVectorField[1]->m_pBin[(iz*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]*voldat->m_iRes[1]+(iy*g_iVectorStride+g_iPlaneX1)*voldat->m_iRes[0]+ix];
							df->m_faVectorData[(iz*df->m_iVectorRes[0]+iy)*2+1] += g_pVectorField[2]->m_pBin[(iz*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]*voldat->m_iRes[1]+(iy*g_iVectorStride+g_iPlaneX1)*voldat->m_iRes[0]+ix];
						}
					}
				}
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					if (df->m_faVectorData[z] > ma)
						ma = df->m_faVectorData[z];
				if (g_fVectorRange > 0)
					fac = g_fVectorScale * 0.5 / g_fVectorRange;
				else
					fac = g_fVectorScale * 0.5 / ma;
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++) {
					df->m_faVectorData[z] *= fac;
					if (df->m_faVectorData[z] > mi)
						mi = df->m_faVectorData[z];
				}
				break;

			case 2: // Y
				df->m_iVectorRes[0] = df->m_iRes[0] / g_iVectorStride;
				df->m_iVectorRes[1] = df->m_iRes[1] / g_iVectorStride;
				df->m_faVectorData.resize(df->m_iVectorRes[0]*df->m_iVectorRes[1]*2);
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					df->m_faVectorData[z] = 0;
				for (iy=slice1;iy<=slice2;iy++) {
					for (ix=0;ix<df->m_iVectorRes[0];ix++) {
						for (iz=0;iz<df->m_iVectorRes[1];iz++) {
							df->m_faVectorData[(iz*df->m_iVectorRes[0]+ix)*2]   += g_pVectorField[0]->m_pBin[(iz*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]*voldat->m_iRes[1]+iy*voldat->m_iRes[0]+ix*g_iVectorStride+g_iPlaneX1];
							df->m_faVectorData[(iz*df->m_iVectorRes[0]+ix)*2+1] += g_pVectorField[2]->m_pBin[(iz*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]*voldat->m_iRes[1]+iy*voldat->m_iRes[0]+ix*g_iVectorStride+g_iPlaneX1];
						}
					}
				}
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					if (df->m_faVectorData[z] > ma)
						ma = df->m_faVectorData[z];
				if (g_fVectorRange > 0)
					fac = g_fVectorScale * 0.5 / g_fVectorRange;
				else
					fac = g_fVectorScale * 0.5 / ma;
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++) {
					df->m_faVectorData[z] *= fac;
					if (df->m_faVectorData[z] > mi)
						mi = df->m_faVectorData[z];
				}
				break;

			case 3: // Z
				df->m_iVectorRes[0] = df->m_iRes[0] / g_iVectorStride;
				df->m_iVectorRes[1] = df->m_iRes[1] / g_iVectorStride;
				df->m_faVectorData.resize(df->m_iVectorRes[0]*df->m_iVectorRes[1]*2);
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					df->m_faVectorData[z] = 0;
				for (iz=slice1;iz<=slice2;iz++) {
					for (ix=0;ix<df->m_iVectorRes[0];ix++) {
						for (iy=0;iy<df->m_iVectorRes[1];iy++) {
							df->m_faVectorData[(iy*df->m_iVectorRes[0]+ix)*2]   += g_pVectorField[0]->m_pBin[iz*voldat->m_iRes[0]*voldat->m_iRes[1]+(iy*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]+ix*g_iVectorStride+g_iPlaneX1];
							df->m_faVectorData[(iy*df->m_iVectorRes[0]+ix)*2+1] += g_pVectorField[1]->m_pBin[iz*voldat->m_iRes[0]*voldat->m_iRes[1]+(iy*g_iVectorStride+g_iPlaneY1)*voldat->m_iRes[0]+ix*g_iVectorStride+g_iPlaneX1];
						}
					}
				}
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++)
					if (df->m_faVectorData[z] > ma)
						ma = df->m_faVectorData[z];
				if (g_fVectorRange > 0)
					fac = g_fVectorScale * 0.5 / g_fVectorRange;
				else
					fac = g_fVectorScale * 0.5 / ma;
				for (z=0;z<df->m_iVectorRes[0]*df->m_iVectorRes[1]*2;z++) {
					df->m_faVectorData[z] *= fac;
					if (df->m_faVectorData[z] > mi)
						mi = df->m_faVectorData[z];
				}
				break;
		}

		mprintf("      The maximum projected vector length in the cut plane is %.6f\n",ma);
		mprintf("      The resulting maximum vector length after rescaling is %.6f\n",mi);

		df->m_fVectorHeadSize = g_fVectorHeadSize * 20.0;
	}

	mprintf("      Writing Gnuplot input data...\n");
	df->WriteGnuplotInput("",s,"",false);

	mprintf("      Writing Mathematica notebook...\n");
	df->WriteMathematicaNb("",s,".nb",false);

	delete df;

	mprintf("      WriteSlice(): Creating cut plane done.\n");
	mprintf(WHITE,"    ------------------------------------------------------\n");

	return true;
}



void CreateAbs() {

	int z;

	if (g_pCubeWork != NULL)
		delete g_pCubeWork;

	g_pCubeWork = new CCube();
	g_pCubeWork->CopyFrom( g_pVectorField[0] );

	for (z=0;z<g_pCubeWork->m_iRes[0]*g_pCubeWork->m_iRes[1]*g_pCubeWork->m_iRes[2];z++)
		g_pCubeWork->m_pBin[z] = sqrt( SQR(g_pVectorField[0]->m_pBin[z]) + SQR(g_pVectorField[1]->m_pBin[z]) + SQR(g_pVectorField[2]->m_pBin[z]) );
}



void Usage() {

	mprintf("\n");
	mprintf(WHITE,"Command Line Usage:\n\n");

	mprintf("    -readcube <input.cube>             Reads a cube file into the working buffer.\n");
	mprintf("    -addcube <input.cube>              Adds a cube file to the working buffer.\n");
	mprintf("    -subtractcube <input.cube>         Subtracts a cube file from the working buffer.\n");
	mprintf("    -writecube <output.cube>           Writes the contents of the working buffer to a cube file.\n");

	mprintf("\n");
	mprintf("    -vectorfield <vx> <vy> <vz>        Reads three cube files with x, y, and z component of a vector field.\n");
	mprintf("    -vectorabs                         Fills the working buffer with the absolute values of the vector field.\n");

	mprintf("\n");
	mprintf("    -add <const>                       Adds a constant to the volumetric data in the working buffer.\n");
	mprintf("    -multiply <const>                  Multiplies the volumetric data in the working buffer with a constant.\n");

	mprintf("\n");
	mprintf("    -normal{x|y|z}                     Defines a cut plane with normal vector along x / y / z direction.\n");
	mprintf("    -planesection <x1> <x2> <y1> <y2>  Uses only the given range of points from the volumetric data for the cut plane.\n");
	mprintf("    -valuerange <min> <max>            Use the specified value range for the cut plane plot (automatically determined by default).\n");
	mprintf("    -slice <n>                         Takes the n-th slice along the normal vector from the CUBE file.\n");
	mprintf("    -slicerange <n1> <n2>              Averages from n1-th to n2-th slice along the normal vector.\n");
	mprintf("    -showatoms <r>                     Displays the atoms in the cut plane plot with radius r (default 1.0).\n");
	mprintf("    -vectorrange <max>                 Use the specified max. vector length for the cut plane plot (automatically determined by default).\n");
	mprintf("    -vectorscale <f>                   Scales vectors of the vector field with factor f for the cut plane (default 1.0).\n");
	mprintf("    -vectorstride <n>                  Shows only every n-th vector of the vector field in the cut plane (default 1).\n");
	mprintf("    -vectorhead <f>                    Scales the head size of the vector arrows in the cut plane (default 1.0).\n");
	mprintf("    -writeplane <output>               Writes the contents of the previously defined cut plane.\n");

	mprintf("\n");
}



int cubetool_main( int argc, const char *argv[] ) {

	int z, z2, ti, ti2;
	CCube *tcu;
	double tf, tf2;


	mprintf("\n");
	mprintf(WHITE,"    ##########################################\n");
	mprintf(WHITE,"    ####                                  ####\n");
	mprintf(WHITE,"    ####         *** CubeTool ***         ####\n");
	mprintf(WHITE,"    ####      (c) Martin Brehm, 2020      ####\n");
	mprintf(WHITE,"    ####    https://brehm-research.de/    ####\n");
	mprintf(WHITE,"    ####                                  ####\n");
	mprintf(WHITE,"    ##########################################\n");
	mprintf("\n");

	mprintf("    A tool to manipulate and process Gaussian Cube files.\n\n");

	if (argc < 2) {

		Usage();
		goto _ende;
	}


	g_pCubeWork = NULL;
	g_iCutNormal = -1;
	g_iCutSliceFrom = -1;
	g_iCutSliceTo = -1;
	g_pVectorField[0] = NULL;
	g_pVectorField[1] = NULL;
	g_pVectorField[2] = NULL;
	tcu = NULL;
	g_bShowAtoms = false;
	g_fAtomRadius = 1.0;
	g_fVectorScale = 1.0;
	g_fVectorHeadSize = 1.0;
	g_iVectorStride = 1;
	g_iPlaneX1 = -1;
	g_iPlaneY1 = -1;
	g_iPlaneX2 = -1;
	g_iPlaneY2 = -1;
	g_fValueRangeMin = 0;
	g_fValueRangeMax = 0;;
	g_fVectorRange = -1.0;
	g_bCustomValueRange = false;


	for (z=1;z<argc;z++) {

		mprintf(YELLOW,"\nNext command line argument: \"%s\"\n",argv[z]);

		/*****************************************************************************************************************/
		if (strcmp(argv[z],"-readcube") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			mprintf("    Trying to read cube file \"%s\" ...\n",argv[z+1]);
			g_pCubeWork = new CCube();
			if (!g_pCubeWork->ReadCube( argv[z+1] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-addcube") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				mprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			mprintf("    Trying to read cube file \"%s\" ...\n",argv[z+1]);
			tcu = new CCube();
			if (!tcu->ReadCube( argv[z+1] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}
			mprintf("    Adding volumetric data to the working buffer...\n");
			if (!g_pCubeWork->AddCube( tcu )) {
				eprintf("Failed.\n");
				goto _ende;
			}
			delete tcu;
			tcu = NULL;

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-subtractcube") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			mprintf("    Trying to read cube file \"%s\" ...\n",argv[z+1]);
			tcu = new CCube();
			if (!tcu->ReadCube( argv[z+1] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}
			mprintf("    Subtracting volumetric data from the working buffer...\n");
			if (!g_pCubeWork->SubtractCube( tcu )) {
				eprintf("Failed.\n");
				goto _ende;
			}
			delete tcu;
			tcu = NULL;

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-add") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			tf = atof(argv[z+1]);

			mprintf("    Adding constant %f to working buffer...\n",tf);
			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			g_pCubeWork->Add( tf );

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-multiply") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			tf = atof(argv[z+1]);

			mprintf("    Multiplying working buffer with constant %f ...\n",tf);
			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			g_pCubeWork->Multiply( tf );

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-writecube") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			mprintf("    Writing working buffer to cube file \"%s\" ...\n",argv[z+1]);
			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			g_pCubeWork->WriteCube( argv[z+1] );

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorfield") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires three additional arguments.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+2]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+3]);

			mprintf("    Trying to read X component of vector field from cube file \"%s\" ...\n",argv[z+1]);
			g_pVectorField[0] = new CCube();
			if (!g_pVectorField[0]->ReadCube( argv[z+1] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}

			mprintf("    Trying to read Y component of vector field from cube file \"%s\" ...\n",argv[z+2]);
			g_pVectorField[1] = new CCube();
			if (!g_pVectorField[1]->ReadCube( argv[z+2] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}

			mprintf("    Trying to read Z component of vector field from cube file \"%s\" ...\n",argv[z+3]);
			g_pVectorField[2] = new CCube();
			if (!g_pVectorField[2]->ReadCube( argv[z+3] )) {
				eprintf("Reading failed.\n");
				goto _ende;
			}

			tf = 0;
			for (z2=0;z2<g_pVectorField[0]->m_iRes[0]*g_pVectorField[0]->m_iRes[1]*g_pVectorField[0]->m_iRes[2];z2++) {
				tf2 = SQR(g_pVectorField[0]->m_pBin[z2]) + SQR(g_pVectorField[1]->m_pBin[z2]) + SQR(g_pVectorField[2]->m_pBin[z2]);
				if (tf2 > tf)
					tf = tf2;
			}
			tf = sqrt(tf);

			mprintf("    The maximum vector length in the vector field is %.6f\n",tf);

			z += 3;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorabs") == 0) {
		/*****************************************************************************************************************/

			if (g_pVectorField[0] == NULL) {
				eprintf("Error: No vector field loaded.\n");
				goto _ende;
			}
			mprintf("    Creating working buffer from absolute value of vector field...\n");
			CreateAbs();

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-normalx") == 0) {
		/*****************************************************************************************************************/

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			mprintf("    Defining cut plane normal to X direction.\n");
			g_iCutNormal = 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-normaly") == 0) {
		/*****************************************************************************************************************/

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			mprintf("    Defining cut plane normal to Y direction.\n");
			g_iCutNormal = 2;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-normalz") == 0) {
		/*****************************************************************************************************************/

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}
			mprintf("    Defining cut plane normal to Z direction.\n");
			g_iCutNormal = 3;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-planesection") == 0) {
		/*****************************************************************************************************************/

			if (z+4 >= argc) {
				eprintf("    Error: This command requires four additional arguments.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+2]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+3]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+4]);

			ti = atoi(argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			g_iPlaneX1 = atoi(argv[z+1]);
			g_iPlaneX2 = atoi(argv[z+2]);
			g_iPlaneY1 = atoi(argv[z+3]);
			g_iPlaneY2 = atoi(argv[z+4]);

			switch(g_iCutNormal) {

				case 1: // X
					if ((g_iPlaneX1	< 0) || (g_iPlaneX1 >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: X1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[1],g_iPlaneX1);
						goto _ende;
					}
					if ((g_iPlaneY1	< 0) || (g_iPlaneY1 >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Y1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[2],g_iPlaneY1);
						goto _ende;
					}
					if ((g_iPlaneX2	< 0) || (g_iPlaneX2 >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: X2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[1],g_iPlaneX2);
						goto _ende;
					}
					if ((g_iPlaneY2	< 0) || (g_iPlaneY2 >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Y2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[2],g_iPlaneY2);
						goto _ende;
					}
					if (g_iPlaneX1 >= g_iPlaneX2) {
						eprintf("Error: X1 >= X2.\n");
						goto _ende;
					}
					if (g_iPlaneY1 >= g_iPlaneY2) {
						eprintf("Error: Y1 >= Y2.\n");
						goto _ende;
					}
					break;

				case 2: // Y
					if ((g_iPlaneX1	< 0) || (g_iPlaneX1 >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: X1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[0],g_iPlaneX1);
						goto _ende;
					}
					if ((g_iPlaneY1	< 0) || (g_iPlaneY1 >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Y1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[2],g_iPlaneY1);
						goto _ende;
					}
					if ((g_iPlaneX2	< 0) || (g_iPlaneX2 >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: X2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[0],g_iPlaneX2);
						goto _ende;
					}
					if ((g_iPlaneY2	< 0) || (g_iPlaneY2 >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Y2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[2],g_iPlaneY2);
						goto _ende;
					}
					if (g_iPlaneX1 >= g_iPlaneX2) {
						eprintf("Error: X1 >= X2.\n");
						goto _ende;
					}
					if (g_iPlaneY1 >= g_iPlaneY2) {
						eprintf("Error: Y1 >= Y2.\n");
						goto _ende;
					}
					break;

				case 3: // Z
					if ((g_iPlaneX1	< 0) || (g_iPlaneX1 >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: X1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[0],g_iPlaneX1);
						goto _ende;
					}
					if ((g_iPlaneY1	< 0) || (g_iPlaneY1 >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: Y1 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[1],g_iPlaneY1);
						goto _ende;
					}
					if ((g_iPlaneX2	< 0) || (g_iPlaneX2 >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: X2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[0],g_iPlaneX2);
						goto _ende;
					}
					if ((g_iPlaneY2	< 0) || (g_iPlaneY2 >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: Y2 argument out of range (allowed 0 .. %d, found %d).\n",g_pCubeWork->m_iRes[1],g_iPlaneY2);
						goto _ende;
					}
					if (g_iPlaneX1 >= g_iPlaneX2) {
						eprintf("Error: X1 >= X2.\n");
						goto _ende;
					}
					if (g_iPlaneY1 >= g_iPlaneY2) {
						eprintf("Error: Y1 >= Y2.\n");
						goto _ende;
					}
					break;
			}

			printf("    Using { %d .. %d } x { %d .. %d } as the section of the cut plane.\n",g_iPlaneX1,g_iPlaneX2,g_iPlaneY1,g_iPlaneY2);

			z += 4;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-slice") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			ti = atoi(argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			switch(g_iCutNormal) {
				case 1: // X
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: Slice index out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[0]-1);
						goto _ende;
					}
					mprintf("    Taking slice %d from range 0 .. %d in X direction.\n",ti,g_pCubeWork->m_iRes[0]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti;
					break;
				case 2: // Y
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: Slice index out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[1]-1);
						goto _ende;
					}
					mprintf("    Taking slice %d from range 0 .. %d in Y direction.\n",ti,g_pCubeWork->m_iRes[1]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti;
					break;
				case 3: // Z
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Slice index out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[2]-1);
						goto _ende;
					}
					mprintf("    Taking slice %d from range 0 .. %d in Z direction.\n",ti,g_pCubeWork->m_iRes[2]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti;
					break;
			}

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-slicerange") == 0) {
		/*****************************************************************************************************************/

			if (z+2 >= argc) {
				eprintf("    Error: This command requires two additional arguments.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			ti = atoi(argv[z+1]);
			ti2 = atoi(argv[z+2]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (ti > ti2) {
				eprintf("Error: First index needs to be smaller than second index.\n");
				goto _ende;
			}

			switch(g_iCutNormal) {
				case 1: // X
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: Slice index 1 out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[0]-1);
						goto _ende;
					}
					if ((ti2 < 0) || (ti2 >= g_pCubeWork->m_iRes[0])) {
						eprintf("Error: Slice index 2 out of range (found %d, allowed 0 .. %d).\n",ti2,g_pCubeWork->m_iRes[0]-1);
						goto _ende;
					}
					mprintf("    Taking slice range %d .. %d from total range 0 .. %d in X direction.\n",ti,ti2,g_pCubeWork->m_iRes[0]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti2;
					break;
				case 2: // Y
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: Slice index 1 out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[1]-1);
						goto _ende;
					}
					if ((ti2 < 0) || (ti2 >= g_pCubeWork->m_iRes[1])) {
						eprintf("Error: Slice index 2 out of range (found %d, allowed 0 .. %d).\n",ti2,g_pCubeWork->m_iRes[1]-1);
						goto _ende;
					}
					mprintf("    Taking slice range %d .. %d from total range 0 .. %d in Y direction.\n",ti,ti2,g_pCubeWork->m_iRes[1]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti2;
					break;
				case 3: // Z
					if ((ti < 0) || (ti >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Slice index 1 out of range (found %d, allowed 0 .. %d).\n",ti,g_pCubeWork->m_iRes[2]-1);
						goto _ende;
					}
					if ((ti2 < 0) || (ti2 >= g_pCubeWork->m_iRes[2])) {
						eprintf("Error: Slice index 2 out of range (found %d, allowed 0 .. %d).\n",ti2,g_pCubeWork->m_iRes[2]-1);
						goto _ende;
					}
					mprintf("    Taking slice range %d .. %d from total range 0 .. %d in Z direction.\n",ti,ti2,g_pCubeWork->m_iRes[2]-1);
					g_iCutSliceFrom = ti;
					g_iCutSliceTo = ti2;
					break;
			}

			z += 2;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-showatoms") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			g_fAtomRadius = atof(argv[z+1]);
			g_bShowAtoms = true;
			mprintf("    Displaying the atoms in the cut plane plot with radius %.3f.\n",g_fAtomRadius);

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-valuerange") == 0) {
		/*****************************************************************************************************************/

			if (z+2 >= argc) {
				eprintf("    Error: This command requires two additional arguments.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);
			mprintf("    Consuming next argument: \"%s\"\n",argv[z+2]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			g_bCustomValueRange = true;
			g_fValueRangeMin = atof(argv[z+1]);
			g_fValueRangeMax = atof(argv[z+2]);
			mprintf("    Using a value range of %.5f .. %.5f for the cut plane plot.\n",g_fValueRangeMin,g_fValueRangeMax);

			z += 2;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorrange") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (g_pVectorField[0] == NULL) {
				eprintf("Error: No vector field loaded.\n");
				goto _ende;
			}

			g_fVectorRange = atof(argv[z+1]);
			mprintf("    Using a max. vector length of %.5f for the cut plane plot.\n",g_fVectorRange);

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorscale") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (g_pVectorField[0] == NULL) {
				eprintf("Error: No vector field loaded.\n");
				goto _ende;
			}

			g_fVectorScale = atof(argv[z+1]);
			mprintf("    Scaling the vector field in the cut plane with factor %.3f.\n",g_fVectorScale);

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorhead") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (g_pVectorField[0] == NULL) {
				eprintf("Error: No vector field loaded.\n");
				goto _ende;
			}

			g_fVectorHeadSize = atof(argv[z+1]);
			mprintf("    Scaling the vector arrow size in the cut plane with factor %.3f.\n",g_fVectorHeadSize);

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-vectorstride") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (g_pVectorField[0] == NULL) {
				eprintf("Error: No vector field loaded.\n");
				goto _ende;
			}

			g_iVectorStride = atoi(argv[z+1]);
			mprintf("    Using a stride of %d to show the vector field in the cut plane.\n",g_iVectorStride);

			z += 1;

		/*****************************************************************************************************************/
		} else if (strcmp(argv[z],"-writeplane") == 0) {
		/*****************************************************************************************************************/

			if (z+1 >= argc) {
				eprintf("    Error: This command requires an additional argument.\n");
				goto _ende;
			}

			mprintf("    Consuming next argument: \"%s\"\n",argv[z+1]);

			if (g_pCubeWork == NULL) {
				eprintf("Error: The working buffer is empty.\n");
				goto _ende;
			}

			if (g_iCutNormal == -1) {
				eprintf("Error: No cut plane defined.\n");
				goto _ende;
			}

			if (g_iCutSliceFrom == -1) {
				eprintf("Error: No slice / slice range defined.\n");
				goto _ende;
			}

			mprintf("    Writing cut plane to \"%s\" ...\n",argv[z+1]);

			if (!WriteSlice( g_pCubeWork, g_iCutNormal, g_iCutSliceFrom, g_iCutSliceTo, argv[z+1] )) {
				eprintf("Failed.\n");
				goto _ende;
			}

			z += 1;

		/*****************************************************************************************************************/
		} else {
		/*****************************************************************************************************************/

			eprintf("Error: Unrecognized command line argument: \"%s\".\n",argv[z]);
			Usage();
			goto _ende;
		}
		/*****************************************************************************************************************/
	}

	mprintf("Finished all tasks.\n");

	if (g_pCubeWork != NULL) {
		delete g_pCubeWork;
		g_pCubeWork = NULL;
	}

_ende:
	mprintf(WHITE,"\nCubeTool leaving.\n\n");

	return 0;
}


