//
//  Tegaki.m
//	手書き処理；1ページ分
//  traceWrite
//
//  Created by 今井 彰彦 on 2011/11/08.
//  Copyright (c) 2011 AIG-Soft. All rights reserved.
//

#import "Tegaki.h"
#import "click.h"
#import "libs.h"
#import "RootViewController.h"
#import "SnapShot.h"
#import <QuartzCore/CoreAnimation.h>	// QuartzCore.frameworkが必要
#ifdef DEBUG
#import "FileControl2.h"
#endif

@implementation TegakiView

//-------------------------------------------------------------------
// 初期化
//-------------------------------------------------------------------

-(void)initTegaki:(id)delegate_ scrollView:(UIScrollView *)scrollView_ imageView:(UIImageView *)imageView_
{
	nslog(@"Tegaki:init/sizeof(LINES)=%lu",SZ_LINES);
	delegate		=delegate_;
	scrollView		=scrollView_;
	imageView		=imageView_;
	//
	_penColor		=PENCIL_COLOR_BLACK;
	_penWidth		=DEF_PENCIL_WIDTH;
	//
	atrb			=kTouchesBegan;
	fsnapShot		=FSNAPSHOT_ALL;	// SnapShot実取得必要
	//
	self.backgroundColor=[UIColor clearColor]; // 背景透過のため
}

//-------------------------------------------------------------------
// 画面タッチイベント
//-------------------------------------------------------------------

// touchesBegan->touchesMoved->touchesEnded
// なぜかEndedが入ってないことがある

-(CGPoint)getTouchPoint:(NSSet *)touches
{
	CGPoint point=[[touches anyObject]locationInView:self];
	if (scrollView!=nil) {
		point.x+=scrollView.contentOffset.x; // 内部座標に変換
		point.y+=scrollView.contentOffset.y;
	}
	//nslog(@"(%f,%f)",point.x,point.y);
	return point;
}

// 画面タッチ開始
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//	printSelf();
//	clickSound();
	[delegate disappearPenWidth]; // ペン幅設定が表示されてたら消す
	//
	LINES line1;
	CGPoint point=[[touches anyObject]locationInView:self];
	line1.point=point;
	if (scrollView!=nil) {
		line1.point.x+=scrollView.contentOffset.x;
		line1.point.y+=scrollView.contentOffset.y;
	}
//	nslog(@"(%f,%f)",line1.point.x,line1.point.y);
	line1.atrb		=atrb=kTouchesBegan;
	line1.penColor	=_penColor;
	line1.penWidth	=_penWidth;
	[_lineArray addObject:[NSValue value:&line1 withObjCType:@encode(LINES)]];
	point0=point; // 差分計算用(画面内座標)
//	nslog(@"touchesBegan(%d):%f,%f",[_lineArray count],point0.x,point0.y);
	// ここは始点なので描画しない
}

// 最終データのatrbをendedに変更する
-(void)touchesLatestEnd
{
	printSelf();
	LINES line1;
	// 最終データを読み出す
	NSValue *val=[_lineArray lastObject];
	[val getValue:&line1];
	// 最終データを消す
	[_lineArray removeLastObject];
	// それをendedにする
	line1.atrb=kTouchesEnded;
	[_lineArray addObject:[NSValue value:&line1 withObjCType:@encode(LINES)]];
}

// ポイントの記録(Moved/Ended用)
-(BOOL)touches:(NSSet *)touches atrb:(ushort)atrb_
{
	LINES line1;
	CGPoint point=[[touches anyObject]locationInView:self];
	line1.point=point;
	if (scrollView!=nil) {
		line1.point.x+=scrollView.contentOffset.x;
		line1.point.y+=scrollView.contentOffset.y;
	}
	if (atrb_==kTouchesMoved) {
		CGFloat wd=_penWidth/2; // width/2以下の動きの時は記録しない(width以下にすると、太文字の時に動きが雑になりすぎる)
		if (fabs(point0.x-line1.point.x)<=wd && fabs(point0.y-line1.point.y)<=wd) {
			// wd以下の動きの時は記録しない
			// データ量を減らすため
			nslog(@"skip");
			return YES;
		}
	} // Ended＝終点は座標によらず記録する
	line1.atrb		=atrb=atrb_;
	line1.penColor	=_penColor;
	line1.penWidth	=_penWidth;
//	nslog(@"_lineArray[%d]",[_lineArray count]);
	[_lineArray addObject:[NSValue value:&line1 withObjCType:@encode(LINES)]];
//	nslog(@"touches(%d):%f,%f",[_lineArray count],line1.point.x,line1.point.y);
	//
	// 再描画領域を計算する(画面内)
	CGFloat sx=point0.x,sy=point0.y,ex=point.x,ey=point.y;
	// sx,sy<=ex,eyにする
	if (sx>ex) {CGFloat x=sx; sx=ex; ex=x;}
	if (sy>ey) {CGFloat y=sy; sy=ey; ey=y;}
	// ペン幅を考慮する
	if (sx>_penWidth) sx-=_penWidth;
	if (sy>_penWidth) sy-=_penWidth;
	ex+=_penWidth;
	ey+=_penWidth;
	CGRect rect={sx,sy,ex-sx+1,ey-sy+1};
//	printFrame(@"画面内再描画",rect);
	// 再描画指示
	[self setNeedsDisplayInRect:rect]; // 再描画範囲を最小限にして高速化する(わずかに早くなる)->drawRectが呼び出される
	EnterRunLoop(0.0);	// できるだけ早くdrawRectを呼び出させるため
	//
	point0=point; // 差分計算用(画面内座標)
	//
	return NO;
}

// ドラッグ
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
//	printSelf();
	// movedがないと再描画はない(begin->endedは消す)
	fsnapShot=FSNAPSHOT_YES; // snapshot必要
	[self touches:touches atrb:kTouchesMoved];
}

// 画面タッチ終了
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//	printSelf();
	[self touches:touches atrb:kTouchesEnded];
}

// キャンセル
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
//	printSelf();
	// キャンセルしたときはkTouchesBeganまで消す
	// 消す＝元に戻るので、snapshotは不要
	[self undo:nil];
}

//-------------------------------------------------------------------
// 描画処理;内部座標
//-------------------------------------------------------------------

#define LINES_MAX	50
static CGPoint lines[LINES_MAX];

// lines[]内の線分群を描画する
void stroke1(CGContextRef c,NSInteger j,LINES *line1)
{
	CGContextSetStrokeColorWithColor(c,colorByNo(line1->penColor).CGColor); // 線色
//	nslog(@"_penWidth=%f",line1->penWidth);
	CGContextSetLineWidth(c,line1->penWidth);
	CGContextAddLines(c,lines,j);
	CGContextStrokePath(c); // 描画指定
//	nslog(@"j=%d",j);
}

// ワーク内全描画
// ワークは全画面分
- (void)reDrawAll
{
	printSelf();
	
	if ([_lineArray count]==0) {
		// 一番最初は描画する内容が無いので弾く
		nslog(@"一番最初は描画する内容が無いので弾く");
		return;
	}
	
	CGContextRef c = UIGraphicsGetCurrentContext();
	//	nslog(@"UIGraphicsGetCurrentContext=%@",c);
	
	CGContextSetLineJoin(c,kCGLineJoinRound);	// 線の結合方式のみ
	CGContextSetLineCap( c,kCGLineCapRound );	// 線の終端の指示
	
	NSValue *val;
	LINES line1;
	
	// 最終ストロークには次の2つのパターンがあり得る
	// (1)ストローク途中;Endedがない状態
	// (2)最終ストロークの後、次のストロークが始まっていない;Endedまで入っている
	
	NSUInteger j=0;
	//	NSInteger i=0;
	NSEnumerator *emu=[_lineArray reverseObjectEnumerator]; // 逆から読み出す
	// 逆からなのでended/moved→beginの順になる;始まりがどちらであるかは処理に影響しない
	// 逆からの場合は必ずbeginが終点として存在する
	while (val=[emu nextObject]) { // 一番最初はデータがないので描画されない
		[val getValue:&line1];
		//nslog(@"(%f,%f)",line1.point.x,line1.point.y);
		//
		lines[j++]=line1.point;
		//
		switch (line1.atrb) {
			case kTouchesMoved:
			//	nslog(@"kTouchesMoved");
				if (j>=LINES_MAX) {
					stroke1(c,j,&line1);
					lines[0]=lines[j-1]; // 最終ポイントを最初ポイントにして継続
					j=1;
				}
				break;
			case kTouchesBegan:
			//	nslog(@"kTouchesBegan");
				stroke1(c,j,&line1);
				j=0;
				break;
			case kTouchesEnded:
			//	nslog(@"kTouchesEnded");
				// 記録は済んでいるのでここでは何もしなくて良い
				break;
		}
	}
//	nslog(@"reDrawAll end");
}

//-------------------------------------------------------------------
// 表示
//-------------------------------------------------------------------

// 表示描画(ワーク内も共通)
// 表示内は毎回全ストロークを再描画する必要がある(前回の描画が消えてしまうから)
// snapshotするまでの間のみ必要
- (void)drawRectSub:(UIScrollView *)scrollView_
{
//	printSelf();
//	nslog(@"drawStart=%d〜%d",drawStart,[_lineArray count]);
	
	if ([_lineArray count]==drawStart) {
		nslog(@"再描画する内容が無いので弾く");
		return;
	}
	// UIGraphicsGetCurrentContext()はdrawRect:中でのみ値を返す(故に、init内で設定しておくようなことはできない)
	CGContextRef c = UIGraphicsGetCurrentContext();
	
	CGContextSetLineJoin(c,kCGLineJoinRound);	// 線の結合方式のみ
	CGContextSetLineCap( c,kCGLineCapRound);	// 線の終端の指示
	
	NSValue *val;
	LINES line1;
	
	for (NSInteger i=drawStart;i<[_lineArray count];i++) {
		// その実座標を読み出す
		val=_lineArray[i];
		[val getValue:&line1];
		// 1ストローク分描画
		lines[0]=line1.point; // line1はbeginのはず
		
		NSUInteger j=1;
		// 表示座標化
		if (scrollView_!=nil) {
			lines[0].x-=scrollView_.contentOffset.x;
			lines[0].y-=scrollView_.contentOffset.y;
		}
#if	0 // 始点に丸をつける
		CGContextSetFillColorWithColor(c,[UIColor redColor].CGColor); // 塗りつぶし色指定;線とは別指定しないといけない
		CGContextAddArc(c,lines[0].x,lines[0].y,6,0,2*M_PI,1); // 0〜2*M_PI=全周,1=時計方向描画,円が描かれるので、半径は幅の半分
#endif
		while (++i<[_lineArray count]) {
			//nslog(@"_lineArray[%d]",i);
			val=_lineArray[i];
			[val getValue:&line1];
			//nslog(@"val(%d)=%f,%f:col=%d",i,line1.point.x,line1.point.y,line1._penColor);
			// 表示座標化
			if (scrollView_!=nil) {
				line1.point.x-=scrollView_.contentOffset.x;
				line1.point.y-=scrollView_.contentOffset.y;
			}
			lines[j++]=line1.point;
			switch (line1.atrb) {
				case kTouchesMoved:
					//	nslog(@"kTouchesMoved");
					if (j>=LINES_MAX) {
						stroke1(c,j,&line1);
						lines[0]=lines[j-1]; // 最終ポイントを最初ポイントにして継続
						j=1;
					}
					break;
				case kTouchesBegan:
					// なぜかEndedが抜けていることがあるので対応
					j--; // 上で入れたlines[j++]を無効にする
					//	nslog(@"kTouchesBegan:j=%d",j);
					// 描画させて終了
				case kTouchesEnded:
					//	nslog(@"kTouchesEnded:j=%d",j);
					goto DS1;
			}
		}
	DS1:;
		if (j>=2) {
			stroke1(c,j,&line1);
		} // j>=LINES_MAXで描画した直後に終わったときは残り描画は不要
#if	0 // 終点に丸をつける
		CGContextSetFillColorWithColor(c,[UIColor magentaColor].CGColor); // 塗りつぶし色指定;線とは別指定しないといけない
		CGContextAddArc(c,line1.point.x,line1.point.y,3,0,2*M_PI,1); // 0〜2*M_PI=全周,1=時計方向描画,円が描かれるので、半径は幅の半分
#endif
	}
}

// 表示描画(ワーク内も共通)
// 表示内は毎回全ストロークを再描画する必要がある(前回の描画が消えてしまうから)
// snapshotするまでの間のみ必要
- (void)drawRect:(CGRect)rect
{
//	printSelf();
	
	[self drawRectSub:scrollView];
}

//-------------------------------------------------------------------
//　Undo
//-------------------------------------------------------------------

// UNDO=最終ストロークを削除する
-(void)undo:(id)sender
{
	printSelf();
	[delegate disappearPenWidth]; // ペン幅設定が表示されてたら消す
	//
	if ([_lineArray count]==0) return; // データがないときはここまで
	//
	// 最終から直前のbeginまでを削除する
	NSEnumerator *emu=[_lineArray reverseObjectEnumerator]; // 配列の内容を最後から前に順に呼び出す
	NSValue *val;
	while (val=[emu nextObject]) {
		LINES line1;
		[val getValue:&line1];
	//	nslog(@"(%f,%f):%d",line1.point.x,line1.point.y,line1.atrb);
		[_lineArray removeLastObject]; // 要素の持つ領域も解放するはず
		if (line1.atrb==kTouchesBegan) break;
	}
	// 内部ワークでの再描画は必要時に行えばいいのでここでは必要ない
	drawStart=0;				// 最初から再描画
	[self setNeedsDisplay];
	imageView.image=nil;		// 一旦画像を全部消す;こうしておかないとUNDOで線が消えない
	fsnapShot=FSNAPSHOT_ALL;	// snapshot必要
	[self imageSnapShot];		// ここでdrawStartが最設定される
	// 並行動作すると、消した後に表示されたりするので直動作
	EnterRunLoop(0);
}

//-------------------------------------------------------------------
// ページクリア
//-------------------------------------------------------------------

-(void)clearPage1
// ストロークページクリア実処理
{
	printSelf();
	[_lineArray  removeAllObjects];
	drawStart=0; // 正確には[_lineArray count];
	//
	imageView.image=nil; // 画像を消す
	[self setNeedsDisplay];	 // drawRectが呼び出される
	fsnapShot=FSNAPSHOT_ALL; // snapshot必要
	// 表示上で消えてればいいので、実snapshotは後でいい
}

//-------------------------------------------------------------------
// レンダリング処理
//-------------------------------------------------------------------

// レンダリング処理
// どこに描画するかは呼び出し側が設定した領域による
-(void)reRendering
{
	printSelf();
	switch (fsnapShot&(~FSNAPSHOT_DISABLE_CONCURRENT)) {
		case FSNAPSHOT_ALL:
			nslog(@"FSNAPSHOT_ALL");
			[self reDrawAll];
			break;
		case FSNAPSHOT_YES:
			nslog(@"FSNAPSHOT_YES");
			[self drawRectSub:nil];
			break;
	}
	snapShot1(UIGraphicsGetCurrentContext(),imageView); // imageViewは大きさの指定だけ
}

// 1ページをUIImage化する
-(UIImage *)snapShot
{
	// SnapShot実取得
//	printSelf();
//	nslog(@"%f,%f",_contentSize.width,_contentSize.height);
	//
	UIGraphicsBeginImageContext(_contentSize);
	//
	[self reRendering]; // 再描画
	//
	UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // autorelease objectであると書いてある
#if 0	// JPEGファイル化
	NSData *imgdata=UIImageJPEGRepresentation(image,0.5);
	[imgdata makeFullPathForSaveData(@"snapShot.jpg") atomically:YES];
#endif
	UIGraphicsEndImageContext();
	return (image);
}

-(void)imageSnapShot
{
	printSelfs(@"fsnapShot=%d",(int)fsnapShot);

	// この処理は並行動作される

	if (fsnapShot&FSNAPSHOT_DISABLE_CONCURRENT) {
		nslog(@"SnapShot禁止");
		return;
	}
	if (fsnapShot==FSNAPSHOT_NO) {
		// 再取得の必要はないので、前の取得結果を返す
		nslog(@"SnapShot再取得必要なし");
		return;
	}
	fsnapShot|=FSNAPSHOT_DISABLE_CONCURRENT; // 並行動作禁止の印
	// ↑ここを実行中にまた呼び出されたときは上の判定で弾かれるように先に設定する(並行動作しているからありえる)
	//
	// この処理は並行動作される
	// したがって、main()とは異なるautorelease poolが必要になる;極めて重要
    @autoreleasepool {
		imageView.image=[self snapShot];
	//
    }
	//
	fsnapShot=FSNAPSHOT_NO; // snapshotを撮った。次回画面が更新されるまで不要
	// UIView再描画対象はここから
	drawStart=[_lineArray count];
}

// UIView等描画用PDF作成
//NSMutableData *pdfData=[[NSMutableData alloc]init ];
- (void)savePDFFileWithImage:(NSMutableData *)pdfData
{
	printSelf();
	//
#define FNTSIZE	18
#define FNTOFST	4
	// 画像サイズ+下部に日時を入れる分に合わせる
	CGRect rect=CGRectMake(0,0,_contentSize.width,_contentSize.height+FNTSIZE);
	//
	UIGraphicsBeginPDFContextToData(pdfData, rect, nil);
	// ページ始まり
	UIGraphicsBeginPDFPageWithInfo(rect, nil);
	// 描画
	[self reRendering];
	//
#if 0
	NSString *string  = makeNowDateStr(); // 現在日時の文字列を得る
	UIFont *font	  = [UIFont systemFontOfSize:FNTSIZE-FNTOFST];
	CGSize stringSize = [string sizeWithFont:font];
	CGRect stringRect = {0,_contentSize.height+FNTOFST,stringSize.width,stringSize.height};
	[string drawInRect:stringRect withFont:font];
	[string release];
#endif
	//
	// PDF contextを閉じ、内容を書き出す
	UIGraphicsEndPDFContext();
}

// 添付ファイル実体を返す
-(NSData *)getAttach
{
	printSelf();
	// imageは直接記録できないので、再圧縮してNSData化して記録する
	[self imageSnapShot]; // 添付取得用なので終了待ちする
	NSData *imgdata=UIImageJPEGRepresentation(imageView.image,0.8); // tegaki.image
#ifdef DEBUG	// JPEGファイル化
	[imgdata writeToFile:makeFullPathForSaveData(@"tegaki.jpg") atomically:YES];
#endif
	return imgdata; // 外部でreleaseすること
}

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


@end
