/*

	Ripatti Artem (c) 2012

	Уровень игры

*/

#include "indirect_iface.h"

#define FIELD_X		16
#define FIELD_Y		44
#define BUTT_X		200
#define BUTT_Y		20
#define HINT_X		255
#define HINT_Y		165

namespace indirect
{
	int dx[] = { 1, 0, -1, 0 };
	int dy[] = { 0, 1, 0, -1 };

	XUnit * ZLevel::unit_by_pos( int x, int y )
	{
		for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
		{
			int xx = it->second.first, yy = it->second.second;
			if (xx <= x && x < xx+3 && yy <= y && y < yy+2)
				return it->first;
		}
		return 0;
	}

	bool ZLevel::can_place_unit( int x, int y )
	{
		if ( x<0 || x>8 || y<0 || y>9) return false;
		for (int a=x; a<x+3; a++)
			for (int b=y; b<y+2; b++)
				if (_field[a][b]!=0)
					return false;
		for (int a=x; a<x+3; a++)
			for (int b=y; b<y+2; b++)
				if (unit_by_pos(a,b))
					return false;
		return true;
	}

	ZLevel::ZLevel(ZIndirectGame * parent)
	{
		_parent = parent;

		for (int a=0; a<11; a++)
			for (int b=0; b<11; b++)
				_field[a][b] = 0;

		for (int a=0; a<11; a++)
		{
			_in[a] = "";
			_out[a] = "";
			_done[a] = 0;
		}

		_num = 0;
		_diff = 0;

		_run_mode = false;
		_crash_mode = false;
		_level_clear = false;
		_drag_unit_mode = false;
		_solved = false;

		_drag_unit = 0;

		_proto.push_back( new ZTriggerUnit() );
		_count.push_back( -1 );

		_proto.push_back( new ZSwapperUnit() );
		_count.push_back( -1 );

		_proto.push_back( new ZAccumulatorUnit() );
		_count.push_back( -1 );

		_proto.push_back( new ZTriggerUnit() );
		_count.push_back( -1 );

		_proto.push_back( new ZTriggerUnit() );
		_count.push_back( -1 );

		_proto.push_back( new ZTriggerUnit() );
		_count.push_back( -1 );

		_kadr = 0;
	}

	void ZLevel::set_input( int x, string str )
	{
		_in[x] = str;
	}

	void ZLevel::set_output( int x, string str )
	{
		_out[x] = str;
	}

	void ZLevel::set_hint( string hint )
	{
		_hint = hint;
	}

	void ZLevel::set_count( int c1, int c2, int c3, int c4, int c5, int c6 )
	{
		_count[0] = c1;
		_count[1] = c2;
		_count[2] = c3;
		_count[3] = c4;
		_count[4] = c5;
		_count[5] = c5;
	}

	void ZLevel::set_info( int num, int diff )
	{
		_num = num;
		_diff = diff;
	}

	int ZLevel::get_diff()
	{
		return _diff;
	}

	bool ZLevel::is_solved()
	{
		return _solved;
	}

	void ZLevel::run()
	{
		if (_run_mode) return;

		_vec.clear();
		for (int a=0; a<11; a++)
			for (int b=0; b<(int)_in[a].size(); b++)
				_vec.push_back( ZBlock( a, -b-1, _in[a][b]-'A'+1 ) );

		_kadr = 0;

		_run_mode = true;
	}

	void ZLevel::clear()
	{
		if (_run_mode) return;

		for (int a=0; a<11; a++)
			for (int b=0; b<11; b++)
				_field[a][b] = 0;

		for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
		{
			for (int a=0; a<(int)_proto.size(); a++)
				if (_count[a]>=0)
					if (_proto[a]->get_type() == it->first->get_type())
					{
						_count[a]++;
						break;
					}
			delete it->first;
		}

		_units.clear();
	}

	void ZLevel::stop()
	{
		if (!_run_mode) return;

		for (int a=0; a<11; a++)
			_done[a] = 0;
		_vec.clear();

		_crash_mode = false;
		_level_clear = false;

		for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
			it->first->reset();

		_run_mode = false;
	}

	void ZLevel::do_input( XInputEngine * IE )
	{
		int x = IE->get_mouse_x()/2, y = IE->get_mouse_y()/2;

		if (!_run_mode)
		{
			for (int a=0; a<(int)_proto.size(); a++)
				if (_count[a]>0)
				{
					int xx = BUTT_X + (a%2)*56;
					int yy = FIELD_Y + 12 + (a/2)*40;
					if (xx<=x && x<xx+48 && yy<=y && y<yy+24)
						if (IE->mouse_pressed( 0, false, true ))
						{
							_drag_unit_mode = true;
							_drag_unit = a;
						}
				}

			if (_drag_unit_mode)
				if (IE->mouse_pressed( 0, true, false ))
				{
					if (FIELD_X <= x && x < FIELD_X + 16*11)
						if (FIELD_Y <= y && y < FIELD_Y + 12*11)
						{
							int fx = (x-FIELD_X)/16, fy = (y-FIELD_Y)/12;

							if (can_place_unit( fx, fy ))
							{
								_count[_drag_unit]--;
								XUnit * unit = _proto[_drag_unit]->get_copy();
								_units[unit] = make_pair( fx, fy );
							}
						}

					_drag_unit_mode = false;
					_drag_unit = 0;
					return;
				}

			if (FIELD_X <= x && x < FIELD_X + 16*11)
				if (FIELD_Y <= y && y < FIELD_Y + 12*11)
				{
					int fx = (x-FIELD_X)/16, fy = (y-FIELD_Y)/12;

					if (IE->mouse_pressed( 0, false, true ))
					{
						XUnit * unit = unit_by_pos( fx, fy );
						if (!unit)
						{
							_field[fx][fy]++;
							if (_field[fx][fy]==5) _field[fx][fy]=1;
						}
						else unit->click();
					}
					else if (IE->mouse_pressed( 1, false, true ))
					{
						_field[fx][fy] = 0;
						XUnit * unit = unit_by_pos( fx, fy );
						if (unit)
						{
							for (int a=0; a<(int)_proto.size(); a++)
								if (_count[a]!=-1)
									if (_proto[a]->get_type() == unit->get_type())
									{
										_count[a]++;
										break;
									}
							_units.erase(unit);
							delete unit;
						}
					}
				}
		}

		if (BUTT_Y <= y && y < BUTT_Y+20)
			for (int a=0; a<4; a++)
				if (BUTT_X + a*20 <= x && x < BUTT_X + (a+1)*20)
					if (IE->mouse_pressed( 0, false, true ))
						if (a==0)
							run();
						else if (a==1)
							stop();
						else if (a==2)
							clear();
						else
						{
							stop();
							_parent->leave_level();
						}
	}

	void ZLevel::do_frame()
	{
		if (_run_mode)
		{
			if (_crash_mode) return;
			_kadr++;

			for (int a=0; a<(int)_vec.size(); a++)
				for (int b=a+1; b<(int)_vec.size(); b++)
				{
					int tmp_a = 1;
					if (0 <= _vec[a].x && _vec[a].x < 11 && 0 <= _vec[a].y && _vec[a].y < 11)
						if (_field[_vec[a].x][_vec[a].y]>0)
							tmp_a = _field[_vec[a].x][_vec[a].y]-1;

					int tmp_b = 1;
					if (0 <= _vec[b].x && _vec[b].x < 11 && 0 <= _vec[b].y && _vec[b].y < 11)
						if (_field[_vec[b].x][_vec[b].y]>0)
							tmp_b = _field[_vec[b].x][_vec[b].y]-1;

					int xx1 = _vec[a].x*16 + dx[tmp_a]*( (_kadr/4) % 16 );
					int yy1 = _vec[a].y*12 + dy[tmp_a]*( ((_kadr%(16*4)) * 3) / 16 );

					int xx2 = _vec[b].x*16 + dx[tmp_b]*( (_kadr/4) % 16 );
					int yy2 = _vec[b].y*12 + dy[tmp_b]*( ((_kadr%(16*4)) * 3) / 16 );

					int ddx = xx1 - xx2, ddy = yy1 - yy2;
					if (ddx*ddx + ddy*ddy < 50)
					{
						_crash_mode = true;
						_crash_res = "столкновение блоков";
						return;
					}
				}
			
			if (_kadr % (16*4) == 0)
			{
				int sz = (int)_vec.size();
				for (int a=0; a<sz; a++)
				{
					int tmp = 1;
					if (_vec[a].y >= 0 && _field[_vec[a].x][_vec[a].y]>0)
						tmp = _field[_vec[a].x][_vec[a].y]-1;
					_vec[a].x += dx[tmp];
					_vec[a].y += dy[tmp];

					if (tmp==3 && _vec[a].y<0)
					{
						_crash_mode = true;
						_crash_res = "выход блока за границу";
					}
					else if (_vec[a].y == 11)
					{
						int done = _done[_vec[a].x];
						int sz = (int)_out[_vec[a].x].size();
						if (done == sz)
						{
							_crash_mode = true;
							_crash_res = "выход блока за границу";
						}
						else
						{
							int color = _out[_vec[a].x][_done[_vec[a].x]]-'A'+1;
							if ((done < sz && color == _vec[a].color) || done == sz)
							{
								_vec[a].color = 0;
								_done[_vec[a].x]++;
							}
							else
							{
								_crash_mode = true;
								_crash_res = "неверный цвет блока";
							}
						}
					}
					else if (_vec[a].x < 0 || _vec[a].x >= 11)
					{
						_crash_mode = true;
						_crash_res = "выход блока за границу";
					}
					else if (_vec[a].y >= 0)
					{
						XUnit * unit = unit_by_pos( _vec[a].x, _vec[a].y );
						if (unit)
						{
							if (tmp!=1)
							{
								_crash_mode = true;
								_crash_res = "столкновение блока\nс управляющим блоком";
							}
						}
						else if (_field[_vec[a].x][_vec[a].y] == 0)
						{
							_crash_mode = true;
							_crash_res = "блок упал с конвеера";
						}
					}
				}

				for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
					for (int a=0; a<3; a++)
					{
						int blo = it->first->output_block(a);
						if (blo)
							_vec.push_back( ZBlock( it->second.first+a, it->second.second+1, blo ) );
					}

				if (!_crash_mode)
					for (int a=0; a<sz; a++)
					{
						XUnit * unit = unit_by_pos( _vec[a].x, _vec[a].y );
						if (unit)
						{
							int ux = _units[unit].first, uy = _units[unit].second;
							if (uy == _vec[a].y)
								if (!unit->input_block( _vec[a].x - ux, _vec[a].color ))
								{
									_crash_mode = true;
									_crash_res = "столкновение блока\nс управляющим блоком";
								}
								else _vec[a].color = 0;
						}
					}

				vector< ZBlock > v2;
				for (int a=0; a<(int)_vec.size(); a++)
					if (_vec[a].color)
						v2.push_back(_vec[a]);
				_vec = v2;

				bool flag = true;
				for (int a=0; a<11; a++)
					if (_done[a]!=(int)_out[a].size())
						flag = false;
				if (flag)
				{
					_level_clear = true;
					_solved = true;
				}
			}
		}
	}

	void ZLevel::draw( XGraphicEngine * GE, XInputEngine * IE )
	{
		GE->set_texture( indirect::TEXTURE_FIELD );

		for (int a=0; a<11; a++)
			if (_in[a]!="")
			{
				for (int b=-3; b<0; b++)
					GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y - 4.f + b*12.f, 16.f, 16.f ).
						set_tex( 0.25f*( (_kadr/4) % 4 ), 0.5f, 0.25f, 0.25f ) );

				if (!_run_mode)
					for (int b=0; b<(int)_in[a].size(); b++)
						GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y - (b+1)*12.f - 8.f, 16.f, 16.f ).
							set_tex( 0.25f*(_in[a][b]-'A'+1), 0.f, 0.25f, 0.25f ) );
			}

		for (int b=0; b<11; b++)
			for (int a=0; a<11; a++)
			{
				if (!unit_by_pos(a,b))
				{
					float tx = 0.f, ty = 0.f;

					if (_field[a][b]==1 || _field[a][b]==3) ty = 0.25f;
					if (_field[a][b]==2 || _field[a][b]==4) ty = 0.5f;
					if (_field[a][b]>0)
						if (_field[a][b]<=2)
							tx = 0.25f * ( (_kadr/4) % 4 );
						else tx = 0.25f * ( 3 - (_kadr/4) % 4 );

					GE->set_texture( indirect::TEXTURE_FIELD );
					GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y - 4.f + b*12.f, 16.f, 16.f ).
						set_tex( tx, ty, 0.25f, 0.25f ) );
				}

				if (_field[a][b]>0)
					GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y - 4.f + b*12.f, 16.f, 16.f ).
						set_tex( 0.25f*(_field[a][b]-1), 0.75f, 0.25f, 0.25f ).
						mul_to_color( 1.f, 1.f, 1.f, 0.75f ) );

				for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
					if ( it->second == make_pair( a, b ) )
						it->first->draw( GE, FIELD_X + a*16, FIELD_Y + b*12, 0, false );
			}

		if (_drag_unit_mode)
		{
			int x = IE->get_mouse_x()/2, y = IE->get_mouse_y()/2;
			if (FIELD_X <= x && x < FIELD_X + 16*9)
				if (FIELD_Y <= y && y < FIELD_Y + 12*10)
				{
					int fx = (x-FIELD_X)/16, fy = (y-FIELD_Y)/12;
					bool flag = can_place_unit( fx, fy );
					_proto[_drag_unit]->draw( GE, FIELD_X + fx*16, FIELD_Y + fy*12, flag ? 1 : 2, false );
					_proto[_drag_unit]->draw( GE, FIELD_X + fx*16, FIELD_Y + fy*12, flag ? 1 : 2, true );
					GE->set_texture( indirect::TEXTURE_FIELD );
				}
		}

		for (int a=0; a<11; a++)
			if (_out[a]!="")
			{
				GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y - 4.f + 11*12.f, 16.f, 16.f ).
					set_tex( 0.25f*( (_kadr/4) % 4 ), 0.5f, 0.25f, 0.25f ) );

				int sz = (int)_out[a].size();
				for (int b=_done[a]; b<sz; b++)
				{
					if (_run_mode && b==_done[a] && ((_kadr/8)&1)) continue;
					GE->draw_pict( ge::PICT( (float)FIELD_X + a*16.f, (float)FIELD_Y + 12*12.f + (sz-b)*6.f - 6.f, 16.f, 16.f ).
						set_tex( 0.25f*(_out[a][b]-'A'+1), 0.f, 0.25f, 0.25f ) );
				}
			}

		vector< ZBlock > v2;
		for (int a=0; a<(int)_vec.size(); a++)
		{
			int tmp = 1;
				if (0 <= _vec[a].x && _vec[a].x < 11 && 0 <= _vec[a].y && _vec[a].y < 11)
					if (_field[_vec[a].x][_vec[a].y]>0)
						tmp = _field[_vec[a].x][_vec[a].y]-1;

			int xx = _vec[a].x*16 + dx[tmp]*( (_kadr/4) % 16 );
			int yy = _vec[a].y*12 + dy[tmp]*( ((_kadr%(16*4)) * 3) / 16 ) - 8;

			v2.push_back( ZBlock( xx, yy, _vec[a].color ) );

			//GE->draw_pict( ge::PICT( (float)(FIELD_X + xx), (float)(FIELD_Y + yy), 16.f, 16.f ).
			//	set_tex( 0.25f*_vec[a].color, 0.f, 0.25f, 0.25f ) );
		}

		for (int a=0; a<(int)v2.size(); a++)
			for (int b=a+1; b<(int)v2.size(); b++)
				if (v2[a].y > v2[b].y)
					swap( v2[a], v2[b] );

		for (int a=0; a<(int)v2.size(); a++)
			GE->draw_pict( ge::PICT( (float)(FIELD_X + v2[a].x), (float)(FIELD_Y + v2[a].y), 16.f, 16.f ).
				set_tex( 0.25f*v2[a].color, 0.f, 0.25f, 0.25f ) );

		for (int b=0; b<11; b++)
			for (int a=0; a<11; a++)
				for (map< XUnit *, pair< int, int > >::iterator it = _units.begin(); it != _units.end(); it++)
					if ( it->second == make_pair( a, b ) )
						it->first->draw( GE, FIELD_X + a*16, FIELD_Y + b*12, 0, true );

		// кнопочки
		GE->set_texture( indirect::TEXTURE_BUTTON );
		for (int a=0; a<4; a++)
			GE->draw_pict( ge::PICT( (float)BUTT_X + 20.f*a, (float)BUTT_Y, 20.f, 20.f ).
				set_tex( 0.25f*a, 0.f, 0.25f, 0.25f ) );

		// юниты
		for (int a=0; a<(int)_proto.size(); a++)
			if (_count[a]!=-1)
			{
				int x = BUTT_X + (a%2)*56;
				int y = FIELD_Y + 12 + (a/2)*40;
				_proto[a]->draw( GE, x, y, _count[a]==0 ? 3 : 0, false );
				_proto[a]->draw( GE, x, y, _count[a]==0 ? 3 : 0, true );
				char str[10];
				sprintf_s( str, "x%d", _count[a] );
				GE->write( (float)(x+40), (float)(y+12), str, 1.f, 1.f, 1.f, 1.f );
			}

		// сообщения
		if (_crash_mode) GE->write( 110.f, 100.f, _crash_res.c_str(), 1.f, 1.f, 1.f );
		if (_level_clear) GE->write( 110.f, 210.f, "Уровень пройден!", 1.f, 1.f, 1.f );

		// подсказка
		string str = _hint;
		for (int a=0; a<(int)_proto.size(); a++)
			if (_count[a]>=0)
			{
				int x = IE->get_mouse_x()/2, y = IE->get_mouse_y()/2;
				int xx = BUTT_X + (a%2)*56;
				int yy = FIELD_Y + 12 + (a/2)*40;
				if (xx<=x && x<xx+48 && yy<=y && y<yy+24)
					str = _proto[a]->get_hint();
			}
		GE->write( HINT_X, HINT_Y, str.c_str(), 0.8f, 0.8f, 0.8f );

		string df[] = { "Easy", "Medium", "Hard" };
		char ch[20];
		sprintf_s( ch, "Уровень %d (%s)", _num, df[_diff-1].c_str() );
		GE->write( HINT_X, BUTT_Y - 16, ch, 1.f, 1.f, 1.f );
	}

}