/*
 *  ADP (Another Data Processor) www.adp.la
 *  Copyright (C) 2010 Katsuhisa Ohfuji <katsuhisa@ohfuji.name>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#ifndef ADP_BUILTIN_GRAPHIC_H
#define ADP_BUILTIN_GRAPHIC_H

#pragma pack(1)
// ビットマップファイルヘッダ 
struct MYBITMAPFILEHEADER {
  unsigned short bfType;
  unsigned int   bfSize;
  unsigned short bfReserved1;
  unsigned short bfReserved2;
  unsigned int   bfOffBits;
};

// ビットマップ情報ヘッダ 
struct MYBITMAPINFOHEADER {
    unsigned int   biSize;
    int            biWidth;
    int            biHeight;
    unsigned short biPlanes;
    unsigned short biBitCount;
    unsigned int   biCompression;
    unsigned int   biSizeImage;
    int            biXPixPerMeter;
    int            biYPixPerMeter;
    unsigned int   biClrUsed;
    unsigned int   biClrImporant;
};
#pragma pack()
static inline unsigned short set_ushort(unsigned short val) {
	unsigned short v;
	unsigned char *p = (unsigned char *)&v;
	p[0] = (unsigned char)(val & 0xff);
	p[1] = (unsigned char)((val >> 8) & 0xff);
	return v;
}
static inline unsigned long set_ulong(unsigned long val) {
	unsigned long v;
	unsigned char *p = (unsigned char *)&v;
	p[0] = (unsigned char)(val & 0xff);
	p[1] = (unsigned char)((val >> 8) & 0xff);
	p[2] = (unsigned char)((val >> 16) & 0xff);
	p[3] = (unsigned char)((val >> 24) & 0xff);
	return v;
}
static inline  long set_long( long val) {
	long v;
	unsigned char *p = (unsigned char *)&v;
	p[0] = (unsigned char)(val & 0xff);
	p[1] = (unsigned char)((val >> 8) & 0xff);
	p[2] = (unsigned char)((val >> 16) & 0xff);
	p[3] = (unsigned char)((val >> 24) & 0xff);
	return v;
}


struct ExecContext_Graph : public ExecContextRoot {
	ExecContext_Graph(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	PINTEGER readOption( const PArray *opt, const char *key, int defvalue) {
		PINTEGER v;
		if ( !(*opt)[key] || !(*opt)[key]->cnv_integer( v ) ) {
			v = defvalue;
		}
		return v;
	}

	int sx;
	int sy;
	void lineto( vector< vector< int> > &canvas, int ex, int ey, int col) {
		canvas[ey][ex] = col;
		if ( sx >= 0 && sy >= 0 && (ex != sx || ey != sy) ) {
			canvas[sy][sx] = col;
			if ( ex == sx || abs(ex-sx) < abs(ey-sy) ) {
				int s = sy;
				int e = ey;
				if ( sy > ey ) {
					s = ey;
					e = sy;
				}
				for ( int ypos = s; ypos <= e; ypos++ ) {
					int xpos = (int)(((double)ex - sx) * ((double)ypos - sy) / ((double)ey - sy) + sx + 0.5);
					canvas[ypos][xpos] = col;
				}
			} else {
				int s = sx;
				int e = ex;
				if ( sx > ex ) {
					s = ex;
					e = sx;
				}
				for ( int xpos = s; xpos <= e; xpos++ ) {
					int ypos = (int)(((double)ey - sy) * ((double)xpos - sx) / ((double)ex - sx) + sy + 0.5);
					canvas[ypos][xpos] = col;
				}
			}
		}
		sx = ex;
		sy = ey;
	}

	void line( vector< vector< int> > &canvas, int sx, int sy, int ex, int ey, int col) {
		this->sx = sx;
		this->sy = sy;
		lineto( canvas, ex, ey, col);
	}

	#define FONTSIZEX	8
	#define FONTSIZEY	8
	void putfont8( vector< vector< int> > &canvas, int xpos, int ypos, char (*font)[FONTSIZEY][FONTSIZEX], int col) {
		for ( int y = 0; y < FONTSIZEY; y++ ) {
			for ( int x = 0; x < FONTSIZEX; x++ ) {
				canvas[FONTSIZEY-y+ypos][x+xpos] = (*font)[y][x] != 0 ?  col : 0xffffffff;
			}
		}
	}

	void rputnumber( vector< vector< int> > &canvas, int xpos, int ypos, double value, int col) {
		static char font[10][FONTSIZEY][FONTSIZEX] = {
			{{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,0,0,1,0,0,0},{0,0,0,1,1,0,0,0},{0,0,0,0,1,0,0,0},{0,0,0,0,1,0,0,0},{0,0,0,0,1,0,0,0},{0,0,0,0,1,0,0,0},{0,0,0,0,1,0,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,0,0,0,1,0,0},{0,0,0,0,1,0,0,0},{0,0,1,1,0,0,0,0},{0,1,1,1,1,1,1,0},{0,0,0,0,0,0,0,0}},
			{{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,0,0,0,0,0,1,0},{0,0,0,1,1,1,0,0},{0,0,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,0,0,1,0,0,0},{0,0,0,1,1,0,0,0},{0,0,1,0,1,0,0,0},{0,0,1,0,1,0,0,0},{0,1,0,0,1,0,0,0},{0,1,1,1,1,1,1,0},{0,0,0,0,1,0,0,0},{0,0,0,0,0,0,0,0}},
			{{0,1,1,1,1,1,0,0},{0,1,0,0,0,0,0,0},{0,1,0,0,0,0,0,0},{0,1,1,1,1,1,0,0},{0,0,0,0,0,0,1,0},{0,0,0,0,0,0,1,0},{0,1,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,1,1,1,1,1,0},{0,1,0,0,0,0,0,0},{0,1,0,0,0,0,0,0},{0,1,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
			{{0,1,1,1,1,1,1,0},{0,0,0,0,0,0,1,0},{0,0,0,0,0,0,1,0},{0,0,0,0,0,1,0,0},{0,0,0,0,1,0,0,0},{0,0,0,1,0,0,0,0},{0,0,1,0,0,0,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
			{{0,0,1,1,1,1,0,0},{0,1,0,0,0,0,1,0},{0,1,0,0,0,0,1,0},{0,0,1,1,1,1,1,0},{0,0,0,0,0,0,1,0},{0,0,0,0,0,0,1,0},{0,1,1,1,1,1,0,0},{0,0,0,0,0,0,0,0}},
		};
		static char fontminus[FONTSIZEY][FONTSIZEX] = {
			{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,1,1,1,1,1,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}
		};

		long long v = value > 0 ? (long long)value : -(long long)value;

		do {
			putfont8( canvas, xpos, ypos, &font[v % 10], col);
			xpos -= FONTSIZEX;
			v = v / 10;
		} while ( v > 0 && xpos >= 0 ); 

		if ( value < 0 && xpos >= 0) {
			putfont8( canvas, xpos, ypos, &fontminus, col);
		}
	}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PArray *ary = get<PArray>(0);
		const PArray *opt = get<PArray>(1);
		const PString *fpath = get<PString>(2);
		const PEVeriable *value = get<PEVeriable>(2);
		double v;
		
		if ( !ary ) return  RERR_argment_type(excp, 0);
		if ( !opt ) return  RERR_argment_type(excp, 1);
		if ( (!fpath || !fpath->c_str()) && !value ) return RERR_argment_type(excp, 2);
		
		// xarea,yarea 全体の描画エリア
		int xarea = static_cast<int>(readOption(opt, "xarea", 460));
		int yarea = static_cast<int>(readOption(opt, "yarea", 330));

		// xsize,ysize グラフの描画エリア
		int xofs = static_cast<int>(readOption(opt, "xpos", 40));	// グラフの位置
		int yofs = static_cast<int>(readOption(opt, "ypos", 20)); // グラフの位置
		int xsize = static_cast<int>(readOption(opt, "xsize", 400)); // グラフのサイズ
		int ysize = static_cast<int>(readOption(opt, "ysize", 300)); // グラフのサイズ

		// 色
		int bcolor = set_ulong( static_cast<int>(readOption(opt, "bgcolor", 0xffffff)) );
		int scolor = set_ulong( static_cast<int>(readOption(opt, "scolor", 0x000000)) );
		int fcolor = set_ulong( static_cast<int>(readOption(opt, "color", 0xff0000)) );

		// ポイント数、最大値、最小値
		size_t num = ary->size();
		double max = DBL_MIN;
		double min = DBL_MAX;
		
		for ( size_t i = 0; i < num; i++ ) {
			if ( !(*ary)[i] || !(*ary)[i]->cnv_double( v ) ) {
				string msg = cformat("Array value error.");
				return RERR(excp, msg);
			}
			if ( max < v ) max = v;
			if ( min > v ) min = v;
		}
		if ( min > 0 ) min = 0;
		
		// グラフの描画（初期化）
		vector< vector< int > > canvas;
		canvas.reserve(yarea);
		for ( int y = 0; y < yarea; y++ ) {
			vector<int> row( xarea, bcolor);
			canvas.push_back(row);
		}
		
		// 原点の描画
		line( canvas, xofs, yofs, xofs, yofs + ysize, scolor);
		v = (0 - min) * ysize / (max - min) + yofs;
		line( canvas, xofs, static_cast<int>(v), xsize + xofs, static_cast<int>(v), scolor);

		// スケールの描画
		rputnumber( canvas, (int)(xofs - FONTSIZEX * 1.5), (int)(v - FONTSIZEY/2), 0, scolor);	// 原点
		rputnumber( canvas, (int)(xofs - FONTSIZEX * 1.5), (int)(yofs - FONTSIZEY/2 + ysize), max, scolor);
		// 最小値
		if ( min < 0 ) {
			rputnumber( canvas, (int)(xofs - FONTSIZEX * 1.5), (int)(yofs - FONTSIZEY/2) , min, scolor);
		}
		rputnumber( canvas, xofs + xsize, (int)(v - FONTSIZEY * 1.5), num, scolor);
		
		// グラフの描画
		sx = -1;
		sy = -1;
		for ( size_t i = 0; i < num; i++ ) {
			(*ary)[i]->cnv_double( v );	// 1回やっているのでエラーチェックをさぼる
			int ypos = int(((double)v - (double)min) * (double)ysize / ((double)max - (double)min) + yofs + 0.5);
			int xpos = int((double)i * (double)xsize / (double)(num-1) + xofs + 0.5);
			canvas[ypos][xpos] = fcolor;	// グラフの色
			if ( xpos + 1 < xsize ) canvas[ypos][xpos + 1] = fcolor;	// グラフの色
			if ( xpos - 1 >= 0 )    canvas[ypos][xpos - 1] = fcolor;	// グラフの色
			if ( ypos + 1 < ysize ) canvas[ypos + 1][xpos] = fcolor;	// グラフの色
			if ( ypos - 1 >= 0 )    canvas[ypos - 1][xpos] = fcolor;	// グラフの色

			if ( xpos + 1 < xsize && ypos + 1 < ysize ) canvas[ypos + 1][xpos + 1] = fcolor;	// グラフの色
			if ( xpos + 1 < xsize && ypos - 1 >= 0    ) canvas[ypos - 1][xpos + 1] = fcolor;	// グラフの色
			if ( xpos - 1 >= 0    && ypos + 1 < ysize ) canvas[ypos + 1][xpos - 1] = fcolor;	// グラフの色
			if ( xpos - 1 >= 0    && ypos - 1 >= 0    ) canvas[ypos - 1][xpos - 1] = fcolor;	// グラフの色

			lineto( canvas, xpos, ypos, fcolor);
		}

		// ビットマップファイルの作成
		int xfilesize = 3 * xarea / 4;	// 横方向は4バイトでアライメントする
		if ( xfilesize * 4 < 3 * xarea ) xfilesize++;
		xfilesize = 4 * xfilesize;
		
		int fsize = sizeof(MYBITMAPFILEHEADER) + sizeof(MYBITMAPINFOHEADER) + xfilesize * yarea;
		MYBITMAPFILEHEADER	head = { set_ushort('B'+'M'*256), set_ulong(fsize), 0, 0, set_ulong(0x36) };
		MYBITMAPINFOHEADER	info = { set_ulong(40), set_long(xarea), set_long(yarea), set_ushort(1), set_ushort(24), 0, set_ulong(3780), set_long(3780), set_long(3780), 0, 0 };

		ofstream outputFile;
		ostringstream outputString;
		ostream *os = &outputString;
		if ( fpath && fpath->c_str() ) {	
			outputFile.open( fpath->c_str(), ios_base::out | ios_base::trunc | ios::binary);
			os = &outputFile;
		}

		os->write( (char*)&head, sizeof(MYBITMAPFILEHEADER));
		os->write( (char*)&info, sizeof(MYBITMAPINFOHEADER));
		char dmy[4] = { 0, 0, 0, 0};
		for ( int y = 0; y < yarea; y++ ) {
			for ( int x = 0; x <  xarea; x++ ) {
				os->write( (char*)&canvas[y][x], 3);
			}
			if ( xfilesize - 3 * xarea > 0 ) {
				os->write( dmy, xfilesize - 3 * xarea);
			}
		}
		
		if ( fpath && fpath->c_str() ) {	
			outputFile.close();
		} else {
			PString *o = pmm.newPString(pobjs,outputString.str());
			return value->unify(*o, this);	
		}
		return true;
	}
};


#endif
