//------------------------------------------------------------
// $Id: pnmReader.h,v 1.1.1.1 1999/11/25 06:26:09 yamauchi Exp $
//------------------------------------------------------------
//	PBM, PGM, PPM file Reader
//	1997年12月 5日(金)
//	ファイル名を指定するとそのファイルを memory image として返す
//	特徴はコメントも返すのでそれに色々埋め込めること
//
//	Copyright (C) 1997--1999 YAMAUCHI Hitoshi 山内 斉
//	1999年1月27日(水) RGB 化 start 
//------------------------------------------------------------
#ifndef _pnmReader_h
#define _pnmReader_h

#include <stdio.h>
#include <iostream.h>
#include <assert.h>
#include "strnew.h"

// pnm file 内に書かれているコメントの保持する最大行数(最大この行数だ
// けはコメントがある場合に保持している)
#ifndef PNMREADER_MAX_COMMENTS_LINES
#  define PNMREADER_MAX_COMMENTS_LINES 8
#endif

//------------------------------------------------------------
// buffer size for read
//------------------------------------------------------------
#ifndef PNMREADER_MAX_LINE 
#  define PNMREADER_MAX_LINELEN 256
#endif

//------------------------------------------------------------
// pnm 形式を保持する class 
//------------------------------------------------------------
class PNMReader {
public:
    enum PNMReaderColor {
	RED,
	BLUE,
	GREEN,
	NUM_OF_PNMCOLOR
    };

protected:
    // information of the pnm file 
    int      mType;
    int      mResolSizeX;
    int      mResolSizeY;
    double   mDepth;		
    int	    *mpColor;
    char    *mppComment[PNMREADER_MAX_COMMENTS_LINES]; // keep the comments

protected:
    // inner status of this class 
    int	     mCurrentCommentLines;

protected:
    // initialize 1st time with no memory allocate (一度しか呼ばれない)
    void init_first()
    {
	this->mType 		= -1;
	this->mResolSizeX	= -1;
	this->mResolSizeY	= -1;
	this->mDepth		= -1.0;
	this->mpColor		= NULL;
	for(int i = 0; i < PNMREADER_MAX_COMMENTS_LINES; i++)
	{
	    this->mppComment[i]	= NULL;
	}
	this->mCurrentCommentLines = 0;
    }

    // position(x, y) -> array index : 多次元配列が使いにくいので
    int getIdx(int x, int y, int rgb) const
    {
	assert((0 <= x)   && (x   < this->mResolSizeX));
	assert((0 <= y)   && (y   < this->mResolSizeY));
	assert((0 <= rgb) && (rgb < NUM_OF_PNMCOLOR));	
	return(rgb * (this->mResolSizeX * this->mResolSizeY) + 
	       y * this->mResolSizeX + x);
    }

    // copy : pSrcColor を this->mpColor にコピーする
    void copy(int resol_x, int resol_y, int *pSrcColor)
    {
	if(this->mpColor == NULL)
	{
	    cerr << "Error! : PNMReader::copy, color memory is not allocated."
		<< endl;
	    exit(1);
	}
	if((resol_x != this->mResolSizeX) || (resol_y != this->mResolSizeY))
	{
	    cerr << "Error! : PNMReader::copy, ResolSize is not same between "
		" src and dest." << endl;
	    exit(1);
	}
	for(int rgb = 0; rgb < NUM_OF_PNMCOLOR; rgb++)
	{
	    for(int y = 0; y < this->mResolSizeY; y++)	    
	    {
		for(int x = 0; x < this->mResolSizeX; x++)
		{
		    int idx = this->getIdx(x, y, rgb);
		    this->mpColor[idx] = pSrcColor[idx];
		}
	    }
	}
    }

public:
    // default constructor
    PNMReader(void)
    {	// 可読性のために `:' による初期化はやめている
	this->init_first();
    }

    // constructor
    // @param aType   : pnm format の type 現在は P5 と P6 の 2 種類
    // 			のため，5 または 6 を指定する．
    // @param resol_X : X 方向の解像度
    // @param resol_Y : Y 方向の解像度
    // @param depth   : 色についての depth (通常 255)
    PNMReader(int aType, int resol_X, int resol_Y, double depth)
    {
	this->init_first();
	this->init(aType, resol_X, resol_Y, depth);
    }

    // copy constructor
    PNMReader(const PNMReader& rhs)
    {
	this->init_first();
	if((rhs.mType > 0) && (rhs.mpColor != NULL))
	{
	    this->init(rhs.mType, 
		       rhs.mResolSizeX, rhs.mResolSizeY, rhs.mDepth);
	    this->copy(rhs.mResolSizeX, rhs.mResolSizeY, rhs.mpColor);
	}
    }

    // assignment
    const PNMReader& operator=(const PNMReader& rhs)
    {
	if(this != &rhs)
	{
	    this->init(rhs.mType, 
		       rhs.mResolSizeX, rhs.mResolSizeY, rhs.mDepth);
	    this->copy(rhs.mResolSizeX, rhs.mResolSizeY, rhs.mpColor);
	}
	return(*this);
    }

    // destructor
    ~PNMReader(void) 
    {
	if(this->mpColor != NULL)
	{
	    delete [] this->mpColor;
	}
	this->mpColor = NULL;
    }

    // initialize with memory allocate (何度も呼ばれる可能性あり)
    void init(int x, int y, int aType, double depth)
    {
	this->init_first();
	if((aType < 0) || (aType > 6))	
	{
	    cerr << "Error! : illegal file type = " << aType << "." << endl;
	    exit(1);
	}
	if((x < 0) || (y < 0))
	{
	    cerr << "Error! : illegal pnmfile size (" << x << ", " << y
		<< ")" << endl;
	    exit(1);
	}
	this->mType   	  = aType;
	this->mResolSizeX = x;
	this->mResolSizeY = y;
	this->mDepth  	  = depth;
	if(this->mpColor != NULL)
	{
	    delete [] this->mpColor;
	}
	// NUM_OF_PNMCOLOR is for R,G,B
	this->mpColor =	new int[NUM_OF_PNMCOLOR * 
				this->mResolSizeX * this->mResolSizeY];
	this->mCurrentCommentLines = 0;
    }

    // set & unset
    void setRGBColors(int x, int y, int col[])
    {
	assert((0 <= x) && (x < this->mResolSizeX));
	assert((0 <= y) && (y < this->mResolSizeY));
	// 現在扱える format は P5 と P6
	assert((this->mType == 5) || (this->mType == 6)); 

	for(int i = 0; i < NUM_OF_PNMCOLOR; i++)
	{
	    int idx = this->getIdx(x, y, i);
	    this->mpColor[idx] = col[i];
	}
    }
    
    int getRGBColor(int x, int y, int rgb) const
    {
	// 現在 implement されている type は P5 と P6 のみ
	assert((this->mType == 5) || (this->mType == 6)); 
	return(this->mpColor[this->getIdx(x, y, rgb)]);
    }
    void getRGBColors(int x, int y, int cols[]) const
    {
	// 現在 implement されている type は P5 と P6 のみ
	assert((this->mType == 5) || (this->mType == 6)); 
	for(int i = 0; i < NUM_OF_PNMCOLOR; i++)
	{
	    cols[i] = this->mpColor[this->getIdx(x, y, i)];
	}
    }


    int    getType(void)	const 	{ return(this->mType);		}
    int    getResolSizeX(void)	const 	{ return(this->mResolSizeX);	}
    int    getResolSizeY(void)	const 	{ return(this->mResolSizeY);	}
    double getDepth(void)	const 	{ return(this->mDepth);		}

    void setComments(char str[])
    {
	if(this->mCurrentCommentLines >= PNMREADER_MAX_COMMENTS_LINES)
	{
	    cerr << "Warning : comment line number is too large. line = " 
		<< this->mCurrentCommentLines << ", not recorded." << endl;
	    return;
	}
	
	if(this->mppComment[this->mCurrentCommentLines] != NULL)
	{
	    cerr << "Warning: comment line is over written at line "
		<< this->mCurrentCommentLines << endl;
	    delete [] this->mppComment[this->mCurrentCommentLines];
	}
	this->mppComment[this->mCurrentCommentLines] = strnew(str);
	this->mCurrentCommentLines++;
    }

    // comment の参照を返す．内部のバッファなので外部でいじらないこと
    const char* getCommentsRef(int line) const
    {
	if((line < 0) || (line >= this->mCurrentCommentLines))
	{
	    cerr << "Warning : line is out of range = " << line << endl;
	    return(NULL);
	}
	return(this->mppComment[line]);
    }
    int getSizeOfComments(void) const { return(this->mCurrentCommentLines);}

    void print(void)
    {
	cerr << "PNMReader:: type P" << this->getType() << ", size [" << 
	    this->mResolSizeX << ", " << this->mResolSizeY <<  
		"], color depth = "  << this->mDepth << ", comment lines = "
		    << this->mCurrentCommentLines << endl;
	for(int i = 0; i < this->mCurrentCommentLines; i++)
	{
	    if(this->mppComment[i] == NULL)
	    {
		break;
	    }
	    cerr << "[" << i << "]:" << this->mppComment[i];
	}
    }

public:
    //------------------------------------------------------------
    // print out as pnm format
    //------------------------------------------------------------
    // 読み込んだものと同じ type のまま stdout に出力する
    //------------------------------
    void printAsPSame(void) const
    {
	this->printAsPSame(stdout);
    }
    //------------------------------
    // 読み込んだものと同じ type のまま FILE *pfp に出力する
    //------------------------------
    void printAsPSame(FILE *pfp) const
    {
	this->printAsP(this->getType(), pfp);
    }
    //------------------------------
    // 出力するタイプの番号を指定する : 5 ... P5 format, 6 ... P6 format
    //------------------------------
    void printAsP(int formatnumber) const
    {
	this->printAsP(formatnumber, stdout);
    }
    //------------------------------
    // 出力するタイプと output stream を指定する
    //------------------------------
    void printAsP(int formatnumber, FILE *pfp) const
    {
	switch(formatnumber)
	{
	case 5:			// P5
	case 6:			// P6
	    break;
	default:
	    cerr << "Error! : it is not implemented `P" << formatnumber << 
	    	" format to output." << endl;
	    exit(1);
	}    

	if(pfp == NULL)
	{
	    cout << "Error! : PNMReader::printAsP" << formatnumber << 
		" : illegal output stream." << endl;
	    exit(1);
	}
	fprintf(pfp, "P%d\n", formatnumber);
	if(this->mCurrentCommentLines == 0)
	{
	    fprintf(pfp, "# creator : readpnm By YAMAUCHI Hitoshi\n");
	}
	for(int i = 0; i < this->getSizeOfComments(); i++)
	{
	    fprintf(pfp, "%s", this->getCommentsRef(i));
	}
	fprintf(pfp, "%d %d\n", this->getResolSizeX(), this->getResolSizeY());
	fprintf(pfp, "%d\n", (int)this->getDepth());
	
	// 画像部分の実体の save
	switch(formatnumber)
	{
	case 5:			// P5
	    this->printAsP5format(pfp);
	    break;
	case 6:			// P6
	    this->printAsP6format(pfp);
	    break;
	default:
	    cerr << "Internal Error! : why you come here?" << endl;
	    exit(1);
	}    
    }

protected:    
    //------------------------------------------------------------
    // print out routine の実体
    //------------------------------------------------------------
    // P5 format で出力 : RGB の平均を出力する
    // 平均を出力するというのが正しいかは不明．正しくない気がする
    void printAsP5format(FILE *pfp) const
    {
	int c;
	for(int y = 0; y < this->getResolSizeY(); y++)
	{
	    for(int x = 0; x < this->getResolSizeX(); x++)
	    {
		int cols[NUM_OF_PNMCOLOR];
		this->getRGBColors(x, y, cols);
		c = (cols[RED] + cols[BLUE] + cols[GREEN]) / NUM_OF_PNMCOLOR;
		fputc(c, pfp);
	    }
	}
    }
    // P6 format で出力
    void printAsP6format(FILE *pfp) const
    {
	int c;
	for(int y = 0; y < this->getResolSizeY(); y++)
	{
	    for(int x = 0; x < this->getResolSizeX(); x++)
	    {
		for(int rgb = 0; rgb < NUM_OF_PNMCOLOR; rgb++)
		{
		    c = this->getRGBColor(x, y, rgb);
		    fputc(c, pfp);
		}
	    }
	}
    }
};

//------------------------------------------------------------
// functions
//------------------------------------------------------------
int  readOnePNMFile(const char *pPMNFilename, PNMReader *pPNMbuf);


#endif				// #ifndef _pnmReader_h
