//----------------------------------------------------------------------------------------------
//
//	Console.m : 描画用コンソール;テキスト/グラフィック/スプライトの共通部分
//  X-BASIC'
//
//  Created by AIG-Soft(iMac) on 2013/01/23.
//  Copyright (c) 2013年 AIG-Soft. All rights reserved.
//

#import "Console.h"
#import "Console+animation.h"
#import <CoreGraphics/CoreGraphics.h>
#import "waitClass.h"

@implementation Console

@synthesize fimage;
@synthesize context;
@synthesize imageRef;
@synthesize fret;
@synthesize imageCnt;
@synthesize fanime=fanime_;
@synthesize frotate=frotate_;
//
@synthesize consolePixel;
@synthesize consoleWidth;
@synthesize consoleHeight;
@synthesize consoleByte1Line;
@synthesize consoleByte1Page;
#ifdef SPDUP
@synthesize updateCnt;
#endif

//----------------------------------------------------------------------------------------------

-(void)init2
// 描画の基本設定
// RunView viewDidLoadから呼び出される
{
	printSelfs(@"%@",self);
	self.clearsContextBeforeDrawing=NO;	// 描画前に消さない
	self.frotate=ROTATE_ASPECTFIT;
	self.backgroundColor=[UIColor clearColor]; // 重ね合わせのため背景は透過
	self.alpha=1.0;
	self.opaque=YES;
	//
	fskipUpdate=NO;
	context=NULL;
}

// console開始処理
// _Frunから呼び出される
-(void)start:(CGBitmapInfo)bitmapInfo
{
	printSelfs(@"%@",self);

	fskipUpdate=YES; // 以下しばらく[update/draw]を無効化する

	// 前回の変形設定が残っていると、self.frameの値がおかしくなっているので、ここで前回の設定をキャンセルさせる
	// V2.2での修正
	// 先に変形をキャンセルしてから設定しないと、その変形を考慮したframe値が設定されてしまうようである
	CGAffineTransform m={-1*-1,0,-1*0,-1,0,0};
	[self.layer setAffineTransform:m];
	frame0=self.frame;
	nslog(@"画面サイズ=%@",NSStringFromCGRect(frame0));
	nslog(@"修正後:%@",self);
	
	// 前回のアニメーションを解除・・・と思ったが、ここでは前のアニメーションは停止できない。.layerが異なるから？
//	[self animation:ANIMATION_STOP];
	
	// bitmapを使ってコンテキストを作成する。
	context=MyCreateBitmapContext(frame0.size.width,frame0.size.height,bitmapInfo);
	// 以後この中をendまで使いまわす=合成不要
	// contextへの初期設定
	[self fontSmoothing:YES]; // フォントのスムージングを有効にする
	[self antiAliasing:YES];  // デフォルトはアンチエイジングも有効
	// V2.2で初期化追加;一部は_Frun()からの移動
	[self alpha:1.0];
	[self backgroundColor:0 g:0 b:0 alpha:0]; // ClearColor相当
	[self border:0 r:0 g:0 b:0 alpha:0];
#if 1
	// 効いているかどうかはわからない
	self.layer.drawsAsynchronously=YES; // 非同期描画指定
#endif
	// Quartzの座標系は上下反転の鏡像なので、その回転をかけておく
	// これはlayerの回転していなので、contextの回転とは違うようである
	// a ,b ,0	x'=ax   ,y'=dy			拡大縮小
	// c ,d ,0	x'=ax+cy,y'=bx+dy		回転		a=cos,b=sin,c=-sin,d=cos
	// tx,ty,1	x'= x+tx,y'= y+ty		平行移動
	//			x'=-x					鏡像
	//			x'=-(ax+cy),y'=bx+dy	回転＋鏡像変換	(a*=-1,c*=-1)	←これ
//	CGAffineTransform m={-1*cos(M_PI),sin(M_PI),-1*-sin(M_PI),cos(M_PI),0,0};
// V2.2で上に移動
//	CGAffineTransform m={-1*-1,0,-1*0,-1,0,0}; // sin/cosを計算するとこうなる
//						{	 1,0,   0,-1,0,0}
	// この基本変形を変更するときは、setAnimeParameter2/setAnimeParameter3/transformにある行列演算を展開した式も変更しなければならない
	
	// この大本になる変形情報を保存
// V2.2で上に移動
//	[self.layer setAffineTransform:m];
	baseAffineWithTrans=baseAffine=m; 	// これがlayerの基本変形
	//
	imageRef=nil;
	imageCnt=0;
	nslog(@"Console.context=%@",context);
	//
	fskipUpdate=NO;	// [update/draw]有効

	// ピクセルワーク用変数設定
	consoleWidth 	= (int)CGBitmapContextGetWidth(		context);
	consoleHeight	= (int)CGBitmapContextGetHeight(	context);
	consoleByte1Line= CGBitmapContextGetBytesPerRow(context);
	consoleByte1Page= consoleByte1Line*consoleHeight;
	nslog(@"width =%u",consoleWidth );
	nslog(@"height=%d",consoleHeight);
	nslog(@"byte1Line=%ld/byte1Page=%ld",consoleByte1Line,consoleByte1Page);
	
	// 合成用ワーク : MyCreateBitmapContext()で作られている
	consolePixel=CGBitmapContextGetData(context);
	
#ifdef SPDUP
	updateCnt=0;
#if 0	// サブスレッド中からはNSTimerは起動できないみたい
//	nslog(@"isMainThread=%d/isMultiThreaded=%d",[NSThread isMainThread],[NSThread isMultiThreaded]);
	drawThread=[NSTimer timerWithTimeInterval:DRAW_INTERVAL target:self selector:@selector(drawSubWithWait) userInfo:nil repeats:YES];
	AddTimerToRunLoop(drawThread); // 実行ループにタイマーを追加する
#else
	// メインスレッドからでないとタイマーがかからないので
	[self performSelectorOnMainThread:@selector(setDrawThread)
						   withObject:nil
						waitUntilDone:YES];
#endif
#endif
	nslog(@"pixel=%p",consolePixel);
	nslog(@"Console.start end");
}

// 描画周期起動タスクを入れる
-(void)setDrawThread
{
	@autoreleasepool { // 別スレッドのお約束
		// 1/30s単位で描画する
#if 1
		drawThread=[NSTimer scheduledTimerWithTimeInterval:DRAW_INTERVAL target:self selector:@selector(drawSubWithWait) userInfo:nil repeats:YES];
#else
		drawThread=[NSTimer timerWithTimeInterval:DRAW_INTERVAL target:self selector:@selector(drawSubWithWait) userInfo:nil repeats:YES];
		AddTimerToRunLoop(drawThread); // 実行ループにタイマーを追加する
#endif
		nslog(@"drawThread=%@",drawThread);
	}
}

-(void)start
{
	[self start:kCGImageAlphaPremultipliedLast]; // R/G/B/A
}

-(void)end
{
	printSelf();
	nslog(@"self=%@/updateCnt=%d",self,updateCnt);
	
	// アニメーションと変形の解除
	// 本来ここで行うべきであるが、そうすると実行時画面の表示で正しく、というか思っているように表示されなくなるので、次回実行にまで置いとくことにする
	[self animation:ANIMATION_STOP]; // アニメーションのみ停止する(V2.6)
//	[self transformCancel];

#ifdef SPDUP
	[drawThread invalidate];
//	[drawThread release];
	// 描画指示が残っていたらはき出す
	[self drawSubWithWait]; // = if (updateCnt>0) [self drawSub:DRAW_NOWAIT];
#endif
	while (self.imageRef!=nil); // 実描画が終わりimageRef=nilが設定されるまで待つ
	// その実描画の間に命令を受けている可能性もある
	if (imageCnt>0) {
		self.fimage=DRAW_WAIT; // KVO発行だけでOK
	}
#ifdef SPDUP
	while (self.imageRef!=nil); // 実描画が終わりimageRef=nilが設定されるまで待つ;DRAW_WAITなので不要な気もするが念のため
	// v2.4
	// 何故か最終描画が抜けることがあるが、理由がわからないので時間待ちすることにした
	WaitClass *wait=[[WaitClass alloc]init];
	[wait waitTime:DRAW_INTERVAL fwait:YES];
#endif
	
	CGContextRelease(context);
	context=NULL; // 開放した印
}

-(void)setKVO:(id)observer forKeyPath:(NSString *)keyPath
{
	printSelf();
	// KVO設定を認識するために内部に引きこむ
	[self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; // 更新通知;外しておくNSKeyValueObservingOptionOld
}

-(void)setKVO:(id)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options
{
	printSelf();
	// KVO設定を認識するために内部に引きこむ
	[self addObserver:observer forKeyPath:keyPath options:options context:NULL];
}

//---------------------------------------------------------------------------------------

-(void)dealloc
{
	printSelf();
	[self end];
}

//---------------------------------------------------------------------------------------
// 公開関数
//---------------------------------------------------------------------------------------

-(void)setFrotate:(int)frotate
{
	printSelfs(@"%d",frotate);
	frotate_=frotate;
	switch (frotate_) { // 定数:UIViewContentMode
		case ROTATE_ASPECTFIT:
			self.contentMode=UIViewContentModeScaleAspectFit;
			break;
		case ROTATE_SCALE2FIT:
		case ROTATE_RUNFIX:
			self.contentMode=UIViewContentModeScaleToFill;
			break;
	}
}

-(CGSize)getScreenSize
// 画面サイズを得る
{
	printSelf();
//	CGSize size=self.frame.size;
	CGSize size=frame0.size;
	return (size);
}

-(DEVICE_TYPE)getDeviceType
// 実行中のデバイス種別を返す
{
	printSelf();
	return(getDeviceType2());
}

-(NSUInteger)getScreenRotation
// 縦横方向を得る
{
	printSelf();
	return (getOrientation());
}

-(void)setHidden2:(BOOL)hidden
{
	printSelfs(@"%d",hidden);
//	nslog(@"%@",self);
	self.hidden=hidden;
	[self update];
}

//---------------------------------------------------------------------------------------

-(void)alpha:(CGFloat)alpha
{
	nslog(@"Console.alpha:%f",alpha);
	// v2.2即時反映処理追加
	self.alpha=alpha;
	[self update];
}

// r/g/b/alpha=0〜255
-(void)backgroundColor:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	printSelf();
	self.backgroundColor=[UIColor colorWithRed:_cf(r)
										 green:_cf(g)
										  blue:_cf(b)
										 alpha:_cf(alpha)];
	[self update];
}

// 各レイヤーに外枠をつける
-(void)border:(CGFloat)width r:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	printSelf();
	rgba_color color=getWithStandardColor(r,g,b,alpha);
	UIColor *uicolor=[UIColor colorWithRed:_cf(color.r) green:_cf(color.g) blue:_cf(color.b) alpha:_cf(color.alpha)];
	nslog(@"width=%f,color=%@",width,uicolor);
	self.layer.borderColor=uicolor.CGColor;
	self.layer.borderWidth=width;
	[self update];
}

//---------------------------------------------------------------------------------------
// 実描画
//---------------------------------------------------------------------------------------

// 実描画は行わないが、KVOを発生させてメインスレッドを呼び出す
// 描画は伴わないが画面更新が必要な処理用
-(void)update
{
	printSelf();
	if (!fskipUpdate) {
		if (context!=NULL) {
			pushContext();
#ifdef SPDUP
			[self draw];
			UIGraphicsPopContext2();
#else
			[self draw];
#endif
		} // context未設定時は設定を覚えておくだけ
	} else {
		nslog(@"スキップ");
	}
}

-(void)draw
{
	[self draw:DRAW_NOWAIT];
}

// これを呼び出す前にpushContext()を呼び出しておくこと
-(void)draw:(int)mode
{
	printSelfs(@"%@",self);
	if (!fskipUpdate) {
#ifdef SPDUP
		self.updateCnt++; // これだけで良い
#else
		[self drawSub:mode];
#endif
	} else nslog(@"スキップ");
	// これだけは通さないと1回の描画が完結しない
	UIGraphicsPopContext2();
}

#ifdef SPDUP
// 周期起動される
-(void)drawSubWithWait
{
//	printSelf();
	if (self.updateCnt>0) {
		[self drawSub:DRAW_NOWAIT];
		updateCnt=0; // 描画済
	} // 描画命令がないときは何もしない
}
#endif

-(void)drawSub:(int)mode
{
//	printSelfs(@"%d",mode);
	if (self.imageRef) { // 前の描画がまだ終わってない時にここに来た時
		imageCnt++; // 回数だけ数えておく
//		nslog(@"imageCnt:%d",imageCnt);
	} else {
//		nslog(@"新描画KVO:%@",self);
		// CGBitmapContextCreateImage()はコピーを作るのでこの後操作してもimageRef内容は変わらない、らしい
		// 現在の内容をそのまま再描画するときもこれが必要
		imageRef=CGBitmapContextCreateImage(context);
		// KVO発生
		self.fimage=mode;
	}
}

//---------------------------------------------------------------------------------------

// 実描画用スレッド(メインスレッド)
// KVO経由で起動がかかる:runViewController:observeValueForKeyPathから
-(void)displayMainThread
{
	@autoreleasepool { // 不要かもしれないけど、別スレッド実行のでのお約束
//	printSelfs(@"%p...",self);
	BOOL ffirst=YES;
	do {
		// ここに来るまでに次の描画指示が入っていた時は新画像に置き換える
		if (self.imageCnt>0) {
			if (ffirst) {
				// いきなり更新の時は、前のを開放する
				CGImageRelease(imageRef);
//				printf("いきなり更新...");
			} else {
				// ここに来るときは1回前のが(下で)CGImageRelease()されている
//				printf("repeat...");
			}
			imageRef=CGBitmapContextCreateImage(context);
		}
		imageCnt=0;
		// ここで毎回CGBitmapContextCreateImage()すると動かなくなってしまう事がある
		self.layer.contents=(__bridge id)imageRef; // これが遅い
		CGImageRelease(imageRef);
		
		ffirst=NO;
		// ここに来るまでに次の描画指示が入っていた時は継続
	} while (self.imageCnt>0);
//	nslog(@"...end\n");
	imageRef=nil;	// ここまで次の描画は無視される
	}
}
// (1)imageRefがnilになる＝描画が終わるまで次のKVOは発生させず、その間の描画は全てcontext中にて行われる。その間imageCntが++される
// (2)displayMainThread終了までに他の描画命令があった場合=imageCnt>0の時は、contextから画像を取り出して描画する。この間も(1)が適応される
// (3)高速動作していると、displayMainThreadが実際に呼び出されるまでに次のKVOが発生することもある

//---------------------------------------------------------------------------------------

-(void)antiAliasing:(BOOL)falias
// アンチエイリアシングを有効にするかどうか(V2.1)
{
	printSelfs(@"%@",self);
	CGContextSetAllowsAntialiasing(context, falias);
	CGContextSetShouldAntialias(   context, falias);
	// これは内部設定だけなのでupdate不要
}

-(void)fontSmoothing:(BOOL)fsmooth
// フォントのスムージングを有効にするかどうか(V2.1)
{
	printSelfs(@"%@",self);
	CGContextSetAllowsFontSmoothing(context, fsmooth);
	CGContextSetShouldSmoothFonts(  context, fsmooth);
	// これは内部設定だけなのでupdate不要
}

//---------------------------------------------------------------------------------------

-(void)wipe
// クリア
{
	printSelf();
	pushContext();
	printFrame(@"bounds", self.bounds);
	CGContextClearRect(context,self.bounds); // self.frameだと画面上の絶対座標になるので
	printf("wipe-");
	[self draw:DRAW_WAIT]; // 念のため、クリアの後は待ちを入れておく;これを外すとやはり直後のprintが消えることがある
//	[self draw];
}

//---------------------------------------------------------------------------------------
// スクロール(V2.0)
//---------------------------------------------------------------------------------------

// テキスト型スクロール
// 上下のみ、スクロール領域は消す
-(void)txScroll:(int)mode value:(int)sc fContext:(BOOL)fContext
{
	printSelf();
	nslog(@"mode=%d,value=%d,fContext=%d",mode,sc,fContext);
#if 0
	static int cnt=0;
	char fname[64];
	sprintf(fname,"/test/text%d.dat",cnt++);
	FILE *fp=fopen(fname,"w");
	if (fp!=NULL) {
		fwrite(pixel,4,long1Line*bitmapHeight,fp);
		fclose(fp);
	} else {
		nslog(@"file can not make");
	}
#endif
	// CGContext*()は座標がQuartz系である。
	// 故に、CGBitmapContextCreateImage(context)をそのまま表示すると上下反転の鏡像で表示されてしまう。
	// それを正常表示するため、consoleは.layerにて表示が上下胸像になるように設定してある。
	// 内包するビットマップもQuartz系座標のため、内部処理は上下逆さまに行なっておく必要がある(x方向は変わりない)。
	//	bitmap/scroll/sprite
	// CGContextDrawImage()は例外的にUIImageをその方向のままビットマップに展開するため同様となる。

	// 故に、スクロールもv2.0とは正反対に処理して同等となる

	// ものすごく力任せなので、iPad1では若干重たい
	if (fContext) {
		pushContext();
	}
	ULong *src,*des;
	ULong *Lpixel=(ULong *)consolePixel;
	int long1Line=(int)(consoleByte1Line/sizeof(SLong));
	int i;
	switch (mode) {
//		case SCROLL_UP:		// v2.0時
		case SCROLL_DOWN:	// v2.1
			// スプロールダウン
			src=Lpixel+long1Line*sc;
//			nslog(@"src=%p,Lpixel=%p,long1Line=%d,sc=%d,consoleHeight=%d",src,Lpixel,long1Line,sc,consoleHeight);
			for (i=0;i<(long1Line*(consoleHeight-sc));i++) {
				*Lpixel++=*src++;
			}
			// 上がった部分をクリア
			for (i=0;i<long1Line*sc;i++) {
				*Lpixel++=0;
			}
			break;

//		case SCROLL_DOWN: 	// v2.0時
		case SCROLL_UP: 	// v2.1
			// スプロールアップ
			// 後ろから
			des=Lpixel+long1Line*consoleHeight;
			src=des-long1Line*sc;
			for (i=0;i<(long1Line*(consoleHeight-sc));i++) {
				*--des=*--src;
			}
			// 下がった部分をクリア
			for (i=0;i<long1Line*sc;i++) {
				*--des=0;
			}
			break;
	}
	if (fContext) {
		[self draw:DRAW_NOWAIT];
	}
	nslog(@"txScroll end");
}

//---------------------------------------------------------------------------------------

-(void)grScroll:(int)mode fContext:(BOOL)fContext
// グラフィック型スクロール
// 上下左右でつながっている
{
	printSelf();
	nslog(@"mode=%d,fContext=%d",mode,fContext);
	// ものすごく力任せなので、iPad1では若干重たい
	if (fContext) {
		pushContext();
	}
	ULong *buffer=(ULong *)alloca(consoleByte1Line); // ローカルバッファー確保
#if 0
	if (buffer==NULL) {
		nslog(@"buffer can not alloc.");
	}
#endif
	ULong *Lpixel=(ULong *)consolePixel;
//	nslog(@"consoleByte1Line=%ld/%ld",consoleByte1Line,consoleByte1Line/4);
	int long1Line=(int)(consoleByte1Line/sizeof(SLong));
	int i;
	ULong *src,*des;
	switch (mode) {
//		case SCROLL_UP:		// v2.0時
		case SCROLL_DOWN:	// v2.1
			// スプロールダウンアップ
		{	// 以下、上下逆に読むこと
			src=Lpixel; // 最上行を保存
			// 最上行をバッファーに保存
			des=buffer;
			for (i=0;i<long1Line;i++) {
				*des++=*src++;
			}
			// スクロールアップ
			for (i=0;i<long1Line*(consoleHeight-1);i++) {
				*Lpixel++=*src++;
			}
			// バッファーから最下行に戻す
			for (i=0;i<long1Line;i++) {
				*Lpixel++=*buffer++;
			}
		}
			break;
			
//		case SCROLL_DOWN: 	// v2.0時
		case SCROLL_UP: 	// v2.1
			// スプロールアップ
		{	// 以下、上下逆に読むこと
			// 後ろから
			// 最下行をバッファーに保存
			src=Lpixel+long1Line*consoleHeight;
			des=buffer+long1Line;
			for (i=0;i<long1Line;i++) {
				*--des=*--src;
			}
			// スクロールダウン
			des=src+long1Line;
			for (i=0;i<(long1Line*(consoleHeight-1));i++) {
				*--des=*--src;
			}
			// バッファーから最上行に戻す
			src=buffer+long1Line;
			for (i=0;i<long1Line;i++) {
				*--des=*--src;
			}
		}
			break;
		
		case SCROLL_RIGHT:
			des=src=Lpixel+long1Line*consoleHeight;
			for (i=0;i<consoleHeight;i++) {
				ULong c=*--src;
				for (int j=0;j<long1Line-1;j++) {
					*--des=*--src;
				}
				*--des=c;
			}
			break;
		
		case SCROLL_LEFT:
			for (i=0;i<consoleHeight;i++) {
				des=Lpixel;
				ULong c=*Lpixel++;
				for (int j=0;j<long1Line-1;j++) {
					*des++=*Lpixel++;
				}
				*des++=c;
			}
			break;
	}
	if (fContext) {
		[self draw:DRAW_NOWAIT];
	}
	nslog(@"grScroll end");
	
#if 0	//!!test!!
	char fname[64];
	sprintf(fname,"/test/bitmap%d.dat",0);
	[self pixelSaveToFile:fname];
#endif

}

//---------------------------------------------------------------------------------------

#if 0	//!!test!!
-(void)pixelSaveToFile:(char *)fname
// これはデバッグ用
{
	nslog(@"bitmapSaveToFile:%s",fname);
	FILE *fp;
	fp=fopen(fname,"w");
	if (fp!=NULL) {
		nslog(@"consolePixel=%p,consoleByte1Line=%ld,consoleHeight=%d",consolePixel,consoleByte1Line,consoleHeight);
		if (fwrite(consolePixel,consoleByte1Line,consoleHeight,fp)<consoleHeight) {
			nslog(@"file write error");
		}
		fclose(fp);
	} else {
		nslog(@"file open error");
	}
}
#endif

//---------------------------------------------------------------------------------------
// deallocは上の方

@end
