module yamalib.draw.filmnoise;

private import y4d_draw.screen;
private import y4d_draw.texture;
private import y4d_draw.texturebase;
private import y4d_draw.textureloader;
private import y4d_draw.drawbase;

private import y4d_math.rand;

private import ytl.vector;

private import yamalib.draw.drawassist;
private import yamalib.counterfsp;
private import yamalib.log.log;

class FilmNoise {
public:

	/// 静的コンストラクタ
	static this() {
		rand = new Rand();
	}

	/// コンストラクタ
	this(FilmNoiseEssenceManager essence) {
		this.m_essenceMgr = essence;
		this.m_useEssence = new vector!(FilmNoiseEssence);
		// ノイズを生成する時間を決めておく
		m_nextCreate = rand.get(this.m_essenceMgr.getCreateInterval());
	}
	
	/// マネージャ取得
	FilmNoiseEssenceManager getManager() {
		return this.m_essenceMgr;
	}
	
	/// 移動処理
	void onMove(Screen screen) {
		
		if ( m_intervalCount >= m_nextCreate ) {
			
			// まずは終了したノイズをクリアする
			int idx = 0;
			int[] delIndex;
			foreach (FilmNoiseEssence essence; m_useEssence) {
				if (essence.finish) {
					delIndex ~= idx;
				}
				++idx;
			}
			foreach (inout int i; delIndex) {
				m_useEssence.erase(i);
			}
			this.m_essenceMgr.checkUsedEssence();
			
			// サイズチェック
			if (m_useEssence.size() < this.m_essenceMgr.getMaxShowNum()) {
				m_useEssence.push_back(getEssence());
			}
		}
		
		foreach (FilmNoiseEssence essence; m_useEssence) {
			essence.onMove(screen);
		}
		
		++m_intervalCount;
	}
	
	/// マネージャからエッセンスの取得
	FilmNoiseEssence getEssence() {
		try {
			auto essence = this.m_essenceMgr.getUnuseEssenceRand();
			essence.reset();
			m_nextCreate = rand.get(this.m_essenceMgr.getCreateInterval());
			m_intervalCount = 0UL;
			return essence;
		} catch (Exception e) {
			Log.printError("Exception %s#getEssence : [%s] [%s]", 
				super.toString(), e.toString(), e.msg);
			throw e;
		}
	}
	
	Texture getNoiseTextureRand() {
		if (m_essenceMgr.getNoiseTextureLoader() is null) {
			return null;
		}
		auto tl = m_essenceMgr.getNoiseTextureLoader();
		int texNum = tl.getInfoList().size();
		return tl.get( rand.get(texNum) );
	}
	
	/// 描画処理
	void onDraw(Screen screen) {

		Color4ub colorOrg = screen.getColor4ub();		
		float rate = m_essenceMgr.getRate();

		// ノイズ描画
		if ( !(m_essenceMgr.getNoiseTextureLoader() is null) ) {
			int drawNum = m_essenceMgr.getDrawNoiseRand()==0 ?
				m_essenceMgr.getDrawNoiseMax() :
				 m_essenceMgr.getDrawNoiseMax() - rand.get(m_essenceMgr.getDrawNoiseRand());
			int sx = screen.getWidth();
			int sy = screen.getHeight();
			screen.setColor(colorOrg.r, colorOrg.g, colorOrg.b, 128);
			
			if (rate == 1.0f || rate == float.nan ) {
				for (int i = 0; i < drawNum; ++i) {
					screen.blt(getNoiseTextureRand(), rand.get(sx), rand.get(sy));
				}
			} else {
				for (int i = 0; i < drawNum; ++i) {
					screen.bltRotate(getNoiseTextureRand(), 
								cast(int) (rand.get(sx) * rate),
								cast(int) (rand.get(sy) * rate),
								0,
								rate,
								0); 
				}
			}
			
			screen.setColor(colorOrg);
		}
		
		foreach (FilmNoiseEssence essence; m_useEssence) {
			if (rate == 1.0f || rate == float.nan ) {
				essence.onDraw(screen);
			} else {
				essence.onDraw(screen,rate);
			}
		}
	}

	///FilmNoiseクラスの全体の動作を決めるエッセンス
	public static class  FilmNoiseEssenceManager {
		/// 追加する
		void addEssence(FilmNoiseEssence essence) {
			if ( essence.isValid() ) {
				m_unuseFlg ~= true;
				m_essencese ~= essence;
			}
		}

		/// ノイズテクスチャセットの設定		
		void setNoiseTextureLoader(TextureLoader tl) {
			this.m_noiseTextureLoader = tl;
		}
		TextureLoader getNoiseTextureLoader() {
			return this.m_noiseTextureLoader;
		}
		
		/// ノイズを描画する数
		void setDrawNoiseNum(uint max_, uint rand_) {
			this.m_drawNoiseMax = max_;
			this.m_drawNoiseRand = rand_;
		}
		uint getDrawNoiseMax() {
			return this.m_drawNoiseMax;
		}
		uint getDrawNoiseRand() {
			return this.m_drawNoiseRand;
		}
		
		/// すべてのエッセンスを取得
		FilmNoiseEssence[] getEssenceAll() {
			return this.m_essencese;
		}

		/// 指定したエッセンスを取得する		
		FilmNoiseEssence getEssence(int index) {
			if (index >= this.m_essencese.length) {
				return null;
			}
			return this.m_essencese[index];
		}

		/// 未使用のエッセンスをランダムで取得する
		/// 使用／未使用のマーカーはここで付与するので、他からの参照で使われていても関知しない
		FilmNoiseEssence getUnuseEssenceRand() {
			try {
				int[] unuseIndex;
				foreach (int i,inout bool b; m_unuseFlg) {
					if (b) {
						unuseIndex ~= i;
					}
				}
				
				int index = rand.get( unuseIndex.length );
				int num = unuseIndex[index];

//printf(" %d", num);
				return getEssence(num);
			} catch (Exception e) {
				Log.printError("Exception %s#getUnuseEssenceRand : [%s] [%s]", 
					super.toString(), e.toString(), e.msg);
				throw e;
			}
		}
		
		/// 使用・未使用マーカーを巡回し、終了したものを未使用にマークする
		void checkUsedEssence() {
			try {
				int[] useIndex;
				foreach (int i,inout bool b; m_unuseFlg) {
					if (!b) {
						if (m_essencese[i].finish) {
							m_unuseFlg[i] = true;
						}
					}
				}
			} catch (Exception e) {
				Log.printError("Exception %s#checkUsedEssence : [%s] [%s]", 
					super.toString(), e.toString(), e.msg);
				throw e;
			}
		
		}
		
		/// 削除する
		void clear() {
			m_essencese = null;
		}
		
		/// 一画面に表示する最大ラインノイズ数
		void setMaxShowNum(uint num) {
			m_showNum = num;
		}
		int getMaxShowNum() {
			return this.m_showNum;
		}
		
		/// ノイズを生成するまでのインターバルを設定する
		void setCreateInterval(int interval) {
			this.m_createInterval = interval;
		}
		int getCreateInterval() {
			return this.m_createInterval;
		}
		
		/// 拡大率の設定
		void setRate(float rate) {
			this.m_rate = rate;
		}
		float getRate() {
			return this.m_rate;
		}
		
		/// コンストラクタ
		this() {
		}
		
	private:
		FilmNoiseEssence[] m_essencese;
		TextureLoader m_noiseTextureLoader;
		int m_showNum = 3;	//!< 一画面に表示する最大ノイズ数
		int m_createInterval = 100;
		uint m_drawNoiseMax;
		uint m_drawNoiseRand;
		bool[] m_unuseFlg;
		float m_rate = 1.0f;
	}

	/// FilmNoiseクラスの動作を決めるエッセンス
	public static class FilmNoiseEssence {
		
		/// 移動処理
		protected void onMove(Screen screen) {
			if ( !isValid() || m_finish) {
				return;
			}

			if ( (start > end) ) {
				// 右から左
				
				if ( m_revesing ) {
					m_y += m_speed;
					if (m_y > start) {
						checkFlg();
					}
				} else {
					m_y -= m_speed;
					if (m_y < end) {
						checkFlg();
					}
				}
			} else {
				// 左から右

				if ( m_revesing ) {
					m_y = m_speed;
					if (m_y < start) {
						checkFlg();
					}
				} else {
					m_y += m_speed;
					if (m_y > end) {
						checkFlg();
					}
				}
			}
		}
		
		/// 描画処理
		protected void onDraw(Screen screen) {
			if ( !isValid() || m_finish) {
				return;
			}
			
			Color4ub colorOrg = screen.getColor4ub();
			screen.setColor( colorOrg.r, colorOrg.g, colorOrg.b, this.m_alpha );
			screen.blt(this.m_texture, cast(int) m_y + rand.get(this.m_posRand), 0 );
			screen.setColor(colorOrg);
		}
		
		/// 描画拡大あり
		protected void onDraw(Screen screen, float rate) {
			if ( !isValid() || m_finish) {
				return;
			}
			
			Color4ub colorOrg = screen.getColor4ub();
			screen.setColor( colorOrg.r, colorOrg.g, colorOrg.b, this.m_alpha );
			screen.bltRotate(this.m_texture, 
						cast(int) ((m_y + rand.get(this.m_posRand) * rate)), 0,
						0,
						rate,
						0 );
			screen.setColor(colorOrg);
		}
		
		/// 有効なエッセンスか
		bool isValid() {
			if (this.m_texture is null || m_speed == float.nan
			) {
				return false;
			}
			return true;
		}
		
		/// 内部変数を初期化し、再動作可能にします
		void reset() {
			m_y = start;
			m_revesing = false;
			m_finish = false;
		}

		/// スピードの設定・取得		
		float speed(float speed_) {
			return this.m_speed = speed_;
		}
		float speed() {
			return this.m_speed;
		}
		
		/// 描画開始位置の設定
		int start() {
			return this.m_startY;
		}
		int start(int y) {
			this.m_y = y;
			return this.m_startY = y;
		}
		
		/// 描画終了位置の設定
		int end() {
			return this.m_endY;
		}
		int end(int y) {
			return this.m_endY = y;
		}
		
		/// 折り返すかどうか
		bool reverse() {
			return this.m_reverse;
		}
		bool reverse(bool b) {
			return this.m_reverse = b;
		}
		
		/// アルファ値の設定
		int alpha() {
			return this.m_alpha;
		}
		int alpha(int alpha_) {
			return this.m_alpha = alpha_;
		}
		
		/// テクスチャの設定
		Texture texture() {
			return this.m_texture;
		}
		Texture texture(Texture texture_) {
			return this.m_texture = texture_;
		}
		
		/// 描画位置のズレ許容値
		int drawRand() {
			return this.m_posRand; 
		}
		int drawRand(int rand_) {
			return this.m_posRand = rand_;
		}
		
		bool finish() {
			return this.m_finish;
		}
		
		static this() {
			rand = new Rand();
		}
		
	private:
		/// フラグ遷移を行います
		void checkFlg() {
			if ( m_reverse ) {
				if ( m_revesing ) {
					m_finish = true;
				} else {
					m_revesing = true;
				}
			} else {
				m_finish = true;
			}
		}
		
		static Rand rand;
	
		Texture m_texture;	//!< ノイズテクスチャ
		int m_alpha;
		bool m_reverse;	//!< 折り返し描画を行うか
		float m_speed;	// 移動速度
		int m_startY;	// 開始位置
		int m_endY;	// 終了位置
		int m_posRand = 5;	//! 描画位置のズレ
		
		float m_y;
		bool m_revesing;
		bool m_finish;
	}

private:
	static Rand rand;
	FilmNoiseEssenceManager m_essenceMgr;	//!< このクラスの動作を決めるエッセンス
	vector!(FilmNoiseEssence) m_useEssence;
	ulong m_intervalCount;
	ulong m_nextCreate;

}

/** エッセンスをランダムを作成するクラス
 * 
 */
class FilmNoiseEssenceCreator {
	
	/// エッセンスの最大幅と、その最大幅のランダム値
	static void setMaxWidth(uint width, uint randWidth) {
		maxWidth = width;
		maxWidthRand = randWidth;
	}
	
	/// エッセンスの最大速度と、その最大速度のランダム値
	static void setMaxSpeed(float maxSpeed_, float randSpeed_) {
		maxSpeed = maxSpeed_;
		maxSpeedRand = randSpeed_;
	}
	
	/// 開始位置と終了位置のの範囲
	static void setRange(int start_, int end_) {
		if (start > end) {
			start = end_;
			end = start_;
		} else {
			start = start_;
			end = end_;
		}
	} 
	
	/// エッセンスの最大アルファと、その最大アルファのランダム値
	static void setMaxAlpha(uint maxAlpha_, uint alphaRand_) {
		maxAlpha = maxAlpha_;
		maxAlphaRand = alphaRand_;		
	}
	
	/// エッセンスのデフォルトの折り返し設定と、その折り返しをランダムで決定するか
	static void setReverseDefault(bool def, bool reverseRand) {
		reverseDefault = def;
		reverseDefaultRand = reverseRand;
	}

	/// エッセンスを作成し返却する
	static FilmNoise.FilmNoiseEssence create() {
		auto essence = new FilmNoise.FilmNoiseEssence();

		int width = maxWidthRand==0 ? maxWidth : maxWidth - rand.get(maxWidthRand);
		int alpha = maxAlphaRand==0 ? maxAlpha : maxAlpha - rand.get(maxAlphaRand);
		float speed = (maxSpeedRand==0.0f || maxSpeedRand==float.nan) ? maxSpeed : 
			maxSpeed - (rand.get( cast(int) (maxSpeedRand * 256) ) / 256.0f);
		bool reverse = reverseDefaultRand ? rand.get(2)==0 : reverseDefault;

		essence.start = start + rand.get(end - start);
		essence.end = rand.get(2)==0 ? essence.start - width : essence.start + width;
		essence.speed = speed;
		essence.alpha = alpha;
		essence.reverse = reverse;
		
		return essence;
	}

	/// 静的コンストラクタ	
	static this() {
		rand = new Rand();
	}
	
private:
	this() {
		// インスタンスは生成不可
	}

	static Rand rand;
	static int maxWidthRand = 0;
	static int maxWidth = 100;
	static int maxAlpha = 255;
	static int maxAlphaRand = 0;
	static int start;
	static int end;
	static float maxSpeed = 1.0f;
	static float maxSpeedRand = 0.0f;
	static bool reverseDefault = false;
	static bool reverseDefaultRand = true;	
}

/**
 * フィルムノイズをかぶせるときに、背景もそれらしいのにして
 * ブレを表現する
 */
class FilmNoiseBgDraw {
	
	/// コンストラクタ
	this(uint interval, uint yRand, uint duration)
	in
	{
		assert( interval != 0 );
		assert( yRand != 0 );
	}
	body
	{
		m_interval = interval;
		m_yRand = yRand;
		m_orgDuration = m_duration = duration;
		m_nextCount = rand.get(interval);
	}
	
	/// ノイズを今ＯＮにする
	void noiseDrawNow(int dy, int duration) {
		noiseDrawOn(dy);
		m_duration = duration;
	}
	
	/// 今、ずれ描画しているところか？
	bool isNoising() {
		return this.m_noiseDraw;
	}
		
	/// 移動処理
	void onMove(Screen screen) {
		
		if (m_noiseDraw) {
			++m_durationCount;
			if (m_durationCount >= m_duration) {
				m_noiseDraw = false;
				m_durationCount = 0;
				m_duration = m_orgDuration;
				m_oldTexture = null;
			}
		} else {
			if (m_intervalCount >= m_nextCount) {
				noiseDrawOn(rand.get(m_yRand) - (m_yRand / 2));
			} else {
				m_noiseDraw = false;
			}
			++m_intervalCount;
		}
	}
	
	/// 描画処理
	void onDraw(Screen screen, ITextureBase texture, int x, int y) {
		Color4ub colorOrg = screen.getColor4ub();		

		if (m_noiseDraw) {
			if (m_oldTexture is null) {
				screen.blt(texture, x, y);
			} else {
				screen.blt(m_oldTexture, x, y);
			}
			screen.setColor(255,255,255,colorOrg.a/2);
			screen.blt(texture, x, y + m_randSize);
		} else {
			screen.blt(texture, x, y);
			m_oldTexture = texture;
			
		}

		screen.setColor(colorOrg);
	}

	/// DrawAssistを使用して描画
	void onDraw(DrawAssist.Context context, 
		Screen screen, ITextureBase texture, int x, int y) {
		Color4ub colorOrg = screen.getColor4ub();		

		if (m_noiseDraw) {
			if (m_oldTexture is null) {
				DrawAssist.blt(screen, texture, x, y, context);
			} else {
				DrawAssist.blt(screen, m_oldTexture, x, y, context);
			}
			screen.setColor(255,255,255,colorOrg.a/2);
			DrawAssist.blt(screen, texture, x, y + m_randSize, context);
		} else {
			DrawAssist.blt(screen, texture, x, y, context);
			m_oldTexture = texture;
		}

		screen.setColor(colorOrg);
	}
	
	/// 静的コンストラクタ
	static this() {
		rand = new Rand();
	}
	
private:
	/// noiseDrawの準備をする dy = ずれ幅
	void noiseDrawOn(int dy) {
		m_nextCount = rand.get(m_interval);
		m_randSize = dy;
		m_intervalCount = 0;
		m_noiseDraw = true;
	}
	
private:
	static Rand rand;
	uint m_interval;
	uint m_duration;
	uint m_orgDuration;
	uint m_yRand;
	
	ITextureBase m_oldTexture;
	bool m_noiseDraw;
	int m_randSize;
	ulong m_nextCount;
	ulong m_durationCount;
	ulong m_intervalCount;
}
