//
//  Yoko2Tate.m : 縦書き専用
//  traceWrite
//
//  Created by 今井 彰彦 on 2011/12/07.
//  Copyright (c) 2011 AIG-Soft. All rights reserved.
//
// zoom対応は、基本外部で行うこと

#import "Yoko2Tate.h"
#import "ScrollView.h"
#import "libs.h"
#import "NSString+sizeWithFont.h"
#import "NSString+drawiOS7.h"

@implementation Yoko2Tate

/*
 横→縦書き変換
 指定:縦文字数

 テキストファイル内;UTF-8
 全角文字のみ
 全角スペース	スペーサー
 /			強制改行

 表示文字サイズはUITextViewで指定する

 ↓縦最大文字数;tcharMax/R	指定/実文字数
 0123456789		→(1)	0
 ABCDEFGHIJ		↓(2)
 〜
 pqrstuvwxyz			lines-1
						lines
*/

//----------------------------------------------------------------------------
// 初期化・終了時処理
//----------------------------------------------------------------------------

-(id)init
{
	printSelf();
	self = [super init];
	if (self!=nil) {
		_lineStr			=[[NSMutableArray alloc]init]; // NSMutableString []
		_scrollAreaArray	=[[NSMutableArray alloc]init]; // CGRect []
		_lineAreaArray	=[[NSMutableArray alloc]init]; // CGRect []
		_ttext=nil;
	}
	return self;
}

-(void)dealloc
{
	printSelf();
	NSEnumerator *emu=[_lineStr objectEnumerator];
	NSMutableString *str;
	while (str=[emu nextObject]) {
		nslog(@"str=%@",str);
	}
}

//----------------------------------------------------------------------------
// メソッド
//----------------------------------------------------------------------------

// 文字チェック
NSInteger checkChar(NSString *text,NSInteger *i_,NSString *(*c_))
{
	NSInteger i=*i_;
	NSString *c=[text substringWithRange:NSMakeRange(i++,1)]; // 文字列から一文字ずつ取り出す
	*i_=i; // 次の文字位置
	if ([c isEqualToString:CR] || [c isEqualToString:SPC] || [c isEqualToString:TAB] || [c isEqualToString:LF]) {
		// 無視対象文字
		nslog(@"無視対象文字");
		return (CHAR_SKIP);
	}
	if ([c isEqualToString:Force]) { // 特殊文字 : /?
		c=[text substringWithRange:NSMakeRange(i,1)]; // 文字列から一文字ずつ取り出す
		nslog(@"[/%@]",c);
		*i_=i+1;
		if ([c isEqualToString:ForceCR   ])	return (CHAR_CRLF);
		//
		if ([c isEqualToString:ForceSpace]) c=@" ";
		else
		if ([c isEqualToString:ForceSlash]) c=@"/";
		// ?が規定文字ではないときは?を返す
	}
	// 通常文字時のみ、その文字を返す
	nslog(@"[%@]",c);
	*c_=c;
	return (CHAR_NORMAL);
}

// Pass1 : 行数を算出する
NSInteger Pass1(NSString *text,NSInteger charMax)
{
	printFunc();
	NSString *c;
	NSInteger charMaxR=0;	// 実際の横最大行数<=charMax
	NSInteger ychars=0;		// 横/縦文字数
	for (NSInteger i=0;i<[text length];) {
		switch (checkChar(text,&i,&c)) {
			case CHAR_SKIP:
				// 物理改行/スペース/タブは無視する
				//nslog(@"物理改行/スペース/タブは無視する");
				continue; // forへ
			default:
				if (++ychars<charMax) break;
				// 横文字数が指定数を超える
			case CHAR_CRLF:
				if (ychars>charMaxR) charMaxR=ychars; // 横文字数が最大となる行を探している
				ychars=0;
				break;
		}
	} // for
	nslog(@"charMaxR=%d",(int)charMaxR);
	return (charMaxR);
}

// Pass2:1行分の処理
NSInteger Pass2main(NSString *text,NSInteger charMaxR,NSInteger i,NSMutableString *str)
{
	NSString *c;
	NSInteger ychars=0; // 横文字数
	do {
		switch (checkChar(text,&i,&c)) {
			case CHAR_SKIP:
				continue; // 物理改行/スペース/タブは無視する(whileへ)
			case CHAR_CRLF:
				nslog(@"強制改行");
				if (ychars==0) continue; // 行頭に強制改行が来るときは無視
				goto Pass22;
		}
		[str appendString:c]; // NSMutableString <<-文字
		ychars++;
		if (ychars>=charMaxR) { // 横文字数が指定数を超える
			if (i>=[text length]) break; // ここで文字群が終わりの時
			// まだ次があるとき
			switch (checkChar(text,&i,&c)) {
				case CHAR_CRLF: // 改行を行った次の一文字が強制改行だった時
					nslog(@"強制改行を消す");
					break;
			}
			break;
		}
	} while (i<[text length]);
Pass22:;
#ifdef DEBUG
	if (ychars<charMaxR) {
		nslog(@"文字数に足りない分を全角スペースで埋める:ychars=%d/charMaxR=%d",(int)ychars,(int)charMaxR);
	}
#endif
	// 文字数に足りない分を全角スペースで埋める
	for (;ychars<charMaxR;ychars++) {
		[str appendString:ZSPC]; // 全角スペース
	}
	return (i);
}

// 横縦変換
// text     : 元横書き文字列
// tcharMax : 縦最大文字数
-(void)yoko2Tate:(NSString *)text tcharMax:(NSInteger)tcharMax;
{
	printSelf();
	nslog(@"元=%@/tcharMax=%d",text,(int)tcharMax);

	// Pass1
	nslog(@"Pass1:縦行数を算出する");
	// 結局のところ、横の文字数が縦の文字数になる
	// 行数が縦行数になる
	NSInteger tcharMaxR=Pass1(text,tcharMax); // 実際の縦最大行数<=tcharMax	
	
	// Pass2
	nslog(@"Pass2:縦の1行分の文字群をNSMutableStringに格納し、その1行分をNSMutableArrayに入れる");
	NSMutableArray *linesArray=[[NSMutableArray alloc]init];
	NSInteger lines=0;  // 縦行数
	NSInteger i=0;
	while (i<[text length]) {
		NSMutableString *str=[[NSMutableString alloc]init]; // 1行分のワークを確保
		[linesArray addObject:str]; // ここで記録されるのは先頭アドレス(内容ではない)
		// 1行分の処理
		i=Pass2main(text,tcharMaxR,i,str);
	//	nslog(@"横1行文(%2d)=%@<",lines,str);
		lines++;
	} // while
	nslog(@"lines=%d",(int)lines);

	// Pass3
	nslog(@"Pass3:縦変換;左→右");
	NSMutableArray *tlineArray=[[NSMutableArray alloc]init];
	NSInteger tchars;	// 縦文字数
	for (tchars=0;tchars<tcharMaxR;tchars++) { // 何文字目を取り出すか
		NSRange range=NSMakeRange(tchars,1);
		NSMutableString *tstr=[[NSMutableString alloc]init];
		[tlineArray addObject:tstr]; // ワークアドレスを保存
		for (i=0;i<lines;i++) {
			[tstr appendString:[linesArray[i] substringWithRange:range]];
		}
	//	nslog(@"縦1行(%2d)=%@<",i,tstr);
	}
	{ // linesArray開放
	NSMutableString *str;
	NSEnumerator *emu=[linesArray objectEnumerator];
	while (str=[emu nextObject]) {
	}
	}

	_ttext=[[NSMutableString alloc]init];

	// Pass4
	nslog(@"Pass4:右→左変換+全体結合");
	// 縦は右から左なので、文字順も逆にする必要がある
	for (i=0;i<tcharMaxR;i++) {
		NSMutableString *tstr=tlineArray[i]; // 縦1行文を左→右の横にしたもの
		NSMutableString *str =[[NSMutableString alloc]init]; // 1行分のワークを確保
		[_lineStr addObject:str]; // アドレス保存
		// 1行分
		for (tchars=lines-1;tchars>=0;tchars--) { // 何文字目を取り出すか
			NSRange range=NSMakeRange(tchars,1);
			[str appendString:[tstr substringWithRange:range]];
		}
		//
	//	nslog(@"縦1行(%2d)=%@<",i,str);
		[str   appendString:CRLF]; // 改行を加える
		[_ttext appendString:str];
	}
	{ // tlineArray開放
	NSMutableString *str;
	NSEnumerator *emu=[tlineArray objectEnumerator];
	while (str=[emu nextObject]) {
	}
	}
	//
	// 終了処理
	nslog(@"終了処理");
	_tketas=lines;		// 結果の横文字数(桁数)
	_tlines=tcharMaxR;	// 結果の行数
	nslog(@"_tketas=%d/_tlines=%d",(int)_tketas,(int)_tlines);
//	nslog(@"縦文=%@",_lineStr);
	//
	nslog(@"yoko2Tate:結果\n%@",_ttext);
	//
	// 現在_ttextは使ってないので、省メモリのため解放する
#if 1
	_ttext=nil;
#endif
}

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

// 指定列の行以降がスペースかどうか調べる＋最終桁なら改行
-(BOOL)isTLeftSpace:(NSInteger)keta line:(NSInteger)line
{
	printSelf();
	nslog(@"%d,%d",(int)keta,(int)line);
	BOOL ret=YES; // とりあえず残り全部全角スペースの印
	if (line<_tlines) {
		for (;line<_tlines;line++) {
			// 指定行
			NSMutableString *line1=_lineStr[line];
			// 指定桁
			NSString *c=[line1 substringWithRange:NSMakeRange(keta,1)];
			//
			nslog(@"(%d,%d)=%@",(int)keta,(int)line,c);
			//
			if (![c isEqualToString:ZSPC]) {
				// 全角スペース以外がある
				ret=NO;
				break;
			}
		}
	} // else 最終桁以上になるので改行
	return ret;
}

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

// スクロールエリアの計算
// point : この行の文字列描画開始位置
// 先に_spSizeの設定が必要
-(void)calcScrollArea:(CGPoint)point fTopLastLine:(BOOL)fTopLastLine
{
	CGRect rect;
	rect.origin.x=point.x;
	rect.origin.y=point.y+SCAreaOffset;
	rect.size.height=SCAreaHeight;			// 縦幅
	rect.size.width =_tketas*_spSize.width;	// 横幅
	NSValue *val=[NSValue value:&rect withObjCType:@encode(CGRect)];
	[_scrollAreaArray addObject:val];
	// 塗りつぶし色指定
	if (fTopLastLine) { // 最終行だけ色を変える
		[[UIColor colorWithRed:0.0 green:0.0 blue:0.8 alpha:0.1]set]; 
	} else {
		[[UIColor colorWithRed:0.0 green:0.4 blue:0.1 alpha:0.1]set]; 
	}
	CGContextFillRect(UIGraphicsGetCurrentContext(),rect); // 四角で塗りつぶし
}

// テキストをイメージ化する
// スペーサーとして、上下左右にフォントサイズの半分の空白領域を設ける
// fscrollArea : NO=スクロールエリアを表示しない , YES=表示する
-(UIImage *)text2Image:(CGFloat)fontsize_ fontName:(NSString *)fontName_ fscrollArea:(BOOL)fscrollArea color:(UIColor *)color
{
	printSelf();
	fontName=fontName_;
	nslog(@"桁=%d*行=%d/font=%@(%f)",(int)_tketas,(int)_tlines,fontName,fontsize_);
	
	// フォント設定
	UIFont *font=[UIFont fontWithName:fontName size:fontsize_];
	nslog(@"font=%@",font);
	nslog(@"font.familyName=%@",font.familyName);
	nslog(@"font.fontName=%@",font.fontName);
	nslog(@"xHeight=%f/pointSize=%f/lineHeight=%f",font.xHeight,font.pointSize,font.lineHeight);
//	フォントのwidthはfontsize==pointSizeにほぼ等しいが、若干異なることがあるので_spSize.widthを使う
	
	/* 描画サイズを計算する
	|~~~~~~~~~~~+<-~~~~~~~~~~~size.width~~~~~~~~~~~~~~~~~~->+~~~~~~~|
	|			+-------------------------------------------+SCAreaOffset---+
				|スクロール領域	[0]							|				SCAreaHeight<_offset.y
				+-------------------------------------------+---------------+
	  _offset.x,y+-------------------------------------------+(_offset.x分)
				|フォント描画領域	[0]							|
				|<-          _spSize.width*_tketas          ->|
				+-------------------------------------------+---------------+---
				|スクロール領域	[1]							|				|
				+-------------------------------------------+-+				Height1Line
				|フォント描画領域	[1]							|_spSize.height	|
				|											| |				|
				+-------------------------------------------+-+-------------+---
					〜〜〜〜
				+-------------------------------------------+
				|スクロール領域	[_tlines-1]					|
				+-------------------------------------------+
				|フォント描画領域	[_tlines-1]					|
				|											|
				+-------------------------------------------+
				+-------------------------------------------+
				|スクロール領域	[_tlines]					|=下にもう1つスクロールエリアが存在する
				+-------------------------------------------+
	|~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~|
	*/
	// スペーサーとして、上下左右にフォントサイズの半分の空白領域を設ける
	_spSize=getFontSize(font);
	// v2.0:固定サイズに変更
	CGFloat	OFFSETY=(64/*_spSize.height*//OFFSETWWY);
	if (_spSize.height<64) {
		OFFSETY=(_spSize.height/OFFSETWWY);
	}
	SCAreaHeight=(OFFSETY/2-OFFSETY/10);		// スクロールエリアの高さ
	SCAreaOffset=((OFFSETY-SCAreaHeight)/2);	// offsetY内のスクロールエリアの相対位置;OFFSETYの中央
	_offset.x=_spSize.width/OFFSETWWX;	// 左側
	_offset.y=OFFSETY;					// 上側
	nslog(@"_offset=%f,%f",_offset.x,_offset.y);
	//
	// 描画領域サイズ計算
	// 現在は、行ごとに幅や高さが異なる状況には対応していない
	NSString *str;
	NSEnumerator *emu;
#if 1
	CGSize size={_tketas*_spSize.width,(_spSize.height+_offset.y)*_tlines}; // 文字列描画幅＝スクロールエリア幅
	nslog(@"実描画行サイズ=%f,%f",size.width,size.height);
	CGRect rect;
	rect.size=size;
	size.width +=_offset.x*2;
	size.height+=_offset.y;
#else // 残念ながら、横書きで正しくサイズが計算できていない（右側）
	CGRect  rect;
	CGFloat height=0;
	CGSize  size  ={0,0}; // Analyzeの警告回避のため
	emu=[_lineStr objectEnumerator];
	while (str=[emu nextObject]) {
	//	nslog(@"str=%@<",str);
		height+=_offset.y;
		// 描画文字列のサイズを得る(1行分);改行が入ったままの文字列を与えると、最終行の幅になってしまう
		// size.heightはfont.lineHeightが返ってくる
		CGSize size0 = [str sizeWithFont:font];
		if (size0.width>size.width) size.width=size0.width; // 最大幅を取る
		size.height=_spSize.height; // lineHeightでは縦に余裕ができすぎるので
	//	nslog(@"1行描画size=%f,%f",size.width,size.height);
		// size.width = 横幅(全行で共通)
		// size.height= 1行分の幅
		height+=size.height;
	}
	size.height=height+_offset.y; // 下分
	size.width+=_offset.x*2;		 // 左右分
#endif
	_areaSize=size; // 全領域サイズ記録
	nslog(@"総サイズ:%f*%f",size.width,size.height);
	
	// サイズでワーク確保
	UIGraphicsBeginImageContext(size);
	
	// 1行ずつ描画
	CGPoint point={_offset.x,0}; // 描画始点
	NSInteger y=0;
	emu=[_lineStr objectEnumerator];
	while (str=[emu nextObject]) {
//		nslog(@"(%f,%f):str=%@<",point.x,point.y,str);
		if (fscrollArea) {
			// スクロールエリア(計算＆描画)
			[self calcScrollArea:point fTopLastLine:(y==0)];
		}
		// 文字列描画
		[color set]; // 描画色の設定(iOS7ではdraw時に設定するので多分不要)
		point.y+=_offset.y;
		rect.origin=point;
#if 1	// 1文字づつ描画しないと桁位置がずれる
		CGPoint pt=point;
		for (NSInteger i=0;i<[str length];i++) {
			NSString *c=[str substringWithRange:NSMakeRange(i,1)];
			// 仮描画
			size=[c sizeWithFont7:font]; // 描画幅を得る
			CGFloat dx=_spSize.width-size.width; // _spSizeと実描画幅の差を取る
		//	nslog(@"_spSize=%f/size=%f/dx=%f",_spSize.width,size.width,dx);
			CGFloat x=pt.x; // 保存
			pt.x+=(dx/2);
			[c drawAtPoint7:pt withFont:font withColor:color]; // センタリングして描画
			pt.x=x+_spSize.width;
		}
		size.height=_spSize.height; // lineHeightでは縦に余裕ができすぎるのでpointSizeにする
#else	
		size=[str drawAtPoint:point withFont:font];
		size.height=_spSize.height; // lineHeightでは縦に余裕ができすぎるのでpointSizeにする
		nslog(@"行サイズ=%f,%f",size.width,size.height);
#endif	
		// 文字列描画位置記録;実際には.xは全て同じ
		// スクロールエリアから[-1]分ずれるので注意
		rect.size=size;
//		printFrame(@"_lineAreaArray", rect);
		NSValue *val=[NSValue value:&rect withObjCType:@encode(CGRect)];
		[_lineAreaArray addObject:val];
		//
		// 次の行へ
		y++;
		point.y+=_spSize.height; // 描画した文字列の高さ分加える
	}
	// 最終行の下のスクロールエリア
	if (fscrollArea) {
		// スクロールエリア(計算＆描画)
		[self calcScrollArea:point fTopLastLine:YES];
	}
	//
	UIImage *image=UIGraphicsGetImageFromCurrentImageContext(); // 画像取得
	// ワーク開放
	UIGraphicsEndImageContext(); 
	
	return image;
}

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

// 文字単位のx座標
// point : スクロールエリアでも文字内でもok
-(NSInteger)getCharX:(CGPoint)point
{
//	nslog(@"point.x=%f,_offset.x=%f,_spSize.width=%f",point.x,_offset.x,_spSize.width);
	return (NSInteger)(((point.x-_offset.x)/_spSize.width));
}

// 文字単位のy座標
// point : スクロールエリアでも文字内でもok
-(NSInteger)getCharY:(CGPoint)point
{
//	nslog(@"point.y=%f,_offset.y=%f,_spSize.height=%f",point.y,_offset.y,_spSize.height);
//	nslog(@"getCharY=%d",(NSInteger)((point.y-_offset.y)/Height1Line));
	return (NSInteger)((point.y-_offset.y)/Height1Line);
}

-(CGFloat)yPoint:(NSInteger)y scrollView:(ScrollView *)scrollView
{
	return (Height1Line*(y+1)-scrollView.frame.size.height+_offset.y);
}

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

// この文字が画面内にあるか(y)
// 画面一番下のスクロールエリアには文字が割りつけられていないのでここには来ない
-(NSInteger)isCharYcheck:(NSInteger)y scrollView:(ScrollView *)scrollView ftategaki:(BOOL)ftategaki
{
	printSelf();

	if (scrollView.contentSize.height<scrollView.frame.size.height) {
		nslog(@"yスクロールは不要なサイズ");
		_pointy=scrollView.contentOffset.y;
		return (CHAR_NO_SCROLL);
	}
	
	// 文字描画位置の取得
	NSValue *val=_lineAreaArray[y];
	CGRect rect;
	[val getValue:&rect];
	
	// 判定
	if (rect.origin.y<scrollView.contentOffset.y) {
		nslog(@"一番上の上にある");
		_pointy=rect.origin.y-_offset.y-_spSize.height/4; // 上の文字も1/4見せる
		if (_pointy<0) _pointy=0;
		return(CHAR_OUTOF_UP);
	}
	
	CGRect frame=scrollView.frame;
#if 0
	nslog(@"rect.origin.y=%f\nrect.size.height=%f\noffset.y=%f\nscrollView.contentOffset.y=%f\nframe.size.height=%f"
		  ,rect.origin.y,rect.size.height,_offset.y,scrollView.contentOffset.y,frame.size.height);
#endif
	if (rect.origin.y+rect.size.height+_offset.y>=scrollView.contentOffset.y+frame.size.height) {
		nslog(@"一番下の下にある");
		// ↑+_offset.yしておかないとスクロールできない領域ができてしまう
		float mojis=frame.size.height/Height1Line;
		nslog(@"画面縦内に入る文字数=%f",mojis);
		if (mojis>=4) {
			// 画面に表示される縦文字数が多い時は、中央から表示する
			nslog(@"画面に表示される縦文字数が多い時は、中央から表示する");
			_pointy=scrollView.contentOffset.y+(mojis/2*Height1Line);
		} else {
			// 同、少ない時は一番上からに表示する
			nslog(@"少ない時は一番上からに表示する");
			_pointy=rect.origin.y-_offset.y; //-_spSize.height/4; // 上の文字も1/4見せる~これをするとスクロール位置があまりよくないので削除
		}
		if (_pointy<0) _pointy=0;
		//
		if (_pointy+frame.size.height>=scrollView.contentSize.height) {
			// 画面を越えるときは画面内に収める
			nslog(@"画面を越えるときは画面内に収める");
			_pointy=scrollView.contentSize.height-frame.size.height;
		}
		else
		if (_pointy+frame.size.height+_offset.y>=scrollView.contentSize.height) {
			// あと少しで全部見えるときは、見せる
			nslog(@"あと少しで全部見えるときは、見せる");
			_pointy=scrollView.contentSize.height-frame.size.height;
		}
		return(CHAR_OUTOF_DOWN);
	}
	// 画面内にある
	nslog(@"y画面内にある");
	if (ftategaki) {
		nslog(@"y半文字移動");
		_pointy=scrollView.contentOffset.y+_spSize.height/2; //	_offset.y; // 画面内にあるときは、文字の半分だけ上に上げる
	} // 横書きの時は動かさない
	return (CHAR_IN_VIEW);
}

// この文字が画面内にあるか(x)
-(NSInteger)isCharXcheck:(NSInteger)x scrollView:(ScrollView *)scrollView
{
	printSelf();
	if (scrollView.contentSize.width<scrollView.frame.size.width) {
		nslog(@"xスクロールは不要なサイズ");
		_pointx=scrollView.contentOffset.x;
		return (CHAR_NO_SCROLL);
	}
	// スクロール座標を計算しておく
	CGFloat gx=_offset.x+x*_spSize.width;
	// 判定
	if (gx<scrollView.contentOffset.x) { // 左の左にある
		nslog(@"左の左にある");
		_pointx=gx-_offset.x-_spSize.width; //_spSize.width/4; // 左の文字も_offset.x分見せる(一番左では_offsetのみ見える)
		if (_pointx<0) _pointx=0;
		return(CHAR_OUTOF_LEFT);
	}
	CGFloat xx=(gx+_spSize.width)-(scrollView.contentOffset.x+scrollView.frame.size.width);
	if (xx>0) { // 右の右にある
		nslog(@"右の右にある");
		_pointx=scrollView.contentOffset.x+xx+_spSize.width+_offset.x; // 右の文字も_offset.x分見せる(一番右では_offsetのみ見える)
		return(CHAR_OUTOF_RIGHT);
	}
	nslog(@"x画面内にある");
	// 横書き時の専用処理は外部で行うこと
	return (CHAR_IN_VIEW);
}

//----------------------------------------------------------------------------
// 縦書き用

-(NSInteger)isTCharYcheck:(NSInteger)y scrollView:(ScrollView *)scrollView
{
	return [self isCharYcheck:y scrollView:scrollView ftategaki:YES];
}

-(NSInteger)isTCharXcheck:(NSInteger)x scrollView:(ScrollView *)scrollView
{
	return [self isCharXcheck:x scrollView:scrollView];
}

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

@end
