module yamalib.clock;

private import y4d;
private import y4d_aux.filesys;
private import ytl.vector;

private import yamalib.counterfsp;
private import yamalib.log.log;
private import yamalib.date.date;

/**
	装飾用時計クラス
	実際の時間とは何の関係もありません
	ClockHand と ClockManager による実現
*/
class DecorativeColck {
	
	///
	override char[] toString() {
		char[] str;
		str ~= "DecorativeColck";
		return str;
	}
	
	/// 表示位置の設定
	void setXY(int x_, int y_) {
		x = x_;
		y = y_;
	}
	
	/// 表示位置の取得
	void getXY(out int x_, out int y_) {
		x_ = x;
		y_ = y;
	}
		
	/// 毎回呼び出すなり
	void onMove(Screen screen) {
		manager.onMove(screen);
		
		// 表示描画
		foreach(inout ClockDisplaySimple item; items) {
			item.onMove(screen);
		}
	}
	
	/// 毎回呼び出すなり
	void onDraw(Screen screen) {
		screen.setColor(255, 255, 255);
		// 時計下敷き
		screen.blt(t_clockBase, x, y);

		// 表示描画
		foreach(inout ClockDisplaySimple item; items) {
			item.onDraw(screen, x, y);
		}
		
		// 時計素材描画
		screen.blt(t_clock, x , y);
		
		// 描画
		manager.onDraw(screen, x, y);
	}

	/// コンストラクタ
	this() {
		rand  = new Rand();
		rand.randomize();
		
		// 時計下敷き
		t_clockBase = new Texture();
		t_clockBase.load( FileSys.makeFullName("img/demo/clock_base.png") );

		//  時計
		t_clock = new Texture();
		t_clock.load( FileSys.makeFullName("img/demo/clock.png") );

		// 大針
		t_bigHand = new Texture();
		t_bigHand.load( FileSys.makeFullName("img/demo/hand.png") );

		// 小針
		t_littelHand = new Texture();
		t_littelHand.load( FileSys.makeFullName("img/demo/lhand.png") );
		
		hand1 = new ClockHand();
		hand2 = new ClockHand();
		hand3 = new ClockHand();
		hand4 = new ClockHand();
		DateTime date = DateUtil.getDateTime();
		
		// 分
		hand1.setTexture(t_bigHand);
		hand1.setXY(OX, OY);
		hand1.setDivRound(60);
		hand1.setDivTick(5);
		hand1.setInit(date.mimutes);
		
		// 1 / 60
		hand2.setTexture(t_littelHand);
		hand2.setXY(OX, OY);
		hand2.setDivRound(60);
		hand2.setReverse(true);

		// 秒
		hand3.setTexture(t_bigHand);
		hand3.setXY(OX, OY);
		hand3.setDivRound(60);
		hand3.setDivTick(10);
		hand3.setInit(date.second);
		
		// 時
		hand4.setTexture(t_bigHand);
		hand4.setXY(OX, OY);
		hand4.setDivRound(12);
		hand4.setDivTick(5);
		hand4.setInit(date.hours % 12);
		
		// 連携元針の設定
		hand3.setCooperateHand( hand2 );
		hand1.setCooperateHand( hand3 );
		hand4.setCooperateHand( hand1 );
		
		manager = new ClockHandManager();
		
		manager.addClockHand( hand2 );
		manager.addClockHand( hand3 );
		manager.addClockHand( hand1 );
		manager.addClockHand( hand4 );
		
		// 時計計
		tl1 = new TextureLoader();
		tl1.loadDefRW( FileSys.read("img/demo/clock_item/item1.lst") );
		// キャッシュに喰っておく
		tl1.setCacheSize(-1);	// 無限キャッシュ
		for(int i = 0; i < tl1.getInfoList().size(); ++i) {
			Texture t = tl1.get(i);
		}
		
		items = new vector!(ClockDisplaySimple);
		
		foreach(inout int pt[]; ayPt) {
			ClockDisplaySimple item = new ClockDisplaySimple();
			item.setRect(pt[0], pt[1], pt[2], pt[3]);
			item.setStopTime( 60 + rand.get(30) );
			item.setTextureLoader( tl1 );
			item.setInitRad( rand.get(512) );
			items.push_back( item );
		}
	}

private:
	/// 表示穴の配列
	static final const int ayPt[][] = [
		[ 297, 83, 327, 111 ],
		[ 337, 94, 367, 122 ],
		[ 369, 126, 399, 154 ],
		[ 375, 170, 405, 198 ],
		[ 353, 212, 383, 240 ],
		[ 313, 237, 343, 265 ],
		[ 253, 227, 283, 245 ],
		[ 220, 191, 250, 219 ],
		[ 217, 146, 247, 174 ],
		[ 237, 106, 267, 134 ]
	];
	
	static final const int OX = 308;
	static final const int OY = 175;

	Texture t_clockBase;
	Texture t_clock;		/// 時計のテクスチャ
	Texture t_bigHand;		/// 時計の大針
	Texture t_littelHand;	/// 時計の小針
	
	int x;	//!< 表示位置
	int y;	//!< 表示位置
	
	ClockHand hand1;
	ClockHand hand2;
	ClockHand hand3;
	ClockHand hand4;
	ClockHandManager manager;	//!< 時計の針管理人
	
	vector!(ClockDisplaySimple) items;	//!< 表示計のベクター
	ClockDisplaySimple item1;	//!< 時計表示
	TextureLoader tl1;
	Rand rand;

}

/**
	時計用の針クラス
*/
class ClockHand {
public:
	
	/// 毎回呼び出すなり
	void onMove(Screen screen) {
		
		// 回転中か？
		if (rotation) {
			// 分割移動するの？
			if ( bDivTick ) {
				// １移動
				divTickCounter++;
				if ( divTickCounter.isEnd() ) {
					rotation = false;
				}
			} else {
				divRoundCounter++;
				if ( divRoundCounter.isEnd() ) {
					divRoundCounter.set(0, 512, divRound);
					around = true;
				}
			}
		} else {
			
			if ( coopeHand != null ) {
				// 連携元針が動いたか？
				if ( coopeHand.isAround() ) {

					if ( divRoundCounter.isEnd() ) {
						divRoundCounter.set(0, 512, divRound);
						divRoundCounter++;
						around = true;
					}
					
					// 針が動く前rad
					int radPre = divRoundCounter.get();
					divRoundCounter++;
					int rad = divRoundCounter.get();
					divTickCounter.set(radPre, rad, divTick);

					rotation = true;
				}
			}
		}
	}
	
	/// 毎回呼び出すなり
	void onDraw(Screen screen, int baseX, int baseY) {
		// テクスチャセットしてくれ
		if (texture == null) return; 
		
		int rad;
		if ( bDivTick ) {
			rad = divTickCounter.get();
		} else {
			rad = divRoundCounter.get();
		}
		
		// 逆回転なら戻るのだー
		if ( reverse ) {
			rad = 512 - rad;
		}
		
		// 描画
		screen.bltRotate(texture, x + baseX, y + baseY, rad, 1.0f, 7);		
	}
	
	/// 針テクスチャを設定します
	void setTexture(Texture tex_) {
		texture = tex_;
	}
	
	/// テクスチャの取得
	Texture getTexture() {
		return texture;
	}
	
	/// 針の回転底辺中心を設定します
	void setXY(int x_, int y_) {
		x = x_;
		y = y_;
	}
	
	/// 針の回転底辺中心の位置を返却します
	void getXY(out int x_, out int y_) {
		x_ = x;
		y_ = y;
	}
	
	/// 針が１周したか
	bool isAround() {
		if (around) {
			around = false;
			return true;
		}
		return false;
	} 
	
	/// １クロックの動作の分割数を設定します
	void setDivTick(int div) {
		divTick = div;
		divTickCounter.set(0, 512, div);
		// １クロック分割を有効にする
		bDivTick = true;
	}
	
	/// １回転の分割数を設定します
	void setDivRound(int div) {
		divRound = div;
		divRoundCounter.set(0, 512, div);
	}
	
	/// 連携する小針を設定します
	void setCooperateHand(ClockHand hand_) {
		coopeHand = hand_;
		/// 連携元があるなら自立的には回転しない
		rotation = false;
	}
	
	/// 初期値を設定します
	void setInit(int i) {
		divRoundCounter.setFlame(i);
		divRoundCounter++;
		setTickCounter( divRoundCounter.get() );
	}
	
	/// 反時計回りの設定
	void setReverse(bool bRev) {
		reverse = bRev;
	}
	
	/// 反時計回りか？
	bool isReverse() {
		return reverse;
	}
	
	/// 回転状態を設定します
	void setRotate(bool b) {
		rotation = b;
	}
	
	/// 回転中か？
	bool getRotate() {
		return rotation;
	}
	
	/// コンストラクタ
	this() {
		divRoundCounter = new InteriorCounter();
		divTickCounter = new InteriorCounter();
		rotation = true;
		coopeHand = null;
	}

private:
	void setTickCounter(int start_) {
		divTickCounter.set(start_,start_,1);
		divTickCounter++;
	}
	
private:
	int x;
	int y;
	bool around;
	Texture texture;
	ClockHand coopeHand;	//!< 連携元針
	bool reverse;
	bool rotation;	//!< 回転中か
	bool bDivTick;  //!< １クロックを分割動作するか
	Texture hand;	//!< 針テクスチャ
	InteriorCounter divTickCounter;
	InteriorCounter divRoundCounter;
	int divTick; 	//!< １クロックの分割数を設定します
	int divRound;	//!< １回転の分割数
}


/**
 時計の針を管理するクラス
*/
class ClockHandManager {

public:
	/// 毎回呼び出すなり
	void onMove(Screen screen) {
		foreach (inout ClockHand hand; clockHands) {
			hand.onMove(screen);
		}
	}
	
	/// 毎回呼び出すなり
	void onDraw(Screen screen, int baseX, int baseY) {
		foreach (inout ClockHand hand; clockHands) {
			hand.onDraw(screen, baseX, baseY);
		}
	}

	/// 針を追加する
	void addClockHand(ClockHand hand_) {
		clockHands.push_back(hand_);
	}
	
	/// コンストラクタ
	this() {
		clockHands = new vector!(ClockHand);
	}
	
private:
	vector!(ClockHand) clockHands;	//!< 針のベクター
	
}

/**
	時計表示用クラス
	なんも連携しない
*/
class ClockDisplaySimple {
	static final const int RENGE = 100;
	static final const int RATE = 128;
public:
	/// 毎回呼び出すなり
	void onMove(Screen screen) {
		stopCounter++;

		// 表示を入れ替えるのか？
		if ( stopCounter.isLapAround() ) {
			// まず回転中心を設定する
			radCounter++;
			
			ox = intx + ((rotateRenge * ( sin.cos( radCounter.get() ) )) >> 16);
			oy = inty + ((rotateRenge * ( sin.sin( radCounter.get() ) )) >> 16);
			
			// 入れ替え回転カウンタに初期値を設定
			changeRoteCounter.setInit( radCounter.get() );
			changeRoteCounter.reset();
			count = 0;
			move = true;
		}
		
		// 入れ替え中なんか？
		if (move) {
			++count;
			x = ox - ((rotateRenge * ( sin.cos( changeRoteCounter.get() ) )) >> 16);
			y = oy - ((rotateRenge * ( sin.sin( changeRoteCounter.get() ) )) >> 16);
			newx = ox - ((rotateRenge * ( sin.cos( changeRoteCounter.get()-RATE ) )) >> 16);
			newy = oy - ((rotateRenge * ( sin.sin( changeRoteCounter.get()-RATE ) )) >> 16);
			changeRoteCounter++;
			if ( count >= RATE/changeRoteCounter.getStep() ) {
				move = false;
				x = intx;
				y = inty;
				newx = -1;
				newy = -1;
				swapTexture();
			}
		}
		
		Rect rc;
		rc.setRect(x, y, x + cast(int)oldTexture.getWidth(), y + cast(int)oldTexture.getHeight());
		if ( collisionRect(dstRect, rc) ) {	
			drawOld = true;
		} else {
			drawOld = false;
		}
		
		rc.setRect(newx, newy, newx + cast(int)newTexture.getWidth(), newy + cast(int)newTexture.getHeight());
		if ( collisionRect(dstRect, rc) ) {
			drawNew = true;
		} else {
			drawNew = false;
		}
	}
	
	/// 毎回呼び出すなり
	void onDraw(Screen screen, int baseX, int baseY) {
		// テクスチャセットしてけれ
		if ( oldTexture == null || newTexture == null) return;
		// 描画
		if ( drawOld ) {
			screen.blt(oldTexture, x + baseX, y + baseY);
		}
		if ( drawNew ) {
			screen.blt(newTexture, newx + baseX, newy + baseY);
		}
	}
	
	/// 表示位置の取得
	void getXY(out int x_, out int y_) {
		x_ = x;
		y_ = y;
	}
	
	/// 表示矩形
	void setRect(Rect rc) {
		dstRect = rc;
		setXY(cast(int) dstRect.left, cast(int) dstRect.top);
	}
	
	/// 表示矩形
	void setRect(int left, int top, int right, int bottom) {
		dstRect.setRect(left, top, right, bottom);
		setXY(left, top);
	}
	
	/// 原回転の初期角度の設定
	void setInitRad(uint rad_) {
		radCounter.setInit(rad_);
		radCounter.reset();
	}
	
	/// 回転ｒの長さの設定
	void setRotateRange(uint len_) {
		rotateRenge = len_;
	}
	
	uint getRotateRange() {
		return rotateRenge;
	}

	/// 表示すべき画像のテクスチャセット
	void setTextureLoader(TextureLoader tl_) {
		int size = tl_.getInfoList().size();
		if ( 2 <= size ) {
			tl = tl_;
	
			// キャッシュに喰っておく
			tl.setCacheSize(-1);	// 無限キャッシュ
			for(int i = 0; i < tl.getInfoList().size(); ++i) {
				Texture t = tl.get(i);
			}
			
			oldTexture = tl.get(0);
			newTexture = tl.get( rand.get(size) );
		}
	}
	
	/// 表示を切り替えるまでのフレーム数
	void setStopTime(uint time_) {
		stopCounter.set(0, time_, 1);
	}

	/// コンストラクタ
	this() {
		stopCounter = new RootCounter();
		moveCounter = new RootCounterS();
		radCounter = new RootCounter(0, 512, 30);
		changeRoteCounter = new RootCounter(0, 512, 3);
		rotateRenge = RENGE;
	}
	
	/// 静的コンストラクタ
	static this() {
		sin = SinTable.get();
		rand = new Rand();
		rand.randomize();
	}
	
private:

	/// 表示位置の設定
	void setXY(int x_, int y_) {
		intx = x = x_;
		inty = y = y_;

		ox = x + ((rotateRenge * ( sin.cos( radCounter.get() ) )) >> 16);
		oy = y + ((rotateRenge * ( sin.sin( radCounter.get() ) )) >> 16);
	}
	
	/// 新旧のテクスチャをスワップします
	void swapTexture() {
		Texture tmp = oldTexture;
		oldTexture = newTexture;
		newTexture = tmp;
		int size = tl.getInfoList().size();
		
		newTexture = tl.get( rand.get(size) );

		++textureCounter;
	}
	
	/// 矩形同士のの当たり判定	
	bool collisionRect(inout Rect rc1, inout Rect rc2){
		// 高速判定 refered by D5（＾＾
	    long a = cast(int)(rc1.left - rc2.right) & cast(int)(rc2.left - rc1.right)
	                   & cast(int)(rc1.top - rc2.bottom) & cast(int)(rc2.top - rc1.bottom);
	    return ( (cast(uint) a) >> 31) ? true : false;
	}
	
protected:
	static SinTable sin;
	static Rand rand;
	
	Texture oldTexture;	//!< すでにある古いテクスチャ
	Texture newTexture;	//!< 新しいテクスチャ
	
	TextureLoader tl;
	
	Rect dstRect;
	bool drawOld;
	bool drawNew;
	int x;
	int y;
	int newx;
	int newy;
	int intx;
	int inty;
	uint rotateRenge;
	int ox;		//!< 回転の中心
	int oy; 	//!< 回転の中心
	bool move;
	int count;
	
	int textureCounter;
	
	RootCounter stopCounter;
	RootCounterS moveCounter;
	RootCounter radCounter;
	RootCounter changeRoteCounter;
	
}
