//------------------------------------------------------------
// $Id: array3d.h,v 1.2 1999/12/07 03:49:55 yamauchi Exp $
//------------------------------------------------------------
//	(M\pi)^2 define 3dim array
// 	Volume Rendeing 用
//
//	範囲をセットできる3次元配列をエミュレート
// 	範囲は [min, max] である。
//
// 1998年2月25日(水)
//	Copyright (C) By Hitoshi YAMAUCHI  山内 斉
//
// あまり他のクラスに依存したくないので通常は iostream.h と assert.h 
// のみに依存するようにした．ただし，簡便のため，このファイルを 
// include する前に IntVec3 を include していると以下に示す IntVec3 を
// 使ってのアクセスを行う method が有効になる． 
// 
//  	void set(const IntVec3& anIdxVec, T val);
//   	T    get(const IntVec3& anIdxVec) const;
// 範囲は   [mMinIdx, mMaxIdx]
// size は  mMaxIdx - mMinIdx + 1 (つまり実際に保持している数)
//------------------------------------------------------------
#ifndef _array3d_h
#define _array3d_h 1

#include <iostream.h>
#include <assert.h>
#include <stdlib.h>
#include "GridColor.h"

// このクラスは ARRAY_3D_DIM 次元の配列を保持するように見える
// 3次元の index でアクセスするが、実装は 1 次元
const int ARRAY_3D_DIM = 3;	

template < class T >
class array3dClass{  //  : public SerializableIF { for eliminating overhead
protected:
    int  mMinIdx[ARRAY_3D_DIM];
    int  mMaxIdx[ARRAY_3D_DIM];    
    int  mSize[ARRAY_3D_DIM];    
    T   *mpVal;

protected:
    void free(void) 
    {
	for(int i = 0; i < ARRAY_3D_DIM; i++)
	{
	    this->mMinIdx[i] = -1;
	    this->mMaxIdx[i] = -2;    
	    this->mSize[i]   = -3;    
	}
	if(this->mpVal != NULL)
	{
	    delete [] this->mpVal;
	    this->mpVal = NULL;
	}
    }
    void copy(const array3dClass< T >& rhs)
    {
	int i;
	for(i = 0; i < ARRAY_3D_DIM; i++)
	{
	    this->mMinIdx[i] = rhs.mMinIdx[i];
	    this->mMaxIdx[i] = rhs.mMaxIdx[i];    
	    this->mSize[i]   = rhs.mSize[i];    
	}
	int idx[ARRAY_3D_DIM];
	assert(ARRAY_3D_DIM == 3);
	for(idx[2] = rhs.mMinIdx[2]; idx[2] <= rhs.mMaxIdx[2]; idx[2]++)
	{
	    for(idx[1] = rhs.mMinIdx[1]; idx[1] <= rhs.mMaxIdx[1]; idx[1]++)
	    {
		for(idx[0] = rhs.mMinIdx[0]; idx[0] <= rhs.mMaxIdx[0];
		    idx[0]++) 
		{
		    this->mpVal[this->getInternalIdx(idx)]
			= rhs.mpVal[rhs.getInternalIdx(idx)];
		}
	    }
	}
    }

    int  checkDim(int dim) const
    {
	if((dim < 0) || (ARRAY_3D_DIM <= dim))
	{
	    cout << "Error! : illegal dimension array3dClass::checkDim"
		<< endl;
	    abort();
	}
	return(1);
    }
    
    int  getInternalIdx(const int idx[]) const
    {
	int i;
	int internalIdx[ARRAY_3D_DIM];
	for(i = 0; i < ARRAY_3D_DIM; i++)
	{
	    assert(idx[i] >= 0);

	    if(!((this->mMinIdx[i] <= idx[i]) && 
		 (idx[i] <= this->mMaxIdx[i])))
	    {
		cout << "Error! : array3dClass::set index is out of range. "
		    "dim=" << i << ", range[" << this->mMinIdx[i] << ", "
			<< this->mMaxIdx[i] << "], idx=" << idx[i] << endl;
		abort();
		exit(1);
	    }
	    internalIdx[i] = idx[i] - this->mMinIdx[i];
	    assert(internalIdx[i] >= 0); 
	}
	// ここは ARRAY_3D_DIM = 3 を仮定して書いている
	assert(ARRAY_3D_DIM == 3); 
	int retIdx = 
	    internalIdx[0] +
		internalIdx[1] * this->mSize[0] + 
		    internalIdx[2] * this->mSize[0] * this->mSize[1];
	assert(retIdx >= 0);
	return(retIdx);
    }

public:
    // constructors 
    array3dClass(void)
    {
	this->mpVal = NULL;
	this->free();

	// 	cout << "debug new " << debugnew << endl;
	// 	debugnew++;
    }
    ~array3dClass(void)
    {
	this->free();

	// 	cout << "debug del " << debugdel << endl;
	// 	debugdel++;
    };
    array3dClass(const array3dClass< T > & rhs)
    {
	this->init(rhs.mMinIdx, rhs.mMaxIdx);
	this->copy(rhs);

	// 	cout << "debug new " << debugnew << endl;
	// 	debugnew++;
    }
    const array3dClass< T > & operator=(const array3dClass< T > & rhs)
    {
	if(this != &rhs)
	{
	    this->init(rhs.mMinIdx, rhs.mMaxIdx);
	    this->copy(rhs);
	}

	// 	cout << "debug new " << debugnew << endl;
	// 	debugnew++;

	return(*this);
    }
    // initialize
    // ここは T が int, double などの基本型の場合には mpVal[] は初期化
    // されないことに注意．
    void init(const int minIdx[], const int maxIdx[])
    {
	int i;
	for(i = 0; i < ARRAY_3D_DIM; i++)
	{
	    assert(maxIdx[i] - minIdx[i] >= 0);
	    this->mMinIdx[i] = minIdx[i];
	    this->mMaxIdx[i] = maxIdx[i];
	    this->mSize[i]   = maxIdx[i] - minIdx[i] + 1;
	}
	
	if(this->mpVal != NULL)	// 二重の初期化を防ぐ
	{
	    delete [] this->mpVal;
	    this->mpVal = NULL;
	}	
	
	this->mpVal = new T[this->getTotalSize()];
    }

    // 通常の配列の宣言と同じ T array[size(=3)] とすると，
    // minidx = 0, maxidx = size - 1 つまり，
    // array[0], array[1], array[2] を作成する
    void initBySizeWithBaseZero(const int size[])
    {
	int minidx[3], maxidx[3];
	for(int i = 0; i < ARRAY_3D_DIM; i++)
	{
	    minidx[i] = 0;
	    maxidx[i] = size[i] - 1;
	    assert(maxidx[i] >= 0);
	}
	this->init(minidx, maxidx);
    }


    //------------------------------
    // get and set functions (member)
    //     注意: これを使って for をまわす場合には
    //		 for(i = getMinIdx(0); i <= getMaxIdx(0); i++)
    //	   のように getMaxIdx に等号が必要になる．大きさでなく，有効な
    //     値が入っている．
    //------------------------------    
    int getMinIdx(int dim) const 	
    {
	assert(checkDim(dim));
	return(this->mMinIdx[dim]);	
    }
    int getMaxIdx(int dim) const 	
    {
	assert(checkDim(dim));
	return(this->mMaxIdx[dim]);	
    }
    int getSize(int dim) const 
    {
	assert(checkDim(dim));
	return(this->mSize[dim]);	
    }
    
    // get and set functions (derived)
    int isInited(void) const 
    {
	if(this->mpVal != NULL)
	{
	    return(!0);	    
	}else
	{
	    return(0);
	}
    }

    //   これはキャッシュ可能 (IMPROVE)
    int  getTotalSize(void) const 
    {
	int i;
	int size = 1;
	for(i = 0; i < ARRAY_3D_DIM; i++)
	{
	    size *= this->mSize[i];
	}
	assert(size >= 0);
	return(size);
    }
    void set(const int idx[], const T& val)
    {
	this->mpVal[this->getInternalIdx(idx)] = val;
    }
    T    get(const int idx[]) const
    {
	return(this->mpVal[this->getInternalIdx(idx)]);
    }
    void setAsOneDimArray(const int idx, const T& val) 
    {
	assert((0 <= idx) && (idx < this->getTotalSize()));
	this->mpVal[idx] = val;
    }
    T    getAsOneDimArray(const int idx) const
    {
	assert((0 <= idx) && (idx < this->getTotalSize()));
	return(this->mpVal[idx]);
    }

    // range 外の index なら getInternalIdx 内で abort する
    void abortIfOutOfRange(int idx[]) const
    {
	this->getInternalIdx(idx);
    }
    
    void print(void) const 
    {
	for(int i = 0; i < 3; i++)
	{
	    cout << "(" << this->mSize[i] << ")[" << 
		this->mMinIdx[i] << "," << this->mMaxIdx[i] << "]";
	}
	cout << endl;	
	if(this->mpVal == NULL)
	{
	    cout << "array3d::contains no data." << endl;
	}else 
	{
	    cout << "array3d::There is data area." << endl;
	}
    }

    // T の比較演算(operator==, operator!=)を書いておかないとここでエ
    // ラーになる 
    friend int operator == (const array3dClass < T > &a, 
			    const array3dClass < T > &b)
    {
	int equalflag = 1;
	for(int i = 0; i < ARRAY_3D_DIM; i++)
	{
	    if((a.mMinIdx[i] 	!= b.mMinIdx[i]) ||
	       (a.mMaxIdx[i] 	!= b.mMaxIdx[i]) ||
	       (a.mSize[i] 	!= b.mSize[i]))
	    {
		equalflag = 0;
	    }
	}
	if(equalflag == 0)
	{
	    return(0);
	}
	if((a.mpVal == NULL) && (b.mpVal == NULL))
	{
	    return(1);		// 両方 NULL
	}
	else if((a.mpVal == NULL) || (b.mpVal == NULL))
	{
	    return(0);		// 片方 NULL
	}

	cout << "Warning : Thie array3dClass operator== assumes "
	    "< GridColorClass >" << endl;
	int size = a.getTotalSize();
	for(int j = 0; j < size; j++)
	{
	    // cout << a.mpVal[j] << endl;
	    if(a.mpVal[j] != b.mpVal[j])
	    {
		return(0);	// fail
	    }
	}
	return(1);
    }

    // IntVec3 がこのファイルの前に include されている場合には以下の 
    // convenient 関数が使えるようになる．
#ifdef _IntVec3_h
    void set(const IntVec3& anIdxVec, T val)
    {
	int idx[3];
	anIdxVec.getAsArray(idx);
	this->mpVal[this->getInternalIdx(idx)] = val;
    }
    T    get(const IntVec3& anIdxVec) const
    {
	int idx[3];
	anIdxVec.getAsArray(idx);
	return(this->mpVal[this->getInternalIdx(idx)]);
    }
#endif 

    //----------------------------------------
    // SerializableIF : double の表現が同じ計算機でないと上手くいかない    
    //----------------------------------------
    int getSerializedSize(void) const
    {
	// mMinIdx[ARRAY_3D_DIM], mMaxIdx[ARRAY_3D_DIM], mSize[ARRAY_3D_DIM];
	// *mpVal;
	assert(this->mpVal != NULL);
	int size = ARRAY_3D_DIM + ARRAY_3D_DIM + ARRAY_3D_DIM + 
	    this->getTotalSize() * this->mpVal[0].getSerializedSize();
	return(size);
    }
    double *newSerializedArray(void)
    {
	double *r = new double[this->getSerializedSize()];
	int idx = 0;
	int i;
	for(i = 0; i < 3; i++)
	{
	    r[idx] = (double)(this->mMinIdx[i]);
	    idx++;
	}
	for(i = 0; i < 3; i++)
	{
	    r[idx] = (double)(this->mMaxIdx[i]);
	    idx++;
	}
	for(i = 0; i < 3; i++)
	{
	    r[idx] = (double)(this->mSize[i]);
	    idx++;
	}

	for(i = 0; i < this->getTotalSize(); i++)
	{
	    double *serializedT = this->mpVal[i].newSerializedArray();
	    for(int j = 0; j < this->mpVal[i].getSerializedSize(); j++)
	    {
		r[idx] = serializedT[j];
		idx++;
	    }
	    delete [] serializedT; // ここだ! ここがメモリリークだった．
	}
	return(r);
    }
    void deserialize(double *pBuf)
    {
	int idx = 0;
	int i;
	for(i = 0; i < 3; i++)
	{
	    this->mMinIdx[i] = (int)(pBuf[idx]);
	    idx++;
	}
	for(i = 0; i < 3; i++)
	{
	    this->mMaxIdx[i] = (int)(pBuf[idx]);
	    idx++;
	}
	
	// この grid を初期化，領域確保
	this->init(this->mMinIdx, this->mMaxIdx);
	for(i = 0; i < 3; i++)
	{
	    if(this->mSize[i] != (int)(pBuf[idx]))
	    {
		cout << "Error! : array3dClass::deserialize, " 
		    "size is not valid." << endl;
		exit(1);
	    }
	    idx++;		// pBuf は進めておかねばならない
	}
	const int size = this->getTotalSize();
	for(i = 0; i < size; i++)
	{
	    this->mpVal[i].deserialize(&(pBuf[idx]));
	    idx += this->mpVal[i].getSerializedSize();
	}
    }
};
    
#endif				// #ifndef _array3d_h 
