#include "stdafx.h"
#include "ip_view.h"
using namespace editwing;
using namespace editwing::view;



//=========================================================================
//---- ip_wrap.cpp   折り返し
//
//		Documentで文字列データが更新されるのを受けて
//		Viewでは折り返し位置情報を更新する。その処理がココ。
//
//---- ip_text.cpp   文字列操作・他
//---- ip_parse.cpp  キーワード解析
//---- ip_scroll.cpp スクロール
//---- ip_draw.cpp   描画・他
//---- ip_cursor.cpp カーソルコントロール
//=========================================================================



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

ViewImpl::ViewImpl( View& vw, DocImpl& dc )
	: doc_   ( dc )
	, cvs_   ( vw )
	, cur_   ( vw.hwnd(), *this, dc )
	, hwnd_  ( vw.hwnd() )
	, vlNum_ ( 0 )
	, textCx_( 0 )
{
	// 適当に折り返し情報初期化
	InsertMulti( 0, doc_.tln()-1 );

	// 適当にスクロール情報初期化
	udScr_.cbSize = rlScr_.cbSize = sizeof(udScr_);
	udScr_.fMask  = rlScr_.fMask  = SIF_PAGE | SIF_POS | SIF_RANGE;
	udScr_.nMin   = rlScr_.nMin   = 0;
	udScr_.nPos   = rlScr_.nPos   = 0;
	udScr_.fMask |= SIF_DISABLENOSCROLL;
	udScr_tl_     = udScr_vrl_    = 0;
	ReSetScrollInfo();
}



//-------------------------------------------------------------------------
// 状態変更への対応
//-------------------------------------------------------------------------

void ViewImpl::DoResize( bool wrapWidthChanged )
{
	// 折り返し位置再計算
	if( wrapWidthChanged )
	{
		ReWrapAll();
		UpdateTextCx();
	}

	// スクロール情報変更
	ReSetScrollInfo();
	if( wrapWidthChanged )
		ForceScrollTo( udScr_tl_ );

	// 再描画
	ReDraw( ALL );
	cur_.ResetPos();
}

void ViewImpl::DoConfigChange()
{
	// 折り返し位置再計算
	ReWrapAll();
	UpdateTextCx();

	// スクロール情報変更
	ReSetScrollInfo();
	ForceScrollTo( udScr_tl_ );

	// 再描画
	ReDraw( ALL );
	cur_.ResetPos();
}

void ViewImpl::on_text_update
	( const DPos& s, const DPos& e, const DPos& e2, bool bAft, bool mCur )
{
	// まず、折り返し位置再計算

	// 置換範囲の先頭行を調整
	int r3 = 0, r2 = 1, r1 = ReWrapSingle( s );

	// 残りを調整
	if( s.tl != e.tl )
		r2 = DeleteMulti( s.tl+1,  e.tl );
	if( s.tl != e2.tl )
		r3 = InsertMulti( s.tl+1, e2.tl );

	// この変更で横幅が…
	// if( "長くなったなてはいない" AND "短くなっちゃった可能性あり" )
	//     横幅再計算();
	if( !(r1==2 || r3==1) && (r1==0 || r2==0) )
		UpdateTextCx();

	// スクロールバー修正
	ReDrawType t = TextUpdate_ScrollBar( s, e, e2 );
	bool doResize = false;

	// 行数に変化があって、行番号表示域の幅を変えなきゃならん時
	if( e.tl!=e2.tl && cvs_.on_tln_change( doc_.tln() ) )
	{
		doResize = true;
	}
	else if( bAft && t!=ALL )
	{
		t = AFTER;
	}

	// カーソル移動
	cur_.on_text_update( s, e, e2, mCur );

	// 再描画
	if( doResize )
		DoResize( true );
	else
	{
		if( e.tl != e2.tl ) // 行番号領域再描画の必要があるとき
			ReDraw( LNAREA, 0 );
		ReDraw( t, &s );
	}
}



//-------------------------------------------------------------------------
// 折り返し位置計算補助ルーチン
//-------------------------------------------------------------------------

void ViewImpl::UpdateTextCx()
{
	if( cvs_.wrapType() == NOWRAP )
	{
		// 折り返しなしなら、数えてみないと横幅はわからない
		ulong cx=0;
		for( ulong i=0, ie=doc_.tln(); i<ie; ++i )
			if( cx < wrap_[i].width() )
				cx = wrap_[i].width();
		textCx_ = cx;
	}
	else
	{
		// 折り返しありなら、横幅:=折り返し幅とする
		textCx_ = cvs_.wrapWidth();
	}
}

ulong ViewImpl::CalcLineWidth( const unicode* txt, ulong len ) const
{
	// 行を折り返さずに書いたときの横幅を計算する
	// ほとんどの行が折り返し無しで表示されるテキストの場合、
	// この値を計算しておくことで、処理の高速化が可能。
	const Painter& p = cvs_.getPainter();

	ulong w=0;
	for( ulong i=0; i<len; ++i )
		if( txt[i] == L'\t' )
			w = p.nextTab(w);
		else
			w += p.W( &txt[i] );
	return w;
}

void ViewImpl::CalcEveryLineWidth()
{
	// 全ての行に対してCalcLineWidthを実行
	// …するだけ。
	for( ulong i=0, ie=doc_.tln(); i<ie; ++i )
		wrap_[i].width() = CalcLineWidth( doc_.tl(i), doc_.len(i) );
}

void ViewImpl::ModifyWrapInfo(
		const unicode* txt, ulong len, WLine& wl, ulong stt )
{
	// 設定幅での折り返しを実行する。
	// 行の途中からの変更の場合、sttが開始addressを指している
	const Painter& p = cvs_.getPainter();
	const ulong   ww = cvs_.wrapWidth();

	while( stt < len )
	{
		ulong i, w;
		for( w=0,i=stt; i<len; ++i )
		{
			if( txt[i] == L'\t' )
				w = p.nextTab(w);
			else
				w += p.W( &txt[i] );

			if( w>ww )
				break; // 幅が設定値を超えた所でおしまい
		}
		wl.Add( stt = (i==stt?i+1:i) );
	}
}

int ViewImpl::GetLastWidth( ulong tl ) const
{
	if( rln(tl)==1 )
		return wrap_[tl][0];

	ulong beg = rlend(tl,rln(tl)-2);
	return CalcLineWidth( doc_.tl(tl)+beg, doc_.len(tl)-beg );
}

void ViewImpl::ReWrapAll()
{
	// 折り返し幅に変更があった場合に、全ての行の
	// 折り返し位置情報を変更する。
	const ulong ww = cvs_.wrapWidth();

	ulong vln=0;
	for( ulong i=0, ie=doc_.tln(); i<ie; ++i )
	{
		WLine& wl = wrap_[i];
		wl.ForceSize(1);

		if( wl.width() < ww )
		{
			// 設定した折り返し幅より短い場合は一行で済む。
			wl.Add( doc_.len(i) );
			++vln;
		}
		else
		{
			// 複数行になる場合
			ModifyWrapInfo( doc_.tl(i), doc_.len(i), wl, 0 );
			vln += wl.rln();
		}
	}
	vlNum_ = vln;
}



//-------------------------------------------------------------------------
// 折り返し位置計算メインルーチン
//-------------------------------------------------------------------------

int ViewImpl::ReWrapSingle( const DPos& s )
{
	// 指定した一行のみ折り返しを修正。
	//
	// 返値は
	//   2: "折り返しあり" or "この行が横に一番長くなった"
	//   1: "この行以外のどこかが最長"
	//   0: "さっきまでこの行は最長だったが短くなっちゃった"
	// で、上位ルーチンにm_TextCx修正の必要性を伝える。
	//
	// 昔は再描画範囲の計算のために、表示行数の変化を返していたが、
	// これは上位ルーチン側で vln() を比較すれば済むし、
	// むしろその方が効率的であるため廃止した。


	// 旧情報保存
	WLine& wl            = wrap_[s.tl];
	const ulong oldVRNum = wl.rln();
	const ulong oldWidth = wl.width();

	// 横幅更新
	wl.width() = CalcLineWidth( doc_.tl(s.tl), doc_.len(s.tl) );

	if( wl.width() < cvs_.wrapWidth() )
	{
		// 設定した折り返し幅より短い場合は一行で済む。
		wl[1] = doc_.len(s.tl);
		wl.ForceSize( 2 );
	}
	else
	{
		// 複数行になる場合
		ulong vr=1, stt=0;
		while( wl[vr] < s.ad ) // while( vr行目は変更箇所より手前 )
			stt = wl[ vr++ ];  // stt = 次の行の行頭のアドレス

		// 変更箇所以降のみ修正
		wl.ForceSize( vr );
		ModifyWrapInfo( doc_.tl(s.tl), doc_.len(s.tl), wl, stt );
	}

	// 表示行の総数を修正
	vlNum_ += ( wl.rln() - oldVRNum );

	// 折り返しなしだと総横幅の更新が必要
	if( cvs_.wrapType() == NOWRAP )
		if( textCx_ <= wl.width() )
		{
			textCx_ = wl.width();
			return 2;
		}
		else if( textCx_ == oldWidth )
		{
			return 0;
		}
		else
		{
			return 1;
		}
	return 2;
}

int ViewImpl::InsertMulti( ulong ti_s, ulong ti_e )
{
	// 指定した分だけ新しく行情報を追加。
	// ＆折り返し情報もきちんと計算
	//
	// 返値は
	//   1: "折り返しあり" or "この行が横に一番長くなった"
	//   0: "この行以外のどこかが最長"
	// 詳しくは ReWrapSingle() を見よ。

	ulong dy=0, cx=0;
	for( ulong i=ti_s; i<=ti_e; ++i )
	{
		WLine* pwl = new WLine;
		pwl->Add( CalcLineWidth( doc_.tl(i), doc_.len(i) ) );

		if( pwl->width() < cvs_.wrapWidth() )
		{
			// 設定した折り返し幅より短い場合は一行で済む。
			pwl->Add( doc_.len(i) );
			dy++;
			if( cx < pwl->width() )
				cx = pwl->width();
		}
		else
		{
			// 複数行になる場合
			ModifyWrapInfo( doc_.tl(i), doc_.len(i), *pwl, 0 );
			dy += pwl->rln();
		}

		wrap_.InsertAt( i, pwl );
	}

	// 表示行の総数を修正
	vlNum_ += dy;

	// 折り返しなしだと総横幅の更新が必要
	if( cvs_.wrapType() == NOWRAP )
	{
		if( textCx_ <= cx )
		{
			textCx_ = cx;
			return 1;
		}
		return 0;
	}
	return 1;
}

int ViewImpl::DeleteMulti( ulong ti_s, ulong ti_e )
{
	// 指定した範囲の行情報を削除
	//
	// 返値は
	//   1: "折り返しあり" or "この行以外のどこかが最長"
	//   0: "さっきまでこの行は最長だったが短くなっちゃった"
	// 詳しくは ReWrapSingle() を見よ。

	bool  widthChanged = false;
	ulong dy = 0;

	// 情報収集しながら削除
	for( ulong cx=textCx_, i=ti_s; i<=ti_e; ++i )
	{
		WLine& wl = wrap_[i];
		dy += wl.rln();
		if( cx == wl.width() )
			widthChanged = true;
	}
	wrap_.RemoveAt( ti_s, (ti_e-ti_s+1) );

	// 表示行の総数を修正
	vlNum_ -= dy;

	// 折り返しなしだと総横幅の更新が必要
	return ( cvs_.wrapType()==NOWRAP && widthChanged ) ? 0 : 1;
}



//-------------------------------------------------------------------------
// 座標値変換
//-------------------------------------------------------------------------

void ViewImpl::ConvDPosToVPos( DPos dp, VPos* vp, const VPos* base ) const
{
	// 補正
	dp.tl = Min( dp.tl, doc_.tln()-1 );
	dp.ad = Min( dp.ad, doc_.len(dp.tl) );

	// 変換の基準点が指定されていなければ、原点を基準とする
	VPos topPos(false); // ０クリア
	if( base == NULL )
		base = &topPos;

	// とりあえずbase行頭の値を入れておく
	ulong vl = base->vl - base->rl;
	ulong rl = 0;
	int vx;

	// 同じ行内だった場合
	//if( dp.tl == base->tl )
	//{
	//	例えば [→] を押したときなど、右隣の文字の横幅を
	//	足すだけで次の位置は算出できる。これを使って普通の
	//	カーソル移動はずっと高速化できるはずであるが、
	//	とりあえず面倒くさいので、今のところ略。
	//}

	// 違う行だった場合
	//else
	{
		// vlを合わせる
		ulong tl = base->tl;
		if( tl > dp.tl )      // 目的地が基準より上にある場合
			do
				vl -= rln(--tl);
			while( tl > dp.tl );
		else if( tl < dp.tl ) // 目的地が基準より下にある場合
			do
				vl += rln(tl++);
			while( tl < dp.tl );

		// rlを合わせる
		ulong stt=0;
		while( wrap_[tl][rl+1] < dp.ad )
			stt = wrap_[tl][++rl];
		vl += rl;

		// x座標計算
		vx = CalcLineWidth( doc_.tl(tl)+stt, dp.ad-stt );
	}

	vp->tl = dp.tl;
	vp->ad = dp.ad;
	vp->vl = vl;
	vp->rl = rl;
	vp->rx = vp->vx = vx;
}

void ViewImpl::GetVPos( int x, int y, VPos* vp, bool linemode ) const
{
// x座標補正

	x = x - lna() + rlScr_.nPos;

// まず行番号計算

	int tl = udScr_tl_;
	int vl = udScr_.nPos - udScr_vrl_;
	int rl = y / fnt().H() + udScr_vrl_;
	if( rl >= 0 ) // View上端より下の場合、下方向を調べる
		while( tl < (int)doc_.tln() && (int)rln(tl) <= rl )
		{
			vl += rln(tl);
			rl -= rln(tl);
			++tl;
		}
	else           // View上端より上の場合、上方向を調べる
		while( 0<=tl && rl<0 )
		{
			vl -= rln(tl);
			rl += rln(tl);
			--tl;
		}

	if( tl == (int)doc_.tln() ) // EOFより下に行ってしまう場合の補正
	{
		--tl, vl-=rln(tl), rl=rln(tl)-1;
		if( linemode )
			x = 0x4fffffff;
	}
	else if( tl == -1 ) // ファイル頭より上に行ってしまう場合の補正
	{
		tl = vl = rl = 0;
		if( linemode )
			x = 0;
	}
	else
	{
		if( linemode ) // 行選択モードの場合
		{
			if( tl == (int)doc_.tln()-1 )
				rl=rln(tl)-1, x=0x4fffffff;
			else
				vl+=rln(tl), rl=0, ++tl, x=0;
		}
	}

	vp->tl = tl;
	vp->vl = vl + rl;
	vp->rl = rl;

// 次に、横位置を計算

	if( rl < static_cast<int>(wrap_[tl].rln()) )
	{
		const unicode* str = doc_.tl(tl);
		const ulong adend = rlend(tl,rl);
		ulong ad = (rl==0 ? 0 : rlend(tl,rl-1));
		int   vx = (rl==0 ? 0 : fnt().W(&str[ad++]));

		while( ad<adend )
		{
			int nvx = (str[ad]==L'\t'
				? fnt().nextTab(vx)
				:  vx + fnt().W(&str[ad])
			);
			if( x+2 < nvx )
				break;
			vx = nvx;
			++ad;
		}

		vp->ad          = ad;
		vp->rx = vp->vx = vx;
	}
	else
	{
		vp->ad = vp->rx = vp->vx = 0;
	}
}
