// I realized late that this code needs to be thread-safe
// so, I panicked and put lock statements everywhere

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using PlayerIO.GameLibrary;
using System.Drawing;

namespace Penguin {
	[RoomType("Combat")]
	public class Combat : Game<Player> {
		public const float maxSpeed = 200.0f;
		public const float tile_ext = 20.0f;

		public int uniqueId = 0;
		public Random rand = new Random();
		public float time = 0.0f;
		public int currLevel;
		public float nextRespawnEverything = 0.0f;
		public int numStarsPickedUp = 0;

		public bool levelLoaded = false;

		// dumped from as3
		public int[] level1 = { 500, 540, 540, 540, 580, 540, 620, 540, 660, 540, 700, 540, 20, 580, 60, 580, 100, 580, 140, 580, 180, 580, 220, 580, 300, 580, 340, 580, 380, 580, 420, 580, 460, 580, 500, 660, 320, 540, 740, 540, 700, 500, 620, 500, 660, 500, 780, 540, 580, 540, 500, 580, 540, 580, 620, 580, 660, 580, 700, 580, 740, 580, 780, 580, 580, 580, 660, 620, 500, 620, 540, 620, 620, 620, 700, 620, 740, 620, 780, 620, 580, 620, 660, 660, 540, 660, 620, 660, 700, 660, 740, 660, 780, 660, 580, 660, 460, 580, 260, 580, 20, 620, 60, 620, 140, 620, 180, 620, 220, 620, 260, 620, 300, 620, 100, 620, 180, 660, 20, 660, 60, 660, 140, 660, 220, 660, 260, 660, 300, 660, 100, 660, 340, 620, 380, 620, 420, 620, 340, 660, 380, 660, 420, 660, 460, 620, 460, 660, 820, 500, 860, 500, 280, 540, 440, 540, 820, 540, 860, 540, 820, 580, 860, 580, 820, 620, 860, 620, 820, 660, 860, 660 };
		public int[] level1Enemies = { 380, 520, 1, 20, 545, 2, 760, 505, 2 };
		public int[] level1Stars = { 860, 460 };
		public int[] level2 = { 20, 580, 60, 580, 100, 580, 140, 580, 180, 580, 220, 580, 300, 580, 340, 580, 380, 580, 420, 580, 460, 580, 460, 580, 260, 580, 20, 620, 60, 620, 140, 620, 180, 620, 220, 620, 260, 620, 300, 620, 100, 620, 180, 660, 20, 660, 60, 660, 140, 660, 220, 660, 260, 660, 300, 660, 100, 660, 340, 620, 380, 620, 420, 620, 340, 660, 380, 660, 420, 660, 460, 620, 460, 660, 340, 540, 560, 580, 600, 580, 700, 540, 740, 540, 840, 500, 960, 500, 1100, 580, 1180, 580, 1100, 620, 1100, 660, 1180, 620, 1180, 660, 840, 540, 840, 580, 960, 540, 960, 580, 700, 580, 700, 620, 740, 580, 740, 620, 560, 620, 560, 660, 600, 620, 600, 660, 740, 660, 700, 660, 960, 620, 840, 620, 880, 540, 880, 580, 880, 620, 880, 500, 920, 540, 920, 580, 920, 620, 920, 500, 880, 660, 840, 660, 960, 660, 920, 660, 200, 540, 460, 540, 1060, 600, 1060, 640, 1060, 560, 1220, 600, 1220, 640, 1220, 560, 1140, 620, 1140, 660, 1140, 580 };
		public int[] level2Enemies = { 280, 520, 1, 20, 545, 2, 900, 460, 2, 400, 520, 1, 1120, 520, 1 };
		public int[] level2Stars = { 1180, 540 };
		public int[] level3 = { 20, 380, 100, 580, 140, 580, 180, 580, 220, 580, 300, 580, 340, 580, 380, 580, 420, 580, 460, 580, 460, 580, 260, 580, 20, 420, 60, 420, 140, 620, 180, 620, 220, 620, 260, 620, 300, 620, 100, 620, 180, 660, 20, 460, 60, 460, 140, 660, 220, 660, 260, 660, 300, 660, 100, 660, 340, 620, 380, 620, 420, 620, 340, 660, 380, 660, 420, 660, 460, 620, 460, 660, 460, 540, 700, 700, 580, 580, 540, 620, 540, 660, 580, 620, 580, 660, 181, 540, 700, 540, 700, 580, 700, 620, 700, 660, 740, 700, 780, 700, 820, 700, 860, 700, 900, 700, 700, 740, 740, 740, 780, 740, 820, 740, 860, 740, 900, 740, 700, 880, 740, 880, 780, 880, 820, 880, 860, 880, 900, 880, 700, 920, 700, 960, 740, 920, 740, 960, 780, 920, 780, 960, 820, 920, 820, 960, 860, 920, 860, 960, 900, 920, 900, 960, 980, 920, 940, 920, 940, 960, 980, 960, 1060, 800, 1020, 840, 1020, 880, 1060, 840, 1060, 880, 1020, 800, 1020, 920, 1060, 920, 1060, 960, 1020, 960, 860, 660, 900, 660, 540, 580, 540, 960, 580, 960, 620, 960, 660, 960, 500, 920, 460, 920, 420, 880, 380, 880, 340, 840, 260, 840, 300, 840, 660, 660, 620, 660, 500, 660, 60, 380, 60, 500, 20, 500, 60, 540, 20, 540, 60, 580, 20, 580, 60, 620, 20, 620, 60, 660, 20, 660, 220, 840, 180, 800, 140, 800, 100, 760, 60, 760 };
		public int[] level3Enemies = { 320, 520, 1, 800, 640, 1, 1020, 760, 2, 1060, 760, 2, 420, 540, 2, 500, 620, 2, 620, 620, 2, 660, 620, 2, 280, 800, 2, 740, 665, 2 };
		public int[] level3Stars = { 80, 720 };

		// false
		public const bool dev_allowAllControls = false; // causes bugs... avoid!!

		public List<Player> playerOrder = new List<Player>();
		Penguin penguin = null;
		public List<AABB> solids = null;
		public List<Enemy> enemies = null;
		public List<AABB> stars = null;
		public List<Fry> fries = null;

		public override void GameStarted() {
			int tickDelay = (int)Math.Floor(1000.0 * 1.0 / 30.0);
			AddTimer(Tick, tickDelay);
			AddTimer(UpdatePlayers, 100);
		}

		public void LoadLevel(int num) {
			Console.WriteLine("Loading level " + num);
			
			numStarsPickedUp = 0;
			currLevel = num;
			penguin = new Penguin();
			penguin.x = 100;
			penguin.y = 500;

			solids = new List<AABB>();
			enemies = new List<Enemy>();
			stars = new List<AABB>();
			fries = new List<Fry>();

			if (num == 1) {
				AddSolids(level1);
				AddEnemies(level1Enemies);
				AddStars(level1Stars);
			}
			else if (num == 2) {
				AddSolids(level2);
				AddEnemies(level2Enemies);
				AddStars(level2Stars);
			}
			else if (num == 3) {
				AddSolids(level3);
				AddEnemies(level3Enemies);
				AddStars(level3Stars);
			}

			// avoid initial connection bug of no names b/c some clients connect faster than others
			for (int i = 0; i < playerOrder.Count; i++) {
				Broadcast(Message.Create("NetName", playerOrder[i].id, playerOrder[i].name));
			}

			Broadcast(Message.Create("NetResetLevel", num));

			// all clients guaranteed to be connected, so it is valid to do this here:
			// (!levelLoaded) {
				for (int i = 0; i < stars.Count; i++) {
					Broadcast(Message.Create("NetSpawnStar", stars[i].x, stars[i].y));
				}
			//}

			levelLoaded = true;
		}

		public void AddSolids(int[] _solids) {
			for (int i = 0; i < _solids.Length; i += 2) {
				float x = _solids[i];
				float y = _solids[i + 1];
				solids.Add(new AABB(x, y, tile_ext, tile_ext));
			}
		}

		public void AddEnemies(int[] _enemies) {
			for (int i = 0; i < _enemies.Length; i += 3) {
				float x = (float)_enemies[i];
				float y = (float)_enemies[i + 1];
				int type = _enemies[i + 2];
				float walkDir = 1.0f;
				if (rand.Next(0, 2) == 1)
					walkDir = -1.0f;
				enemies.Add(new Enemy(x, y, type, walkDir));
			}
		}

		public void AddStars(int[] _stars) {
			for (int i = 0; i < _stars.Length; i += 2) {
				float x = _stars[i];
				float y = _stars[i + 1];
				stars.Add(new AABB(x, y, 10.0f, 10.0f));
			}
		}

		public bool spacebar = false;
		public bool movingLeft = false;
		public bool movingRight = false;
		public bool mouseDown = false;
		public float crosshairWorldx = 0;
		public float crosshairWorldy = 0;
		public float charge = 0;
		public bool isShooting = false;

		public Pointf AABBMinTranslation(AABB A, AABB B) {
			Pointf mtd = new Pointf(0, 0);

			float left = B.minx - A.maxx;
			float right = B.maxx - A.minx;
			float top = B.miny - A.maxy;
			float bottom = B.maxy - A.miny;

			// box dont intersect   
			if (left > 0 || right < 0) return null;
			if (top > 0 || bottom < 0) return null;

			// box intersect. work out the mtd on both x and y axes.
			if (Math.Abs(left) < right)
				mtd.x = left;
			else
				mtd.x = right;

			if (Math.Abs(top) < bottom)
				mtd.y = top;
			else
				mtd.y = bottom;

			/*
			// 0 the axis with the largest mtd value.
			if (Math.Abs(mtd.x) < Math.Abs(mtd.y))
				mtd.y = 0;
			else
				mtd.x = 0;
			*/

			return mtd;
		}

		public Pointf AABBMinTranslationX(AABB A, AABB B) {
			Pointf mtd = new Pointf(0, 0);

			float left = B.minx - A.maxx;
			float right = B.maxx - A.minx;
			float top = B.miny - A.maxy;
			float bottom = B.maxy - A.miny;

			// box dont intersect   
			if (left > 0 || right < 0) return null;
			if (top > 0 || bottom < 0) return null;

			mtd.y = 0;

			// box intersect. work out the mtd on both x and y axes.
			if (Math.Abs(left) < right)
				mtd.x = left;
			else
				mtd.x = right;

			return mtd;
		}

		public Pointf AABBMinTranslationY(AABB A, AABB B) {
			Pointf mtd = new Pointf(0, 0);

			float left = B.minx - A.maxx;
			float right = B.maxx - A.minx;
			float top = B.miny - A.maxy;
			float bottom = B.maxy - A.miny;

			// box dont intersect   
			if (left > 0 || right < 0) return null;
			if (top > 0 || bottom < 0) return null;

			mtd.x = 0;

			if (Math.Abs(top) < bottom)
				mtd.y = top;
			else
				mtd.y = bottom;

			return mtd;
		}

		public const float maxvely = 300.0f;
		public const float grav = 1200.0f;
		public float nextUpdate = 0;
		public float nextCheckDisconnected = 0.0f;

		public void Tick() {
			lock (this) {
				if (!levelLoaded) {
					if (playerOrder.Count >= 3) {
						LoadLevel(1);
						AssignRoles();
						levelLoaded = true;
					}
					return;
				}

				/*
				if (nextCheckDisconnected < time) {
					nextCheckDisconnected = time + 1.0f;
					for (int i = 0; i < playerOrder.Count; i++) {
						// more than 2.. 1 or whichever
						if (!playerOrder[i].disconnected && playerOrder[i].numNotifies >= 2 && (time - playerOrder[i].lastNotify) > 5.0f) {
							playerOrder[i].disconnected = true;
							//Broadcast(Message.Create("NetLeft", playerOrder[i].id));
						}
					}
				}
				 * */

				const float dt = 1.0f / 30.0f;
				time += dt;
				bool onground = false;

				if (enemies != null) {
					for (int i = 0; i < enemies.Count; i++) {
						if (enemies[i].type != 1)
							continue;

						Enemy enemy = enemies[i];
						enemy.vely += grav * dt;
						if (enemy.vely > maxvely)
							enemy.vely = maxvely;
						enemy.box.Set(enemy.x, enemy.y, enemy.box.ex, enemy.box.ey);

						/*
						for (int j = 0; j < solids.Count; j++) {
							Pointf trans = AABBMinTranslation(enemy.box, solids[j]);
							if (trans != null) {
								if (trans.y != 0) {
									enemy.box.Set(enemy.x + trans.x, enemy.y + trans.y, enemy.box.ex, enemy.box.ey);
									enemy.x = enemy.box.x;
									enemy.y = enemy.box.y;
									enemy.vely = 0.0f;
								}

								if (Math.Abs(trans.x) >= 1.0f) {
									enemy.box.Set(enemy.x + trans.x, enemy.y + trans.y, enemy.box.ex, enemy.box.ey);
									enemy.x = enemy.box.x;
									enemy.y = enemy.box.y;
									enemy.walkDir *= -1.0f;
								}
							}
						}
						 * */
						for (int j = 0; j < solids.Count; j++) {
							Pointf trans = AABBMinTranslationY(enemy.box, solids[j]);
							if (trans != null) {
								if (Math.Abs(trans.y) < 10) {
									enemy.box.Set(enemy.x + trans.x, enemy.y + trans.y * 1.01f, enemy.box.ex, enemy.box.ey);
									enemy.x = enemy.box.x;
									enemy.y = enemy.box.y;
									enemy.vely = 0.0f;
								}
							}
						}

						for (int j = 0; j < solids.Count; j++) {
							Pointf trans = AABBMinTranslationX(enemy.box, solids[j]);
							if (trans != null) {
								if (Math.Abs(trans.x) >= 1.0f) {
									enemy.box.Set(enemy.x + trans.x, enemy.y + trans.y, enemy.box.ex, enemy.box.ey);
									enemy.x = enemy.box.x;
									enemy.y = enemy.box.y;
									enemy.walkDir *= -1.0f;
								}
							}
						}


						enemy.y += enemy.vely * dt;
						enemy.x += enemy.walkDir * enemy.speed * dt;
					}
				}

				if (fries != null) {
					List<Fry> deadFries = null;
					for (int i = 0; i < fries.Count; i++) {
						Fry f = fries[i];
						f.x += f.velx * dt;
						f.y += f.vely * dt;
						f.vely += f.grav * dt;
						if (f.nextKill < time) {
							f.killed = true;
							if (deadFries == null)
								deadFries = new List<Fry>();
							deadFries.Add(f);
						}
						else {
							// check solids
							for (int j = 0; j < solids.Count; j++) {
								AABB s = solids[j];
								if (s.PointInside(f.x, f.y)) {
									f.killed = true;
									if (deadFries == null)
										deadFries = new List<Fry>();
									Broadcast(Message.Create("NetPlaySound", 2));
									deadFries.Add(f);
									break;
								}
							}
							// check if hitting any enemies
							if (!f.killed) {
								for (int j = 0; j < enemies.Count; j++) {
									if (enemies[j].type != 1 || !enemies[j].alive)
										continue;
									AABB s = enemies[j].box;
									if (s.PointInside(f.x, f.y)) {
										f.killed = true;
										if (deadFries == null)
											deadFries = new List<Fry>();
										enemies[j].alive = false;
										Broadcast(Message.Create("NetKilledEnemy", j));
										Broadcast(Message.Create("NetPlaySound", 1));
										deadFries.Add(f);
										break;
									}
								}
							}
						}
					}

					if (deadFries != null) {
						for (int i = 0; i < deadFries.Count; i++) {
							Broadcast(Message.Create("NetDeadFry", deadFries[i].id));
							fries.Remove(deadFries[i]);
						}
					}
				}

				if (penguin.killed) {
					if (nextRespawnEverything < time) {
						LoadLevel(currLevel);
						return;
					}
				}
				else {
					// gravity
					penguin.vely += grav * dt;
					if (penguin.vely > maxvely)
						penguin.vely = maxvely;

					// strafing
					if (movingLeft) {
						if (penguin.velx > 0)
							penguin.velx = 0;
						penguin.velx -= 800 * dt;
					}
					else if (movingRight) {
						if (penguin.velx < 0)
							penguin.velx = 0;
						penguin.velx += 800 * dt;
					}
					else {
						penguin.velx = Misc.Dampen(penguin.velx, 7.0f, dt);
					}
					penguin.velx = Misc.Clamp(penguin.velx, -maxSpeed, maxSpeed);
					penguin.box.Set(penguin.x, penguin.y, penguin.ex, penguin.ey);

					// check collision horizontal, push out

					/*
					for (int i = 0; i < solids.Count; i++) {
						Pointf trans = AABBMinTranslation(penguin.box, solids[i]);
						if (trans != null) {
							penguin.box.Set(penguin.box.x + trans.x, penguin.box.y + trans.y, penguin.ex, penguin.ey);
							if (trans.y != 0) {
								penguin.vely = 0;
								if (trans.y < 0)
									onground = true;
							}
							else if (trans.x != 0) {
								penguin.velx = 0;
							}
						}
					}
					 * */

					// test Y
					for (int i = 0; i < solids.Count; i++) {
						Pointf trans = AABBMinTranslationY(penguin.box, solids[i]);
						if (trans != null && Math.Abs(trans.y) < 10.0f) {
							penguin.box.Set(penguin.box.x + trans.x, penguin.box.y + trans.y * 1.01f, penguin.ex, penguin.ey);
							if (trans.y != 0) {
								penguin.vely = 0;
								if (trans.y < 0)
									onground = true;
							}
						}
					}
					
					for (int i = 0; i < solids.Count; i++) {
						Pointf trans = AABBMinTranslationX(penguin.box, solids[i]);
						if (trans != null) {
							penguin.box.Set(penguin.box.x + trans.x, penguin.box.y + trans.y, penguin.ex, penguin.ey);
							if (trans.y != 0) {
								penguin.vely = 0;
								if (trans.y < 0)
									onground = true;
							}
							else if (trans.x != 0) {
								penguin.velx = 0;
							}
						}
					}

					penguin.x = penguin.box.x;
					penguin.y = penguin.box.y;

					if (spacebar && onground) {
						Broadcast(Message.Create("NetPlaySound", 0));
						penguin.vely = -540;
					}

					penguin.x += penguin.velx * dt;
					penguin.y += penguin.vely * dt;

					if (penguin.y > 1280) {
						LoadLevel(currLevel);
						return;
					}

					for (int i = 0; i < enemies.Count; i++) {
						if (!enemies[i].alive)
							continue;
						Pointf trans = AABBMinTranslation(penguin.box, enemies[i].box);
						if (trans != null) {
							penguin.killed = true;
							nextRespawnEverything = time + 3.0f;
							Broadcast(Message.Create("NetPenguinKilled"));
							break;
						}
					}

					for (int i = 0; i < stars.Count; i++) {
						if (!stars[i].pickedUp) {
							Pointf trans = AABBMinTranslation(penguin.box, stars[i]);
							if (trans != null) {
								numStarsPickedUp++;
								stars[i].pickedUp = true;
								Broadcast(Message.Create("NetCollectedStar", i));

								currLevel++;
								if (currLevel > 3)
									currLevel = 1;
								LoadLevel(currLevel);
							}
						}
					}
				}
			}
			/*
			if (nextUpdate <= time) {
				nextUpdate = time + 0.1f;
				UpdatePlayers();
			}
			 */
		}

		public void UpdatePlayers() {
			lock (this) {
				if (!levelLoaded)
					return;

				const float dt = 10.00f;
				if (enemies != null) {
					for (int i = 0; i < enemies.Count; i++) {
						Enemy e = enemies[i];
						Message m = Message.Create("NetUpdateEnemy", e.x, e.y, e.type, i);
						Broadcast(m);
					}
				}
				if (fries != null) {
					for (int i = 0; i < fries.Count; i++) {
						Fry f = fries[i];
						float rot = (float)Math.Atan2((double)f.vely, (double)f.velx);
						Broadcast(Message.Create("NetUpdateFry", f.id, f.x, f.y, rot));
					}
				}
				Broadcast(Message.Create("NetUpdatePenguin", penguin.x, penguin.y));
				Broadcast(Message.Create("NetControls", movingLeft, movingRight, spacebar, mouseDown, crosshairWorldx, crosshairWorldy, charge));
			}
		}

		public override void GameClosed() {
			Console.WriteLine("RoomId: " + RoomId);
		}

		public override void UserJoined(Player player) {
			lock (this) {
				player.type = -1;
				player.rvar = rand.Next(0, 99999);
				player.id = 500 + playerOrder.Count;
				playerOrder.Add(player);
				//Console.WriteLine("UserJoined " + player.id + "," + playerOrder.Count);
			}
		}

		public void AssignRoles() {
			lock (this) {
				//playerOrder.Sort(ComparePlayer);
				Console.WriteLine("assigning roles");
				for (int i = 0; i < playerOrder.Count; i++) {
					playerOrder[i].type = i;
				}
				// send to p
				for (int i = 0; i < playerOrder.Count; i++) {
					Player p = playerOrder[i];
					// manual broadcast
					for (int j = 0; j < playerOrder.Count; j++) {
						bool self = (i == j);
						//Console.WriteLine("sending " + playerOrder[j].id + "," + self);
						p.Send(Message.Create("NetAnnounceHuman", playerOrder[j].id, self, j));
					}
				}
			}
		}

		public int ComparePlayer(Player p1, Player p2) {
			if (p1.rvar >= p2.rvar)
				return 1;
			return -1;
		}

		public override void UserLeft(Player player) {
			lock (this) {
				Console.WriteLine("Player " + player.id + " left the room");
				Broadcast("NetLeft", player.id);
			}
		}

		public void Respawn() {
			LoadLevel(currLevel);
		}

		public override void GotMessage(Player player, Message message) {
			lock (this) {
				switch (message.Type) {
					case "movement":
						if (dev_allowAllControls || player.type == 0) {
							int type = message.GetInt(0);
							if (type == 1) {
								movingLeft = true;
								movingRight = false;
							}
							else if (type == 2) {
								movingLeft = false;
								movingRight = true;
							}
							else {
								movingLeft = false;
								movingRight = false;
							}
						}
						break;
					case "spacebar":
						if (dev_allowAllControls || player.type == 1) {
							spacebar = message.GetBoolean(0);
						}
						break;
					case "MyNameIs":
						int id = message.GetInt(0);
						for (int i = 0; i < playerOrder.Count; i++) {
							if (playerOrder[i].id == id) {
								playerOrder[i].name = message.GetString(1);
								Broadcast(Message.Create("NetName", id, playerOrder[i].name));
							}
						}
						break;
					case "notify":
						int id2 = message.GetInt(0);
						for (int i = 0; i < playerOrder.Count; i++) {
							if (playerOrder[i].id == id2) {
								playerOrder[i].lastNotify = time;
								playerOrder[i].numNotifies++;
							}
						}
						break;
					case "crosshair":
						if (player.type == 2) {
							crosshairWorldx = message.GetFloat(0);
							crosshairWorldy = message.GetFloat(1);
							mouseDown = message.GetBoolean(2);
							charge = message.GetFloat(3);
							// would have been safer if the server keeps track of the charge
							if (charge < 0)
								charge = 0;
							else if (charge > 1)
								charge = 1;
							isShooting = message.GetBoolean(4);

							float dx = message.GetFloat(5);
							float dy = message.GetFloat(6);

							//Console.WriteLine("" + dx + "," + dy);
							//float avelx = crosshairWorldx - penguin.x;
							//float avely = crosshairWorldy - penguin.y;
							//Console.WriteLine("" + avelx + "," + avely);

							if (isShooting) {
								if (!penguin.killed) {
									if (dx != 0.0f || dy != 0.0f) {
										Fry f = new Fry();
										f.x = penguin.x;
										f.y = penguin.y - penguin.ey * 0.6666f;
										f.grav = Misc.Lerp(1000.0f, 0.0f, charge);
										float speed = Misc.Lerp(20.0f, 500.0f, charge);
										f.velx = dx * speed;
										f.vely = dy * speed;
										f.nextKill = time + 5.0f;
										f.id = uniqueId++;
										fries.Add(f);
									}
									/*
									float velx = crosshairWorldx - penguin.x;
									float vely = crosshairWorldy - penguin.y;
									float len2 = velx * velx + vely * vely;
									if (len2 >= 1.0) {
										float len = (float)Math.Sqrt((double)len2);
										float speed = Misc.Lerp(20.0f, 300.0f, charge);
										velx *= speed / len;
										vely *= speed / len;
										Fry fry = new Fry();
										fry.id = uniqueId++;

										fry.x = penguin.x;
										fry.y = penguin.y;
										fry.velx = velx;
										fry.vely = vely;

										Console.WriteLine(fry.x + "," + fry.y + "," + fry.velx + "," + fry.vely);

										fries.Add(fry);
									}
									 * */
								}
								isShooting = false;
							}
						}
						break;
				}
			}
		}
	}
}
