shaman = {}

shaman.params = 
{
	--[[
	0 - сетка,
	1- сломанная горизонтальная преграда,
	2- сломанная вертикальная преграда,
	3- горизонтальная преграда,
	4- вертикальная преграда,
	5- зеленая лунка,
	6- желтая лунка,
	7- голубая лунка,
	8- красная лунка.
	]]
	grid = 
	{
		"      0   0      ",
		"       0 0       ",
		"    0   1   0    ",
		"     0 0 0 0     ",
		"  0   3   2   0  ",
		"   0 0 0 0 0 0   ",
		"    4   3   4    ",
		"   0 0 0 0 0 0   ",
		"  2   3   3   6  ",
		"   0 0 0 0 0 0   ",
		"    5   4   3    ",
		"   0 0 0 0 0 0   ",
		"  2   3   3   2  ",
		"   0 0 0 0 0 0   ",
		"0   4   7   4   0",
		" 0 0 0 0 0 0 0 0 ",
		"  4   4   4   4  ",
		" 0 0 0 0 0 0 0 0 ",
		"0   1   0   1   0",
	},
	
	barrier_images = 
	{
		"scenes_scene04_01_minigame_minigame03_barrier_hor_02",
		"scenes_scene04_01_minigame_minigame03_barrier_ver_02",
		"scenes_scene04_01_minigame_minigame03_barrier_hor_01",
		"scenes_scene04_01_minigame_minigame03_barrier_ver_01"
	},
	
	barrier_selection = 
	{
		clips = 
		{
			"partition01",
			"partition02"
		},
		fade_time = 500
	},
	
	balls = 
	{
		{ -- зеленый
			image = "scenes_scene04_01_minigame_minigame03_ball_green",
			effect_grid_out = "destroy_ball_green",
			effect_ball_collision = "minigame_ball_destroy_green",
			effect_hole_in = "destroy_ball_green"
		},
		{ -- желтый
			image = "scenes_scene04_01_minigame_minigame03_ball_yellow",
			effect_grid_out = "destroy_ball_yellow",
			effect_ball_collision = "minigame_ball_destroy_yellow",
			effect_hole_in = "destroy_ball_yellow"
		},
		{ -- голубой
			image = "scenes_scene04_01_minigame_minigame03_ball_blue",
			effect_grid_out = "destroy_ball_blue",
			effect_ball_collision = "minigame_ball_destroy_blue",
			effect_hole_in = "destroy_ball_blue"
		},
		{ -- красный
			image = "scenes_scene04_01_minigame_minigame03_ball_red",
			effect_grid_out = "destroy_ball_red",
			effect_ball_collision = "minigame_ball_destroy_red",
			effect_hole_in = "destroy_ball_red"
		}
	},
	
	grid_rect = sf.misc.FloatRect(244, 77, 467, 525),
	ball_cell_time = 1500,
	
	pipeline = 
	{
		size = 5,
		move_time = 500,
		balls_start_periods = {3000, 2500, 2000, 1500},
		change_periods_period = 10000
	},
	
	barriers_layer = 2,
	holes_layer = 2,
	balls_layer = 3,
	
	-- настройки лунки, в которую попал шарик
	hole_effect = 
	{
		clip = "hole01",
		fade_time = 500
	},
	
	ball_size = sf.misc.FloatVector(16,16),
	
	sounds = 
	{
		rotate = "mini_game_mini_game_shaman_rotation_01",
		collision1 = "mini_game_mini_game_shaman_collision_01", --Звук удара голубого, желтого и зеленого шариков о перегородку 
		collision2 = "mini_game_mini_game_shaman_collision_02", --Звук удара красного шарика о перегородку 
		correct_hole = "mini_game_mini_game_shaman_true_01" -- попал в верную лунку
	}
}

function __visible(_widget, _visible)
	local flags = sf.misc.BitwiseOr(sf.gui.CBaseWidget.FlagHidden, sf.gui.CBaseWidget.FlagDisabled)
	if _visible then
		_widget.RemFlags(flags)
	else
		_widget.AddFlags(flags)
	end
end

shaman.CBarrier = function(_owner, _cell)
	local widgets = {}
	local selections = {}
	
	local center = sf.misc.FloatVector(_cell.pos)
	
	if _cell.value > 2 then
		table.insert(widgets, sf.gui.CImageWidget(shaman.params.barrier_images[3], "", shaman.params.barriers_layer, 0))
		table.insert(widgets, sf.gui.CImageWidget(shaman.params.barrier_images[4], "", shaman.params.barriers_layer, 0))
		
		local selection1 = sf.gui.CClipWidget("", shaman.params.barriers_layer, 0)
		selection1.GetClip().Load(shaman.params.barrier_selection.clips[1])
		local s1 = selection1.GetClip().GetViewSize()
		selection1.SetOffset(center.X - s1.X / 2, center.Y - s1.Y / 2)
		shaman._owner.AddWidget(selection1)
		
		local selection2 = sf.gui.CClipWidget("", shaman.params.barriers_layer, 0)
		selection2.GetClip().Load(shaman.params.barrier_selection.clips[2])
		local s2 = selection2.GetClip().GetViewSize()
		selection2.SetOffset(center.X - s2.X / 2, center.Y - s2.Y / 2)
		shaman._owner.AddWidget(selection2)
		
		table.insert(selections, selection1)
		table.insert(selections, selection2)
	else
		table.insert(widgets, sf.gui.CImageWidget(shaman.params.barrier_images[_cell.value], "", shaman.params.barriers_layer, 0))
	end
	
	for _, w in pairs(widgets) do	
		local s = w.GetSize()
		w.SetOffset(center.X - s.X / 2, center.Y - s.Y / 2)
		_owner.AddWidget(w)
	end
	
	local barrier = 
	{
		_value = _cell.value,
		_widgets = widgets,
		_selections = selections,
		_selection_alpha = 0,
		_selection_direct = -1,
		_last_update = 0,
		
		Update = function(_self)
			if _self._value > 2 then
				__visible(_self._widgets[1], _self:IsHor())
				__visible(_self._widgets[2], _self:IsVer())
				__visible(_self._selections[1], _self:IsHor())
				__visible(_self._selections[2], _self:IsVer())
				
				local t = shaman._timer.Get().GetTime()
				local dt = t - _self._last_update
				local fade_time = shaman.params.barrier_selection.fade_time
				_self._last_update = t
				
				_self._selection_alpha = _self._selection_alpha + _self._selection_direct *  255 * dt / fade_time
				if _self._selection_alpha > 255 then _self._selection_alpha = 255 end
				if _self._selection_alpha < 0 then _self._selection_alpha = 0 end
			
				local color = sf.graphics.Color(_self._selections[2 - _self._value % 2].GetColor())
				color.Alpha = _self._selection_alpha
				_self._selections[2 - _self._value % 2].SetColor(color)
			end
		end,
		
		MouseDown = function(_self, _pos)
			if _self._value > 2 then
				if _self:GetPoly().IsContains(_pos.X, _pos.Y) then
					_self._value = 7 - _self._value
					
					if _self._value == 3 or _self._value == 4 then
					-- перегородка
						if shaman.params.sounds.rotate then
							sound(shaman.params.sounds.rotate)
						end
					end
					return true
				end
			end
			return false
		end,
		
		MouseMove = function(_self, _pos)
			if _self._value > 2 then
				if _self:GetPoly().IsContains(_pos.X, _pos.Y) then
					_self._selection_direct = 1
					return true
				else
					_self._selection_direct = -1
				end
			end
			return false
		end,
		
		IsHor = function(_self)
			return _self._value % 2 == 1
		end,
		
		IsVer = function(_self)
			return _self._value % 2 == 0
		end,
		
		GetPoly = function(_self)
			if _self._value > 2 then
				return _self._widgets[2 - _self._value % 2].GetPoly()
			end
			return _self._widgets[1].GetPoly()
		end
	}
	
	_cell.barrier = barrier
	barrier:Update()
	
	return barrier
end

shaman.CBall = function(_owner,_color)
	local ball_params = shaman.params.balls[_color]
	assert(ball_params)
	
	local widget = sf.gui.CImageWidget(ball_params.image, _T(""), shaman.params.balls_layer, 0)
	_owner.AddWidget(widget)
	
	return 
	{
		_color = _color,
		_widget = widget,
		_sound_in_hole_played = nil,
		
		Move = function(_self, _i, _j, _di, _dj)
			
			assert(math.abs(_di)==2)
			assert(math.abs(_dj)==2)
			assert(shaman._grid[_i] and shaman._grid[_i][_j])
			assert(shaman._grid[_i + _di] and shaman._grid[_i + _di][_j + _dj])
			
			_self._cell1 = shaman._grid[_i][_j]
			_self._cell2 = shaman._grid[_i + _di][_j + _dj]
			_self._start_pos  = _self._pos or sf.misc.FloatVector(_self._cell1.pos)
			_self._start_time = shaman._timer.Get().GetTime()
			_self._is_in_cell2 = nil
			
			_self:Update()
		end,
		
		Update = function(_self)
			
			if not _self._cell1 or _self._is_dead then
				return
			end
			
			assert(_self._cell2)
			assert(_self._start_pos)
			assert(_self._start_time)
			
			local t = shaman._timer.Get().GetTime()
			local dt = t - _self._start_time
			local k = dt / shaman.params.ball_cell_time			
			local is_in_cell2 = (k > 1)
			
			if is_in_cell2 then k = 1 end
			
			-- перемещаем шарик
			local s = _self._widget.GetSize()
			_self._pos =_self._start_pos + (_self._cell2.pos - _self._start_pos) * k
			_self._widget.SetOffset(_self._pos.X - s.X / 2, _self._pos.Y - s.Y / 2)
			
			local i = _self._cell2.i
			local j = _self._cell2.j			
			local di = _self._cell2.i - _self._cell1.i
			local dj = _self._cell2.j - _self._cell1.j
			
			-- если в целевой клетке имеется барьер и шарик с ним соприкоснулся, то меняем направление шарика
			if _self._cell2.barrier then
				local poly1 = _self:GetPoly()
				local poly2 = _self._cell2.barrier:GetPoly()
				if poly1.IsIntersects(poly2) then
					if _self._cell2.barrier:IsHor() then
						_self:Move(i, j, -di, dj)
					else
						_self:Move(i, j, di, -dj)
					end
					if _self._color == 4 then
						if shaman.params.sounds.collision2 then
							sound(shaman.params.sounds.collision2)
						end
					else
						if shaman.params.sounds.collision1 then
							sound(shaman.params.sounds.collision1)
						end
					end
					return
				end
			end
			
			-- если шарик соприкоснулся с каким-то другим шариком, то уничжтожаем оба шарика и запускаем для них эффекты effect_ball_collision
			for _, ball in pairs(shaman._balls) do
				if ball ~= _self then
					local poly1 = _self:GetPoly()
					local poly2 = ball:GetPoly()
					if poly1.IsIntersects(poly2) then
						_self._is_dead = true
						_self._widget.AddFlags(_self._widget.FlagDead)
						shaman_effects.CreateEffectBallCollision(_self._color, _self._pos)
						
						ball._is_dead = true
						ball._widget.AddFlags(ball._widget.FlagDead)
						shaman_effects.CreateEffectBallCollision(ball._color, ball._pos)
						return
					end
				end
			end
			
			-- если шарик достиг целевой клетки, и в ней расположена лунка, уничтожаем его и запускаем эффект в зависимости от того, попал шарик в свою лунку или нет.
			
			if is_in_cell2 and _self._cell2.hole then
				if _self._color == _self._cell2.hole._color then
					_self._is_in_hole = true
					--shaman_effects.CreateEffectHoleInOk(_self._color, _self._pos)
					if shaman.params.sounds.correct_hole and not _self._sound_in_hole_played then
						sound(shaman.params.sounds.correct_hole)
						_self._sound_in_hole_played = true
					end
				else	
					_self._is_dead = true
					_self._widget.AddFlags(_self._widget.FlagDead)
					shaman_effects.CreateEffectHoleIn(_self._color, _self._pos)
				end
				return
			end
			
			-- если шарик достиг целевой клетки, проверяем, можно ли продолжать движение. если нет, то уничтожаем его и запускаем эффект effect_grid_out
			if is_in_cell2 then
				if	shaman._grid[i + di/2] and shaman._grid[i + di/2][j + dj/2] and shaman._grid[i + di/2][j + dj/2].value then
					_self:Move(i, j, di, dj)
					return
				end
				_self._is_dead = true
				_self._widget.AddFlags(_self._widget.FlagDead)
				shaman_effects.CreateEffectGridOut(_self._color, _self._pos)
			end
		end,
				
		IsDead = function(_self)
			return _self._is_dead ~= nil
		end,
		
		IsInHole = function(_self, _color)
			return (_self._is_in_hole ~= nil and (not _color or _color == _self._color))
		end,
		
		GetPoly = function(_self)
			local offset = _self._widget.GetOffset()
			offset = offset + _self._widget.GetSize() / 2
			offset = offset - shaman.params.ball_size / 2
			return sf.misc.Poly4FromRect(sf.misc.FloatRect(offset.X, offset.Y, shaman.params.ball_size.X, shaman.params.ball_size.Y))
		end
	}
end

shaman.CHole = function(_owner, _color, _pos)
	local image = sf.gui.CImageWidget("scenes_scene04_01_minigame_minigame03_opening01", "", shaman.params.holes_layer, 0)
	local s1 = image.GetSize()
	image.SetOffset(_pos.X - s1.X / 2, _pos.Y - s1.Y / 2)
	_owner.AddWidget(image)
	
	if not shaman._holes_effects then
		shaman._holes_effects = visual_effects.CreateEffectsList()
	end
	
	local clip = sf.gui.CClipWidget("", shaman.params.holes_layer+1, 0)
	clip.GetClip().Load(shaman.params.hole_effect.clip)
	local s2 = clip.GetClip().GetViewSize()
	clip.SetOffset(_pos.X - s2.X / 2, _pos.Y - s2.Y / 2)
	_owner.AddWidget(clip)
	
	local hole = 
	{
		_color = _color, 
		_widget = image, 
		_clip = clip, 
		_alpha = 0,
		_alpha_direct = -1,
		_last_update = 0,
		
		Update = function(_self)
			_self._alpha_direct = -1
			for _, ball in pairs(shaman._balls) do
				if ball:IsInHole(_self._color) then
					_self._alpha_direct = 1
					break
				end
			end
			
			local t = shaman._timer.Get().GetTime()
			local dt = t - _self._last_update
			local fade_time = shaman.params.hole_effect.fade_time
			_self._last_update = t
			_self._alpha = _self._alpha + _self._alpha_direct * 255 * dt / fade_time
			if _self._alpha > 255 then _self._alpha = 255 end
			if _self._alpha < 0 then _self._alpha = 0 end
			
			local color = sf.graphics.Color(_self._clip.GetColor())
			color.Alpha = _self._alpha
			_self._clip.SetColor(color)
		end
	}
	
	hole:Update()
	return hole
end

shaman.CPipeline = function(_owner, _start_cell, _direct)
	local pipeline_widget = sf.gui.CWidget(_T(""), 0, 0)
	
	local size = shaman.params.pipeline.size
	local colors = {}
	local widgets = {}

	for i=1, size do
		local color = math.random(4)
		table.insert(colors, color)
		local widget = sf.gui.CImageWidget(shaman.params.balls[color].image, _T(""), shaman.params.balls_layer, 0)
		table.insert(widgets, widget)
		local s = widget.GetSize()
		widget.SetOffset(_start_cell.pos.X - s.X / 2, 
			_start_cell.pos.Y - shaman.params.ball_size.Y * (i-1) - s.Y / 2)
		pipeline_widget.AddWidget(widget)
	end
	
	_owner.AddWidget(pipeline_widget)
	
	return
	{
		_owner = _owner,
		_pipeline_widget = pipeline_widget,
		_start_cell = _start_cell,
		_direct = _direct,
		_colors = colors,
		_widgets = widgets,
		_current_y = 0,
		_target_y = 0,
		_last_update = 0,
		
		Update = function(_self, _time)
			local dt = _time - _self._last_update
			if _self._current_y < _self._target_y then
				local dy = (shaman.params.ball_size.Y * dt / shaman.params.pipeline.move_time)
				_self._current_y = _self._current_y + dy
				if _self._current_y > _self._target_y then
					_self._current_y = _self._target_y
				end
				_self._pipeline_widget.SetOffset(0, _self._current_y)
			end
			_self._last_update = _time
		end,
		
		Pop = function(_self)
			
			local ball = shaman.CBall(_self._owner, _self._colors[1])
			ball:Move(_self._start_cell.i, _self._start_cell.j, 2, _self._direct * 2)
			
			_self._widgets[1].AddFlags(sf.gui.CBaseWidget.FlagDead)
			table.remove(_self._colors, 1)
			table.remove(_self._widgets, 1)
			local offset = sf.misc.FloatVector(_self._widgets[#_self._widgets].GetOffset())
			offset = offset + _self._widgets[#_self._widgets].GetSize() / 2
			offset.Y = offset.Y - shaman.params.ball_size.Y
			local color = math.random(4)
			table.insert(_self._colors, color)
			local widget = sf.gui.CImageWidget(shaman.params.balls[color].image, _T(""), shaman.params.balls_layer, 0)
			table.insert(_self._widgets, widget)			
			offset = offset - widget.GetSize() / 2
			widget.SetOffset(offset.X, offset.Y)
			_self._pipeline_widget.AddWidget(widget)
			
			_self._target_y = _self._target_y + shaman.params.ball_size.Y
			
			return ball
		end
	}
end

shaman.Init = function(_owner)
	sminigames.SetStandartScriptName(_owner, shaman)
	
	shaman_effects.effects_list = visual_effects.CreateEffectsList()
	
	shaman._owner = _owner
	
	shaman._timer = __create_timer("", "qe_level")
	
	shaman._grid = {}
	shaman._barriers = {}
	shaman._balls = {}
	shaman._holes = {}
	
	local row_size = string.len(shaman.params.grid[1])
	local col_size = #shaman.params.grid
	
	shaman._cell_size = sf.misc.FloatVector(
		shaman.params.grid_rect.Width / (row_size-1), shaman.params.grid_rect.Height / (col_size-1))
	
	for i, row in ipairs(shaman.params.grid) do
		shaman._grid[i] = {}
		assert(row_size == string.len(row))
		for j=1, row_size do
			local c = string.sub(row, j, j)
			local value
			if c ~= " " then 
				value = tonumber(c)
			end
			
			local pos = sf.misc.FloatVector(shaman.params.grid_rect.X, shaman.params.grid_rect.Y)
			pos.X = pos.X + shaman._cell_size.X * (j-1)
			pos.Y = pos.Y + shaman._cell_size.Y * (i-1)
			
			shaman._grid[i][j] = {}
			shaman._grid[i][j].i = i
			shaman._grid[i][j].j = j
			shaman._grid[i][j].value = value
			shaman._grid[i][j].pos = pos
			
			if value and value > 0 then
				if value < 5 then
					table.insert(shaman._barriers, shaman.CBarrier(_owner, shaman._grid[i][j]))
				else
					local color = value-4
					assert(color < 5)
					local hole = shaman.CHole(_owner, color, pos)
					shaman._grid[i][j].hole = hole
					table.insert(shaman._holes, hole)
				end
			end
		end
	end
	
	shaman._current_ball_start_period = 1
	shaman._last_ball_start = 0
	shaman._last_change_start_ball_period = 0
	
	shaman._pipelines = 
	{
		shaman.CPipeline(_owner, shaman._grid[3][5], 1),
		shaman.CPipeline(_owner, shaman._grid[3][13], -1)
	}
	
	shaman._clips = {}
end

function shaman.CheckEnd(_owner)
	local flag = false
	if flag then
		_owner.SetGameResult(1)
		_owner.OnEndGame()
	end
end

function shaman.OnMouseDown(_owner, _pos, _button, _state, _broadcast)
	local ret = false
	if  _button == sf.gui.CBaseWidget.LeftButton then	 
		for _, b in pairs(shaman._barriers) do
			ret = ret or b:MouseDown(_pos)
		end
	end
	return ret
end 

function shaman.OnMouseMove(_owner, _pos, _state, _broadcast)
	local ret = false
	for _, b in pairs(shaman._barriers) do
		ret = ret or b:MouseMove(_pos)
	end
	return ret
end 

function shaman.OnUpdate(_owner)
	for _, barrier in pairs(shaman._barriers) do
		barrier:Update()
	end
	
	for _, ball in pairs(shaman._balls) do
		ball:Update()
	end
	
	for _, hole in pairs(shaman._holes) do
		hole:Update()
	end
	
	local i=1
	local c = #shaman._balls
	while i <= c do
		if shaman._balls[i]:IsDead() then
			table.remove(shaman._balls, i)
			c = c-1
		else
			i = i+1
		end
	end
	
	i=1
	c = #shaman._clips
	while i <= c do
		local clip = shaman._clips[i]
		local ball = clip.GetClip().FindObject("ball")
		assert(ball, "В клипе исчезновения шарика объект с изображением шарика должно иметь id=\"ball\"")
		if math.abs(ball.GetTime()-ball.GetPeriodTime()) <= 0.001 then
			clip.SetFlags(clip.FlagDead)
			table.remove(shaman._clips, i)
			c = c-1
		else
			i = i+1
		end
	end
		
	local balls_in_hole = 0
	for _, ball in pairs(shaman._balls) do
		if ball:IsInHole() then
			balls_in_hole = balls_in_hole + 1
		end
	end
	
	if balls_in_hole == 3 then
		_owner.SetGameResult(1)
		_owner.OnEndGame()
		return
	end
	
	local t = shaman._timer.Get().GetTime()
	
	if t - shaman._last_change_start_ball_period >= shaman.params.pipeline.change_periods_period then
		if shaman._current_ball_start_period < #shaman.params.pipeline.balls_start_periods then
			shaman._current_ball_start_period = shaman._current_ball_start_period + 1
			shaman._last_change_start_ball_period = t
		end
	end
	
	local balls_start_period = shaman.params.pipeline.balls_start_periods[shaman._current_ball_start_period]
	
	if t - shaman._last_ball_start >= balls_start_period then
		local pipeline = math.random(1,2)
		local ball = shaman._pipelines[pipeline]:Pop()
		table.insert(shaman._balls, ball)
		shaman._last_ball_start = t
	end
	
	shaman._pipelines[1]:Update(t)
	shaman._pipelines[2]:Update(t)
end

shaman_effects = shaman_effects or {}

__inherite(shaman_effects, null_lua_widget_handler()) --default_lua_widget_handler

function shaman_effects.__CreateInstance()
    return "shaman_effects"
end

function shaman_effects.DoUpdate(_this)
	if shaman_effects.effects_list then
		shaman_effects.effects_list:Update()
	end
    return true
end

function shaman_effects.DoDraw(_this, _renderer)
	if shaman_effects.effects_list then
		shaman_effects.effects_list:PreDraw(_renderer)
		shaman_effects.effects_list:PostDraw(_renderer)
	end
    return true
end

function shaman_effects.CreateEffectGridOut(_color, _pos)
	local clip = sf.gui.CClipWidget("", shaman.params.balls_layer, 0)
	clip.GetClip().Load(shaman.params.balls[_color].effect_grid_out)
	local s = clip.GetClip().GetViewSize()
	clip.SetOffset(_pos.X - s.X / 2, _pos.Y - s.Y / 2)
	shaman._owner.AddWidget(clip)
	table.insert(shaman._clips, clip)
end

function shaman_effects.CreateEffectBallCollision(_color, _pos)
	shaman_effects.effects_list:CreateInstantEffect(shaman.params.balls[_color].effect_ball_collision, _pos)
end

function shaman_effects.CreateEffectHoleIn(_color, _pos)
	local clip = sf.gui.CClipWidget("", shaman.params.balls_layer, 0)
	clip.GetClip().Load(shaman.params.balls[_color].effect_hole_in)
	local s = clip.GetClip().GetViewSize()
	clip.SetOffset(_pos.X - s.X / 2, _pos.Y - s.Y / 2)
	shaman._owner.AddWidget(clip)
	table.insert(shaman._clips, clip)
end

function shaman_effects.CreateEffectHoleInOk(_color, _pos)
	shaman_effects.effects_list:CreateInstantEffect(shaman.params.balls[_color].effect_hole_in_ok, _pos)
end
