////////////////////////////////////////////////////////////////////////////
// CCursor クラスのインプリメンテーション
//
////////////////////////////////////////////////////////////////////////////

#include "Cursor.h"
#include "TaEditDraw.h"
#include "TaEditShell.h"

////////////////////////////////////////////////////////////////////////////
// 構築/破棄
////////////////////////////////////////////////////////////////////////////

CCursor::CCursor( const CTaEditDraw *argpDraw, const CTaEditDoc *argpDoc, VClsPtr< CXimMgr > argXimMgr ) :
	pDraw( argpDraw ),
	pDoc( argpDoc ),
	pXimMgr( argXimMgr ),
	xpPos( 0 ),
	ypPos( 0 )
{
	CurDFlg = false;
}

CCursor::~CCursor()
{

}

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

// 文字単位の位置から、カーソル位置の設定
void CCursor::SetCurPos( T_CurPos argPos, ScrollInfoPacket *pPacket, bool OnShift, bool RedrawCursor )
{
	int i;
	const T_CurPos Before = CurPos;	// 現在のカーソル位置を待避
	bool flg = true;
	bool RedrawUB;

	assert( argPos.GetLine() >= 0 && argPos.GetLine() < pDoc->RefData().size() );

	// カーソル位置に変化がなければ、カーソルの再描画を行わない
	if ( Before == argPos ) flg = false;

	// カーソル移動前に、カーソルを消去する
	if ( CurDFlg && flg && RedrawCursor ) OnTimer();
	RedrawUB = ( Before.GetLine() != argPos.GetLine() );

	// カレントの行が変わったら、既存の下線を消去する
	if ( RedrawUB ) pDraw->DrawCurLineUnderbar( Before.GetLine(), false );

	const CLineData &rLineData = pDoc->RefData()[ argPos.GetLine() ];
	assert( argPos.GetCPos() >= 0 );
	if ( argPos.GetCPos() > rLineData.length() )
		argPos.SetCPos( rLineData.length() );
	CurPos = argPos;

	JdgIsScroll( pPacket );
	RefleshXIMPos();	// XIMの位置を再設定
	ProcSelRange( Before, CurPos, OnShift, pPacket );	// 選択範囲を設定
	if ( flg && RedrawCursor ) OnTimer();	// カーソルの再描画
	UpdateCopyPasteMenuSensitive();	// メニューの状態を更新
	m_TaEditShell->DelayCursorTimer();

	// 既存の下線を描画
	if ( RedrawUB ) pDraw->DrawCurLineUnderbar( argPos.GetLine(), true );
}

// カーソル位置と選択範囲を設定
void CCursor::SetCurPos( T_CurPos argPos, ScrollInfoPacket *pPacket, const T_SelRange& rArgSelRange, bool RedrawCursor )
{
	T_SelRange BeforeSelRange = SelRange;
	pPacket->IsSelCange = false;

	// カーソルを移動
	SetCurPos( argPos, pPacket, false, RedrawCursor );	// ここで選択範囲が解除される
	SelRange = rArgSelRange;		// 再度選択範囲を設定する

	// 選択範囲が変化した範囲を求める
	if ( BeforeSelRange != rArgSelRange ) {
		// 異なる範囲を指定された
		pPacket->IsSelCange = true;
		SelRange = rArgSelRange;	// 選択範囲を設定
		Calc_SRChangeLine( &(pPacket->ChangeSLine), &(pPacket->ChangeELine), BeforeSelRange, rArgSelRange );
	}

	UpdateCopyPasteMenuSensitive();	// メニューの状態を更新
	m_TaEditShell->DelayCursorTimer();
}

// 一定時間ごとのカーソルの描画
void CCursor::OnTimer()
{
	int x;
	int y;

	// ウインドウにフォーカスがなければカーソルは描画しない
	// ただし、カーソルの消去は行う
	if ( !pDraw->GetForcusState() && !CurDFlg ) return ;

	if ( !CurDFlg ) {
		CalcDrawCurPos( &x, &y );
		xpPos = x;	// カーソルを描画する時には、
		ypPos = y;	// その座標を保存する。
	}
	else {
		x = xpPos;	// カーソルを消去するときは
		y = ypPos;	// 最後に描画した位置を使用する
	}
	if ( x < 0 || y < 0 ) {
		// カーソルが描画範囲に含まれていない。
		CurDFlg = false;
		return ;
	}
	pDraw->DrawCursor( x, y );
	CurDFlg = !CurDFlg;
}

// 再描画された
void CCursor::OnRedraw()
{
	// 再描画されるとカーソルは消えるため、もう一度カーソルを描画する
	CurDFlg = false;
	OnTimer();
	pDraw->DrawCurLineUnderbar( CurPos.GetLine(), true );
}

// カーソルの移動
// カーソルの移動結果により、画面をスクロールする必要が生じた場合には、
// pIsXScroll、pIsYScrollにtrueを設定し、pX_pixには左端に設定すべき位置（ピクセル単位）、
// pY_lineには上端に設定すべき位置（行単位）を設定する。
void CCursor::Up( ScrollInfoPacket *pPacket, bool OnShift )
{
	T_CurPos wCurPos = CurPos;

	// これ以上上へは行けない場合
	if ( wCurPos.GetLine() <= 0 ) return ;

	// 上へ移動（横方向の位置に関しては、現在のX座標を元に決定する）
	int wx = CalcXPosition( wCurPos );
	wCurPos.DecrLine();
	wCurPos.SetCPos( CalcCPosByXPosition( wCurPos.GetLine(), wx ) );

	// カーソル位置を設定
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::Down( ScrollInfoPacket *pPacket, bool OnShift )
{
	T_CurPos wCurPos = CurPos;

	// これ以上下へは行けない場合
	if ( wCurPos.GetLine() >= pDoc->RefData().size() - 1 )
		return ;

	// 下へ移動
	int wx = CalcXPosition( wCurPos );
	wCurPos.IncrLine();
	wCurPos.SetCPos( CalcCPosByXPosition( wCurPos.GetLine(), wx ) );

	// カーソル位置を設定
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::Right( ScrollInfoPacket *pPacket, bool OnShift, bool OnCtrl )
{
	T_CurPos wCurPos = CurPos;
	const wstring &rStr = pDoc->RefData()[ CurPos.GetLine() ].getString();
	int length = rStr.length();

	if ( wCurPos.GetCPos() >= length ) {
		// 次の行へいく
		if ( wCurPos.GetLine() >= pDoc->RefData().size() - 1 )
			return ; // 次の行へもいけない
		wCurPos.SetCPos( 0 );
		wCurPos.IncrLine();
	}
	else {
		if ( !OnCtrl )
			wCurPos.IncrCPos();
		else {
			// 単語単位での移動
			wCurPos.SetCPos( WordSkip(
				rStr,
				wCurPos.GetCPos(),
				true,
				m_TaEditShell->GetConfigInfo()->RefWordCharSet()
			) );
		}
	}

	// カーソル位置を設定
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::Left( ScrollInfoPacket *pPacket, bool OnShift, bool OnCtrl )
{
	T_CurPos wCurPos = CurPos;
	if ( wCurPos.GetCPos() <= 0 ) {
		// 前の行へいく
		if ( wCurPos.GetLine() <= 0 ) return ; // 前の行へもいけない
		wCurPos.DecrLine();
		int length = pDoc->RefData()[ wCurPos.GetLine() ].length();
		wCurPos.SetCPos( length );
	}
	else {
		if ( !OnCtrl )
			wCurPos.DecrCPos();
		else {
			// 単語単位での移動
			wCurPos.SetCPos( WordSkip( 
				pDoc->RefData()[ CurPos.GetLine() ].getString(),
				 wCurPos.GetCPos() - 1,
				 false,
				m_TaEditShell->GetConfigInfo()->RefWordCharSet()
			) );
		}
	}

	// カーソル位置を設定
	SetCurPos( wCurPos, pPacket, OnShift );
}

// 単語単位での移動において、移動先を求める
int CCursor::WordSkip( const wstring &rStr, int cpos, int IsRight, const wstring &rWordCharSet ) const
{
	// 動けない場合を除外
	if ( IsRight && rStr.length() == cpos ) return rStr.length();
	if ( !IsRight && 0 == cpos ) return 0;

	bool flg = wcschr( rWordCharSet.c_str(), rStr[cpos] ) == NULL;

	// 移動先を求める
	// （cposに指定された位置が「単語」上であれば、単語の末尾もしくは先頭まで移動する。
	// 　そうでなければ、単語の先頭もしくは末尾まで移動する。）
	if ( IsRight )
		for (; cpos < rStr.length() && !( wcschr( rWordCharSet.c_str(), rStr[cpos] ) == NULL ^ flg ); ++cpos );
	else {
		for (; cpos >= 0 && !( wcschr( rWordCharSet.c_str(), rStr[cpos] ) == NULL ^ flg ); --cpos );
		++cpos;
	}
	return cpos;
}

void CCursor::PageUp( ScrollInfoPacket *pPacket, bool OnShift )
{
	T_CurPos wCurPos = CurPos;
	int ViewLineCnt = pDraw->GetViewLineCnt();	// 画面の行数
	int wl = wCurPos.GetLine() - ViewLineCnt;	// 移動先の行番号を算出
	if ( wl < 0 ) wl = 0;
	wCurPos.SetLine( wl );
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::PageDown( ScrollInfoPacket *pPacket, bool OnShift )
{
	T_CurPos wCurPos = CurPos;
	int ViewLineCnt = pDraw->GetViewLineCnt();	// 画面の行数
	int TatalLineCnt = pDoc->RefData().size();	// 総行数
	int wl = wCurPos.GetLine() + ViewLineCnt;	// 移動先の行番号を計算
	if ( wl >= TatalLineCnt ) wl = TatalLineCnt - 1;
	wCurPos.SetLine( wl );
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::End( ScrollInfoPacket *pPacket, bool OnShift, bool OnCtrl )
{
	T_CurPos wCurPos = CurPos;
	if ( !OnCtrl )
		wCurPos.SetCPos( pDoc->RefData()[ wCurPos.GetLine() ].length() );
	else {
		// コントロールキーが押されていた場合は、文書の末尾へ移動
		int wLineCnt = pDoc->RefData().size() - 1;
		int wCharCnt = pDoc->RefData()[ wLineCnt ].length();
		wCurPos = T_CurPos( wLineCnt, wCharCnt );
	}
	SetCurPos( wCurPos, pPacket, OnShift );
}

void CCursor::Home( ScrollInfoPacket *pPacket, bool OnShift, bool OnCtrl )
{
	T_CurPos wCurPos = CurPos;
	if ( !OnCtrl )
		wCurPos.SetCPos( 0 );	// 移動先の位置を算出
	else
		wCurPos = T_CurPos( 0, 0 );	// 文書の先頭へ
	SetCurPos( wCurPos, pPacket, OnShift );
}

// 単語の選択
void CCursor::SelectWord( ScrollInfoPacket *pPacket, const T_CurPos &rPos )
{
	const wstring &rStr = pDoc->RefData()[ rPos.GetLine() ].getString();
	int length = rStr.length();
	bool wIsSelCange;
	int wChangeSLine;
	int wChangeELine;

	// まずは、指定された位置に移動
	SetCurPos( rPos, pPacket, false );

	// 選択範囲の変化の情報を待避する
	wIsSelCange = pPacket->IsSelCange;
	wChangeSLine = pPacket->ChangeSLine;
	wChangeELine = pPacket->ChangeELine;

	// 指定された位置から、一旦左に移動し、その後右移動で範囲選択を行う
	// ただし、行をまたいだ選択は行わない
	pPacket->IsSelCange = false;
	if ( rPos.GetCPos() > 0 )
		Left( pPacket, false, true );
	if ( CurPos.GetCPos() < length )
		Right( 	pPacket, true, true );	

	// 再描画用に、選択範囲の変化の情報を設定する
	if ( !wIsSelCange ) return ;	// 最初の段階で変化がなければ、特にすることはない

	if ( !pPacket->IsSelCange ) {
		// ２回目の段階で変化がなければ、最初の変化のみを設定する。
		pPacket->IsSelCange = wIsSelCange;
		pPacket->ChangeSLine = wChangeSLine;
		pPacket->ChangeELine = wChangeELine;
	}
	else {
		pPacket->IsSelCange = true;
		// 一回目と二回目との変更範囲を内包する範囲を求める
		if ( wChangeSLine < pPacket->ChangeSLine )
			pPacket->ChangeSLine = wChangeSLine;
		if ( wChangeELine > pPacket->ChangeELine )
			pPacket->ChangeELine = wChangeELine;
	}
}

// 文字単位でのカーソル位置を取得
const T_CurPos& CCursor::GetCurPos() const
{
	return CurPos;
}

// XIMの位置を再設定する
void CCursor::RefleshXIMPos() const
{
	int x, y;
	CalcDrawCurPos( &x, &y );

	if ( x < 0 || y < 0 ) return ;

	// XIMの入力位置を設定
	y += pDraw->GetTextAscent();
	pXimMgr->SetPosition( x, y );
}

// 指定された文字が選択範囲に含まれているか否か
bool CCursor::IsCharSelected( const T_CurPos &r ) const
{
	return ( SelRange.GetSPos() <= r && SelRange.GetEPos() > r );
}

// 選択範囲を取得
const T_SelRange& CCursor::GetSelRange() const
{
	return SelRange;
}

// 画面をスクロールする必要の有無を判断する
void CCursor::JdgIsScroll( ScrollInfoPacket *pPacket ) const
{
	int TopLineNum = pDraw->GetTopLineNum();
	int ViewLineCnt = pDraw->GetViewLineCnt();
	int LeftPixPos = pDraw->GetLeftPixPos();
	int ViewWidth = pDraw->GetViewWidth();
	int pixCurX = CalcXPosition( CurPos );

	pPacket->NeedYScroll = false;
	pPacket->NeedXScroll = false;

	if ( CurPos.GetLine() < TopLineNum ) {
		pPacket->NeedYScroll = true;
		pPacket->lineY = CurPos.GetLine();
	}
	if ( CurPos.GetLine() >= TopLineNum + ViewLineCnt ) {
		pPacket->NeedYScroll = true;
		pPacket->lineY = CurPos.GetLine() - ViewLineCnt + 1;
	}
	if ( pixCurX < LeftPixPos ){
		pPacket->NeedXScroll = true;
		pPacket->pixX = pixCurX;
	}
	if ( pixCurX > LeftPixPos + ViewWidth ) {
		pPacket->NeedXScroll = true;
		pPacket->pixX = pixCurX - ViewWidth;
	}
}

// カーソルの描画位置を算出（画面の左上を0とした値）
void CCursor::CalcDrawCurPos( int *px, int *py ) const
{
	int TopLineNum = pDraw->GetTopLineNum();
	int ViewLineCnt = pDraw->GetViewLineCnt();
	int LeftPixPos = pDraw->GetLeftPixPos();
	int ViewWidth = pDraw->GetViewWidth();
	int pixCurX = CalcXPosition( CurPos );

	if ( CurPos.GetLine() < TopLineNum || CurPos.GetLine() >= TopLineNum + ViewLineCnt  )
		(*py) = -1;
	else
		(*py) = ( CurPos.GetLine() - TopLineNum ) * pDraw->GetTextHeight();

	if ( pixCurX < LeftPixPos || pixCurX > LeftPixPos + ViewWidth )
		(*px) = -1;
	else
		(*px) = pixCurX - LeftPixPos;
}

// カーソルの描画位置（X座標）を取得
int CCursor::GetCurPosX() const
{
	return CalcXPosition( CurPos );
}

// キー操作による選択範囲の変化を処理する
void CCursor::ProcSelRange( const T_CurPos &Before, const T_CurPos &After, bool OnShift, ScrollInfoPacket *pPacket )
{
	T_SelRange hSelRange = SelRange;	// 現状の選択範囲を待避

	if ( !OnShift ) {
		// Shiftが押されていなかった場合は、選択範囲を解除する
		SelStartPos = After;
		SelRange = T_SelRange( After, After );
	}
	else {
		// Shiftが押されていた場合は、範囲選択を行う
		if ( SelRange.GetLength() == T_CurPos( 0, 0 ) )
			SelStartPos = Before;	// 新規に選択を行う場合は、選択開始位置を設定する
		SelRange = T_SelRange( SelStartPos, After );
	}

	if ( hSelRange == SelRange || ( hSelRange.GetLength() == 0 && SelRange.GetLength() == 0 ) ) {
		// 選択範囲に変化がない場合
		pPacket->IsSelCange = false;
	}
	else {
		// 選択範囲に変化がある場合は、再描画用の情報を設定する
		pPacket->IsSelCange = true;
		Calc_SRChangeLine( &(pPacket->ChangeSLine), &(pPacket->ChangeELine), hSelRange, SelRange );
	}
}

// 選択範囲が変化した行の範囲を求める
void CCursor::Calc_SRChangeLine( int *pSLine, int *pELine, const T_SelRange& rSR1, const T_SelRange& rSR2 ) const
{
	assert( NULL != pSLine && NULL != pELine );

	if ( rSR1.GetSPos() < rSR2.GetSPos() )
		(*pSLine) = rSR1.GetSPos().GetLine();
	else {
		if ( rSR1.GetSPos() > rSR2.GetSPos() )
			(*pSLine) = rSR2.GetSPos().GetLine();
		else {
			if ( rSR1.GetEPos() < rSR2.GetEPos() )
				(*pSLine) = rSR1.GetEPos().GetLine();
			else
				(*pSLine) = rSR2.GetEPos().GetLine();
		}
	}
	if ( rSR1.GetEPos() < rSR2.GetEPos() )
		(*pELine) = rSR2.GetEPos().GetLine();
	else {
		if ( rSR1.GetEPos() > rSR2.GetEPos() )
			(*pELine) = rSR1.GetEPos().GetLine();
		else {
			if ( rSR1.GetSPos() < SelRange.GetSPos() )
				(*pELine) = rSR2.GetSPos().GetLine();
			else
				(*pELine) = rSR1.GetSPos().GetLine();
		}
	}
	(*pELine)++;	// 範囲終端は、再描画対象に含まれないため
}

// 切り取り・コピーメニューの有効・無効を更新する
void CCursor::UpdateCopyPasteMenuSensitive() const
{
	bool flg = true;
	if ( SelRange.GetLength() == T_CurPos( 0, 0 ) )
		flg = false;
	m_TaEditShell->SetEnableCopyPasteMenu( flg );
}

// 指定されたカーソル位置のX座標を求める
int CCursor::CalcXPosition( T_CurPos pos ) const
{
	assert( NULL != this && NULL != pDoc && NULL != pDraw );
	assert( pos.GetLine() >= 0 && pos.GetLine() < pDoc->RefData().size() );
	const CLineData &rLineData = pDoc->RefData()[ pos.GetLine() ];

	assert( pos.GetCPos() >= 0 );
	if ( pos.GetCPos() > rLineData.length() )
		pos.SetCPos( rLineData.length() );

	// 必要に応じて描画位置情報を更新
	if ( !rLineData.IsEnableDrawCharPos() )
		rLineData.UpdateDrawCharPos( pDraw );

	// 位置を取得
	if ( pos.GetCPos() < rLineData.length() )
		return rLineData.GetDrawCharPos().GetCharPos( pos.GetCPos() );
	else
		return rLineData.GetDrawCharPos().GetRightXPos();
}

// 指定された行において、指定されたX座標に最も近い文字位置を算出する
int CCursor::CalcCPosByXPosition( int line, int xpos ) const
{
	assert( NULL != this && NULL != pDraw );
	return pDraw->XPosToCharNum( line, xpos - pDraw->GetLeftPixPos() );
}
