require 'rarg'

require 'darkhall/savedata'
require 'darkhall/game/debug_monitor'

module DarkHall
	module Game
		@cyclic_frame_count = 0
		@reporter = nil
	end
	
	class << Game
		attr_accessor :reporter
		bool_attr_accessor :debug_mode
		def text_buffer
			@text_buffer ||= SDL::Surface.new(SDL::SRCCOLORKEY, $screen.w, Game.regular_ttf.line_skip + 6, $screen).display_format
		end
		
		def put_title(dest = $screen, x = 440, y = 360)
			shadow = 4
			Game.title_ttf.draw_blended_utf8(dest, "Dark Hall", x + 4, y + 4, *[60, 60, 60])
			Game.title_ttf.draw_blended_utf8(dest, "Dark Hall", x, y, *Color::WHITE)

		end
		



		def timelog(title = "Time", indent_level = 1)
			log = "\t" * indent_level + sprintf(TIMELOG_FORMAT, title, Benchmark.realtime{yield})
			LOGGER.puts(log)
		end

		
		def current_member
			GS.party.members[$member_index]
		end
		
		def current_member_id
			GS.members.index(Game.current_member)
		end 
		
		def current_window
			$windows.last
		end
		
		def on_found(type, id)
			GS.found_model_switches[type.to_s][id.to_s] = true
		end
		
		
			
			
			
		def save(level = FQ_CHANGE, scene_title = '?', force = false)
			if savable? or force then
				$savedata.save(level, scene_title)
				if Game.debug_mode? then
					@debug_monitor.last_save_level = level
					@debug_monitor.last_save_scene_title = scene_title
					@debug_monitor.save_display_remaining_time = 2
				end

			else
				LOGGER.warn('save command is blocked. (Game.savable? is false)')
			end
		end
		
		def savable?
			!($section == Section::BATTLE)
		end

	
		
		def message(original_msgs)
			original_msgs = [original_msgs] unless original_msgs.kind_of?(Array) # back compatibility
			return if original_msgs.compact.empty?
			
			# メッセージウインドウの行数を超えていれば分割
			#msgs = []
			#original_msgs.each do |msg|
			#	msg = Message.new(msg) unless msg.kind_of?(Message)
			#	if msg.lines.size > 4 then
			#		new_1 = Message.new(msg.lines.slice(0...4))
			#		new_2 = Message.new([msg.lines.slice(0)] + msg.lines.slice(4..-1))
			#		msgs << new_1 << new_2
			#	else
			#		msgs << msg
			#	end
			#end
			
			msgs = original_msgs

			
	
			# ウインドウ配置
			if $section == Section::BATTLE then
				MESSAGE_WINDOW.set_position(:center, 16)
			else
				MESSAGE_WINDOW.centering(false)
			end
			
			yield(MESSAGE_WINDOW) if block_given?
	
			
	
			if msgs.first.kind_of?(Message) then
				MESSAGE_WINDOW.silent = true
				line_max = msgs.map{|x| x.lines.size}.max
				
				msgs.each do |msg|
					body = msg.lines.find_all{|x| not x.nil? and not x.empty?}.join("\n")
					if msg.lines.size < line_max then
						body.concat("\n" * (line_max - msg.lines.size))
					end

					MESSAGE_WINDOW.update(body).show
	
					msg.play_se
	
					case msg.effect_type
					when Message::Effect::ENEMY_DAMAGE
						shake_windows([msg.target_window], msg.shaking_level)
					when Message::Effect::MEMBER_DAMAGE
						shake_windows([PARTY_WINDOW], msg.shaking_level)
					end
	
					Game.operation_loop("MessageLoop")
				end
			else
			# 以下、旧仕様（obsolete）
				MESSAGE_WINDOW.silent = false
				upside = msgs.shift
				downsides = msgs
				
		
				if downsides.empty? then
					MESSAGE_WINDOW.update(upside).show
					Game.operation_loop("MessageLoop")
					MESSAGE_WINDOW.hide
		
				else
					line_max = (upside.count("\n") + 1) + downsides.map{|x| x.count("\n") + 1}.max
			
					downsides.each do |downside|
						msg = (downside.empty? ? upside.dup : (upside + "\n" + downside))
			
						if (line = msg.count("\n") + 1) < line_max then
							msg.concat("\n " * (line_max - line))
							
						end
						MESSAGE_WINDOW.update(msg).show
						Game.operation_loop("MessageLoop")
					end
		
				end
				
	
			end
	
			MESSAGE_WINDOW.hide
		end
		
		
		SHAKE_VALUES = {}
		SHAKE_VALUES[1] = [+4, -8, +6, -4, +3, -1]
		SHAKE_VALUES[2] = [+6, -12, +10, -8, +6, -4, +3, -1]
		def shake_windows(targets, level = 1)
			alpha = 255
			(SHAKE_VALUES[level] || SHAKE_VALUES[1]).each do |s|
				targets.each do |win|
					win.left += s
					win.top += s
				end
				
				refresh
			end
		end
		
		def dungeon_message(msgs)
			message(msgs){|window|
				window.set_position(:center, DUNGEON_WINDOW.bottom - 24)
			}
		end
		
		def show_debug_data_window(texts = [[]])
			DEBUG_DATA_WINDOW.set_good_width(texts).make_surface(nil, texts.size).update(texts).centering(false).show
			Game.operation_loop
			DEBUG_DATA_WINDOW.hide
		end
		
		
		def ask(message, option_hash = {})
			opt = RArg.parse(option_hash) do
				default_arg :default, :yes
				default_arg :silent, false
			end
		
			MESSAGE_WINDOW.centering(false).update(message).show
			YN_WINDOW.make_surface
			case opt[:default]
			when :yes
				YN_WINDOW.index = 0
			when :no
				YN_WINDOW.index = 1
			when nil
				YN_WINDOW.index = nil
			end
	
			YN_WINDOW.dock_beside(MESSAGE_WINDOW, :bottom).set_position(:center, YN_WINDOW.top)
			YN_WINDOW.warp.show
			YN_WINDOW.silent = opt[:silent]
			
			#dec_win = DecisionWindow.new.make_surface.update.centering
			#Window.centering_y([MESSAGE_WINDOW, dec_win])
			#dec_win.warp.show
			
					
			answer = Game.operation_loop("AskLoop")
			YN_WINDOW.hide
			MESSAGE_WINDOW.hide
			return answer
		end
		
		def ask_about_item_recieving(item, ask_message, option_hash = {}, &getting_proc)
			opt = RArg.parse(option_hash) do
				default_arg :full_message, _("これ以上、アイテムを持てる者がいない")
			end
			
			if ask(ask_message) then
				getter = GS.party.get_item(item)
				if getter then
					getting_proc.call if getting_proc
					SE.equip
					message _("%{getter}は$c[item]%{item}$cを手に入れた").evaluate(:getter => getter.name, :item => item.name)
					Game.save(FQ_BIG_CHANGE, "#{item.name}入手")
				else
					message(full_message)
				end
				
				return true

			else
				return false
			end
		end
		
		def select(select_items, option_hash = {})
			# MEMO: アイテムが空の時の表示
			select_items = [SelectItem.new('        ')] if select_items.empty?
			
			opt = RArg.parse(option_hash) do
				default_arg :selected, true
				default_arg :display_max, nil
				default_arg :silent, false
				default_arg :default_id, nil
			end
		
			window = TemporarySelectableWindow.new
			width = select_items.map{|x| Game.regular_ttf.text_size(x.caption)[0]}.max + 16
			if opt[:display_max] then
				window.make_surface(width, opt[:display_max])
				window.display_max = opt[:display_max]
				window.update(select_items).centering
			else
				window.make_surface(width, select_items.size).update(select_items).centering
			end
			window.silent = opt[:silent]

			
			yield(window) if block_given?
			
			if opt[:selected] then
				if opt[:default_id] then
					window.set(opt[:default_id])
					unless window.index then
						window.reset_index
					end
				else
					window.regulate_index
				end
			else
				window.index = nil
			end
			window.regulate_display
			window.show.warp
			answer = Game.operation_loop('SelectingLoop')
			window.hide
			
			return answer
		end
		
		
		def member_select(caption, option_hash = {})
			# 旧仕様互換
			unless option_hash.kind_of?(Hash) then
				index = option_hash
				option_hash = {:default_member_index => index}
			end
		
			opt = RArg.parse(option_hash) do
				default_arg :silent, false
				default_arg :default_member_index, nil
			end

			INFORMATION_WINDOW.make_surface(160).align = AL_LEFT
			INFORMATION_WINDOW.update(caption).dock_beside(PARTY_WINDOW, :top).show
	
			if opt[:default_member_index] then
				PARTY_WINDOW.index = opt[:default_member_index]
			else
				PARTY_WINDOW.reset_index
			end
			PARTY_WINDOW.show_shortcut.update_surface
			PARTY_WINDOW.silent = opt[:silent]
			PARTY_WINDOW.show.warp
	
			selected = Game.operation_loop('MemberSelectLoop')
			INFORMATION_WINDOW.hide
			PARTY_WINDOW.update
			$windows.delete(PARTY_WINDOW)
			$windows.unshift(PARTY_WINDOW)
			return selected
		end
		
		def alive_members_select
			PARTY_WINDOW.show.warp
			PARTY_WINDOW.cursor_indexies = GS.party.alive_members.map{|x| GS.party.members.index(x)}
			re = Game.operation_loop('AliveMembersSelectLoop')
			PARTY_WINDOW.cursor_indexies = nil
			
			re
		end
		
		def number_input(caption, default = 0, range = 0..999)
			text_win = TextWindow.new
			text_win.text = caption
			text_win.set_good_width.make_surface(nil, 1).update
			text_win.centering.show
			win = NumberInputWindow.new(default, range).make_surface.update
			win.centering.show
			Window.centering_y([text_win, win])
			re = Game.operation_loop('NumberInputLoop')

			text_win.hide
			win.hide

			re
		end
		
		def battle(troop, appearing_message = _('敵と遭遇した！'), dummy = nil)
			$troop = troop
			$troop.groups.each do |group|
				Game.on_found('enemy', group.first.data_id)
			end
			MESSAGE_WINDOW.make_surface(nil, 5).update
			
			dungeon_phase = $phase
			Phase.change(BattleStartPhase.new(appearing_message))
			result = Game.operation_loop('BattleLoop')
			Phase.change(dungeon_phase)
			MESSAGE_WINDOW.make_surface(nil, 6).update
			Game.clear_event_pool
			return result
		end
		
		def seal_system_menu
			@system_menu_sealed = true
			yield
			@system_menu_sealed = false
		end
		
		def system_menu
			if @system_menu_sealed then
				SE.cancel
			end
			
			if not $phase.kind_of?(TitlePhase) and not $in_system_menu then
				SE.select
				$in_system_menu = true
				items = []
				#items << SelectItem.new(:save, 'クイックセーブ(F2)', SDL::Key::F2)
				#if $section == Section::BATTLE then
				#	items.last.disable("戦闘中はセーブできません")
				#end
				
				items << SelectItem.new(:open_manual, _('プレイヤーズマニュアルを開く(F1)'), SDL::Key::F1)
				items << SelectItem.new(:config, _('ゲーム設定(F2)'), SDL::Key::F2)
				items << SelectItem.new(:title, _('タイトル画面に戻る(F3)'), SDL::Key::F3)
				items << SelectItem.new(:quit, _('ゲーム終了(F4)'), SDL::Key::F4)
				items << SelectItem.new(:debug, '＊デバッグメニュー') if Game.debug_mode?
				items << SelectItem.new(:cancel, _('キャンセル'))
				
				return_to_title_proc = proc do
					BGM.fade_out
					Game.message(_('タイトル画面に戻ります...'))
					Phase.change(TitlePhase.new)
				end
				
				re = Game.select(items)
				
				case re
				when :open_manual
					Util.open_manual
				when :save
					Game.now_saving{
						Game.save(FQ_QUIT)
					}
				when :title
					if savable? then
						Game.save(FQ_QUIT)
						return_to_title_proc.call
					else
						if Game.ask(_("戦闘中のためデータは記録されません。\n本当にタイトル画面に戻ってもよろしいですか？")) then
							return_to_title_proc.call
						end
					end
				when :quit
					if savable? then
						Game.now_saving{
							Game.save(FQ_QUIT)
						}
						Game.quit
					else
						if Game.ask(_("戦闘中のためデータは記録されません。\n本当に終了してよろしいですか？")) then
							Game.quit
						end

					end
				when :config
					Phase.change(GameConfigPhase.new)
					Game.operation_loop(:GameConfigLoop)
					Phase.back
				
				when :debug
				
					loop do
						items = []
						items << SelectItem.new(:get_gold, '金貨の支給')
						items << SelectItem.new(:recover, "HP＆MP＆状態全回復")
						items << SelectItem.new(:revive, '全員蘇生')
						items << SelectItem.new(:kill, "メンバー即死")
						items << SelectItem.new(:soul, "メンバー霊魂化")
						items << SelectItem.new(:change_rule, 'ルール変更')
						items << SelectItem.new(:change_age, '年齢変更')
						items << SelectItem.new(:error, "強制エラー")
	
						items << SelectItem.new(:cancel, 'キャンセル')
						re = Game.select(items)
						case re
						when :get_gold
							SE.coin
							GS.party.members.first.get_gold(200)
						when :kill
							index = Game.member_select("だれを？")
							if index then
								GS.party.members[index].damage(9999)
							end
						when :soul
							index = Game.member_select("だれを？")
							if index then
								GS.party.members[index].damage(9999)
								GS.party.members[index].soul = true
							end
						when :revive
							GS.existing_members.each{|x| x.revive}
							Game.message('死亡メンバー全員を蘇生しました')
						when :recover
							GS.party.members.each do |member|
								member.revive
								member.hp_max_penalty = 0
								member.recover_hp(999)
								member.recover_mp(999)
								member.added_status.clear
							end
							PARTY_WINDOW.update
							Game.message("HP・MPを全回復し、状態を元に戻しました")
						when :error
							raise('forced error')
						when :change_rule
							items = %w(ShortTimeRule NormalRule HardRule).map{|x| SelectItem.new(x, x)}
							re = Game.select(items, :default_id => GS.rule.class.to_s.split('::').last)
							unless re == :cancel then
								GS.rule = eval("#{re}.new")
							end
						when :change_age
							index = Game.member_select("だれを？")
							if index then
								member = GS.party.members[index]
								member.age = Game.number_input('新しい年齢を入力', member.age) || member.age
								member.birth_week = GS.week - member.age * WEEK_PER_YEAR
							end

						when :cancel
							break
						end
						
						PARTY_WINDOW.update
					end
					
				end
				$in_system_menu = false
			end

		end

		
		
			
		def cycle_count
			@cyclic_frame_count += 1
			if @cyclic_frame_count >= FPS then
				@cyclic_frame_count = 0
				GS.playing_time += 1
			end
		end

			
		def refresh
			@timer.wait_frame do
				draw_screen
			end
			cycle_count
		end
		
		def wait(frame, input_reservable = false)
			frame.times do
				@timer.wait_frame do
					draw_screen
				end
				cycle_count
			end
			
			clear_event_pool unless input_reservable
			
		end
		
		def clear_event_pool
			# イベントキューを空っぽにする
			while SDL::Event2.poll do
			end
		end
		
		def draw_screen
	
			# カーソル点滅処理
			if SETTING.use_alpha_effect? then
				if $cursor_alpha_increasing then
					$cursor_alpha += 5
				else
					$cursor_alpha -= 5
				end
				
				if $cursor_alpha >= 255 then
					$cursor_alpha = 255
					$cursor_alpha_increasing = false
				elsif $cursor_alpha <= 160 then
					$cursor_alpha = 160
					$cursor_alpha_increasing = true
				end
			end
			
			# 画面再描画
			$screen.fill_rect( 0, 0, $screen.w, $screen.h, [ 0, 0, 0 ] )
			if SETTING.use_alpha_effect? && current_window.kind_of?(SelectableWindow) then
				$windows.last.cursor_surface.set_alpha(SDL::SRCALPHA, $cursor_alpha)
			elsif SETTING.use_alpha_effect? && $phase.kind_of?(DungeonMapPhase) && current_window.kind_of?(DungeonMapWindow) then
				$windows.last.cursor_surface.set_alpha(SDL::SRCALPHA, $cursor_alpha)
				$windows.last.range_cursor_surface.set_alpha(SDL::SRCALPHA, $cursor_alpha)
			end
			
			blit_time = Benchmark.realtime do
				$windows.each do |window|
					window.blit
				end
			end
				
			# FPS表示
			if Game.debug_mode? then
				@debug_monitor.on_every_frame(@timer, @cyclic_frame_count, blit_time)
			end
	
	
			$screen.flip
		end
	
			
			
			
	
		def set_enemy_positions
			# 全員を並べたときの総横幅を計算
			front_total_width = 0
			back_total_width = 0
			
			$troop.front_groups.each do |enemies|
				front_total_width += (enemies.first.graphic.width * enemies.size)
				front_total_width += (enemies.first.graphic.side_margin * (enemies.size - 1))
				
				front_total_width += GROUP_MARGIN
				
			end
			
			front_total_width -= GROUP_MARGIN
			
			$troop.back_groups.each do |enemies|
				back_total_width += (enemies.first.graphic.width * enemies.size)
				back_total_width += (enemies.first.graphic.side_margin * (enemies.size - 1))
				
				back_total_width += GROUP_MARGIN
				
			end
			
			back_total_width -= GROUP_MARGIN

			area_width = SCREEN_WIDTH - ENEMY_AREA_MARGIN * 2

	
			if front_total_width > area_width then
				sizing_rate = (area_width.to_f / front_total_width.to_f)
				cx = ENEMY_AREA_MARGIN
			else
				sizing_rate = 1
				cx = ENEMY_AREA_MARGIN + area_width / 2 - front_total_width / 2
			end
			
			# 前列の敵を配置
			$troop.front_groups.each do |enemies|
				one_width = (enemies.first.graphic.width * sizing_rate).to_i
				one_side_margin = (enemies.first.graphic.side_margin * sizing_rate).to_i
				
				enemies.each do |enemy|
					enemy.window.left = cx
	
					enemy.window.top = PARTY_WINDOW.top - 8 - enemy.graphic.bottom_margin - enemy.graphic.height
					cx += one_width
					cx += one_side_margin
				end
				
				cx -= one_side_margin
				cx += (GROUP_MARGIN * sizing_rate).to_i
			end
			
			cx = ENEMY_AREA_MARGIN


			if back_total_width > area_width then
				sizing_rate = (area_width.to_f / back_total_width.to_f)
				cx = ENEMY_AREA_MARGIN
			else
				sizing_rate = 1
				cx = ENEMY_AREA_MARGIN + area_width / 2 - back_total_width / 2
			end

			# 後列の敵を配置
			$troop.back_groups.each do |enemies|
				one_width = (enemies.first.graphic.width * sizing_rate).to_i
				one_side_margin = (enemies.first.graphic.side_margin * sizing_rate).to_i
				
				enemies.each do |enemy|
					enemy.window.left = cx
	
					enemy.window.top = PARTY_WINDOW.top - 8 - enemy.graphic.bottom_margin - enemy.graphic.height - 16
					cx += one_width
					cx += one_side_margin
				end
				
				cx -= one_side_margin
				cx += (GROUP_MARGIN * sizing_rate).to_i
			end


		

			
			return self
		end
		
		def fade_in_enemy_windows(fade_time = 10)
			if SETTING.use_alpha_effect? then
				(0...fade_time).each do |i|
					rate = i.to_f / fade_time
					$enemy_windows.each do |win|
						win.set_alpha_rate(rate)
					end
					Game.refresh
				end
				$enemy_windows.each{|x| x.reset_alpha}
				return true
			else
				return false
			end
		end
		
		def fade_out_enemy_windows(fade_time = 10)
			if SETTING.use_alpha_effect? then
				(0...fade_time).each do |i|
					rate = (1.0 - i.to_f / fade_time)
					rate = i.to_f / fade_time
					$enemy_windows.each do |win|
						win.set_alpha_rate(rate)
					end
					Game.refresh
				end
				$enemy_windows.each{|x| x.set_alpha_rate(1.0)}
				return true
			else
				return false
			end
		end
		
		def now_saving(&proc)
			working(_('セーブ中...'), &proc)
			SE.equip
		end
		
		def now_loading(&proc)
			working(_('読み込み中...'), &proc)
		end
		
		def working(msg, width = nil)
			win = TextWindow.new
			if width then
				win.width = width
			else
				win.set_width_from_text(msg)
			end
			
			win.make_surface
			win.align = AL_CENTER
			win.update(msg).centering
			win.show
			refresh
			yield
			win.hide
			
			return self
		end
	
	
		
		def flash
			$screen.fill_rect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, [ 255, 255, 255 ] )
			$screen.flip
		end



		def add_default_member(member_cls, level, age = :Young)
			member = member_cls.new(level, age)
			new_name = nil
			1.upto(256) do |i|
				new_name = sprintf("%s%d", member.class_caption, i)
				unless GS.members.compact.find{|x| x.name == new_name} then
					break
				end
			end
			member.name = new_name
			GS.members << member
			GS.bar_member_ids << (GS.members.size - 1)
		end

		
		def set_merchant_products
			LOGGER.puts "デミトリの商品入れ替え"
			GS.merchant_products.clear
			list = [TRT::WEAPON, TRT::ARMOR, TRT::ITEM, TRT::ACCESSORY]
			12.times do
				t = TREASURE_TABLE.pick(rand(4) + 1, random_pick(list))
				if t and not t.kind_of?(BundleItem) then
					t.identify if t.respond_to?(:identify)
					t.set_prefix if  t.respond_to?(:set_prefix)
					GS.merchant_products << t
				end
			end
			
			# 無くしたレジェンドアイテム回収
			legends = DB.items.find_all{|x| x.legendary? and GS.found_item?(x.id)}
			legends.each do |model|
				next if GS.existing_members.find{|x| x.find_item(model.id)}
				next if GS.shop_stocks.find{|id, n| id == model.id}
				next if GS.merchant_products.find{|x| x.data_id == model.id}
				
				GS.merchant_products << Item.new(model.id)
			end
			
			GS.merchant_products.each do |item|
				LOGGER.puts "\t#{item.name} (#{item.price})"
			end
		end
	
		
		def set_room_events
			FLOORS.each_pair do |floor_id, floor|
				# 一度初期化
				GS.room_troops[floor_id].clear
				GS.room_treasure_boxes[floor_id].clear
				
				spell_candidates = DB.spells.find_all{|x| x.book_rank and not x.id.in?(GS.got_spell_ids)} 
				
				floor.sections.each_with_index do |sect, id|
					# 部屋にしか出ない
					next unless sect.area == Dungeon::Area::ROOM
				
					# 敵の配置されているリージョンにしか出ない
					region = sect.region_id
					next unless region
			
					list = floor.pick_enemy_list(region)
					next unless list
					
					if rand(100) < sect.enemy_appearance_percentage then
						troop = RoomTroop.new
						
						# 部屋の広さによって敵の多さに修正
						if sect.largeness <= 2 then
							bonus = -1
						elsif sect.largeness >= 6 then
							bonus = +1
						else
							bonus = 0
						end
						troop.set_enemies(list, bonus)

						GS.room_troops[floor_id][id] = troop
						if sect.treasure_type then
							treasure_level = floor.region_treasure_levels[region]
							box = TreasureBox.new(treasure_level, sect.treasure_type)
							GS.room_treasure_boxes[floor_id][id] = box
							
							# アイテムの入っていない宝箱には、ランダムに呪文書がセットされる
							if not spell_candidates.empty? and box.item.nil? and rand(100) < 25 then
								candidates = spell_candidates.find_all{|x| x.book_rank <= treasure_level}
								unless candidates.empty? then
									spell_id = Util.random_pick(candidates).id
									box.item = Item.new("SpellBookOf#{spell_id}")
								end
							end

						end
					end
				end
				
			end
			LOGGER.log(SetRoomTroopLog)
		end
	
		
		def start
			raise 'DarkHall must be initialized before Game.start' unless inited?
			Phase.change(TitlePhase.new)
			Game.operation_loop("FirstLoop")
			raise StandardError, "first loop stopped"
		end
		
		def quit
			exit
		end
		
		def revive_start(member_id, week = 1)
			GS.reviving_member_ids << member_id
			if member_id.in?(GS.party.member_ids) then
				GS.party.leave_member(member_id)
			else
				GS.bar_member_ids.delete(member_id)
			end
			GS.members[member_id].remaining_week_before_revived = week
			

		end

	end
	
	include Game
end

