package com.vogella.jersey.first.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.bson.Document;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.vogella.jersey.first.model.Account;
import com.vogella.jersey.first.model.AccountInfo;
import com.vogella.jersey.first.model.MoveGameCharacterInfo;
import com.vogella.jersey.first.model.Reward;
import com.vogella.jersey.first.model.mapobject.AreaEffect;
import com.vogella.jersey.first.model.mapobject.Chest;
import com.vogella.jersey.first.model.mapobject.Coin;
import com.vogella.jersey.first.model.mapobject.Crown;
import com.vogella.jersey.first.model.mapobject.GameCharacter;
import com.vogella.jersey.first.model.mapobject.Key;
import com.vogella.jersey.first.model.mapobject.Portal;

public class GameMongoDBUtils{

	public static final int MIN_CHARACTER_SLOT = 0;
	public static final int MAX_CHARACTER_SLOT = 1;
	public static final long MIN_EXP_LEVEL = 1;
	public static final long MAX_EXP_LEVEL = 1000;
	public static final long MIN_MONEY = 0;
	public static final long MIN_EXP_PROGRESS_GENERAL = 0;
	public static final String DATABASE_NAME = "geogame";
	public static final String ACCOUNT_COLLECTION_NAME = "accounts";
	public static final String PORTALS_COLLECTION_NAME = "portals";
	public static final String CHAT_GEN_COLLECTION_NAME = "chatGen";
	public static final String CHAT_TEAM0_COLLECTION_NAME = "chat0";
	public static final String CHAT_TEAM1_COLLECTION_NAME = "chat1";
	public static final double EARTH_RADIUS_1 = 6371000;
	public static final double EARTH_RADIUS_2 = 6378137;
	public static final double LEVEL_PROGRESS_GAIN_COIN = 0.075;
	public static final double LEVEL_PROGRESS_CAPTURE_PORTAL = 0.225;
	public static final long MAX_CHAT_MESSAGES_STORED = 50;
	public static final int CHAT_TYPE_GENERAL = 0;
	public static final int CHAT_TYPE_TEAM0 = 1;
	public static final int CHAT_TYPE_TEAM1 = 2;

	private MongoClient mongoClient;
	private AccountManager accountManager;
	private ChestManager<Chest> chestManager;
	private CoinManager<Coin> coinManager;
	private CrownManager<Crown> crownManager;
	private GameCharacterManager<GameCharacter> gameCharacterManager;
	private KeyManager<Key> keyManager;
	private PortalManager<Portal> portalManager;
	private AreaEffectManager<AreaEffect> areaEffectManager;

	public GameMongoDBUtils(){
		this.mongoClient = new MongoClient("localhost",20206);
		accountManager = new AccountManager(mongoClient);
		chestManager = new ChestManager<Chest>(Chest.class,mongoClient);
		coinManager = new CoinManager<Coin>(Coin.class,mongoClient);
		crownManager = new CrownManager<Crown>(Crown.class,mongoClient);
		gameCharacterManager = new GameCharacterManager<GameCharacter>(GameCharacter.class,mongoClient);
		keyManager = new KeyManager<Key>(Key.class,mongoClient);
		portalManager = new PortalManager<Portal>(Portal.class,mongoClient);
		areaEffectManager = new AreaEffectManager<AreaEffect>(AreaEffect.class,mongoClient);
	}

	public void close(){
		if(mongoClient != null){
			mongoClient.close();
		}
	}

	public void dropCollection(MongoCollection<Document> collection){
		if(collection != null){
			collection.drop();
		}
	}

	public void dropbDB(){
		MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
		database.drop();
	}

	public static boolean isEmpty(String string){
		return string != null && string.isEmpty();
	}

	public static boolean isEmptyNull(String string){
		return (string == null) || (isEmpty(string));
	}

	public static boolean isEmptyNull(String...strings){
		for(String string:strings){
			if(!isEmptyNull(string)){
				return false;
			}
		}
		return true;
	}

	public AccountInfo login(String id,String password){
		Account account = accountManager.login(id,password);
		if(account != null){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			return new AccountInfo(account,gameCharacter0,gameCharacter1);
		}
		return null;
	}

	public AccountInfo logout(String token,String id,String password){
		Account account = accountManager.logout(token,id,password);
		if(account != null){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			return new AccountInfo(account,gameCharacter0,gameCharacter1);
		}
		return null;
	}

	public AccountInfo register(String id,String password,String email){
		Account account = accountManager.register(id,password,email);
		if(account != null){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			return new AccountInfo(account,gameCharacter0,gameCharacter1);
		}
		return null;
	}

	public AccountInfo updateEmail(String token,String id,String password,String newEmail){
		Account account = accountManager.updateEmail(token,id,password,newEmail);
		if(account != null){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			return new AccountInfo(account,gameCharacter0,gameCharacter1);
		}
		return null;
	}

	public AccountInfo getAccountInfo(String token,String id,String password){
		Account account = accountManager.get(id);
		System.out.println("getAccountInfo account: " + account);
		if(account != null && account.validateEqual(token,id,password)){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			System.out.println("getAccountInfo gameCharacter0: " + gameCharacter0);
			System.out.println("getAccountInfo gameCharacter1: " + gameCharacter1);
			return new AccountInfo(account,gameCharacter0,gameCharacter1);
		}
		return null;
	}

	public AccountInfo selectGameCharacter(String token,String id,String password,int accountSlot){
		Account account = accountManager.get(id);
		if(account != null && account.validateEqual(token,id,password)){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			if(accountSlot == 0 && gameCharacter0 != null && account.gameCharacterSlot0.equals(gameCharacter0.id)){
				account.currentGameCharacterSlot = accountSlot;
				account = accountManager.updateById(account);
				if(account != null){
					return new AccountInfo(account,gameCharacter0,gameCharacter1);
				}
			}else if(accountSlot == 1 && gameCharacter1 != null && account.gameCharacterSlot1.equals(gameCharacter1.id)){
				account.currentGameCharacterSlot = accountSlot;
				account = accountManager.updateById(account);
				if(account != null){
					return new AccountInfo(account,gameCharacter0,gameCharacter1);
				}
			}
		}
		return null;
	}

	public AccountInfo addGameCharacter(String token,String id,String password,int accountSlot,int gameCharacterTeam,int gameCharacterClass,String gameCharacterName,double gameCharacterLatitude,double gameCharacterLongitude,double gameCharacterMetersSee,double gameCharacterMetersTouch){
		Account account = accountManager.get(id);
		if(account != null && account.validateEqual(token,id,password)){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			if(accountSlot == 0 && gameCharacter0 == null){
				// TODO TESTING gameCharacter0 = new
				// GameCharacter("",0,0,account.id,gameCharacterTeam,gameCharacterClass,0,1,0,gameCharacterName,gameCharacterMetersSee,gameCharacterMetersTouch,false,null);
				gameCharacter0 = new GameCharacter("",0,0,account.id,gameCharacterTeam,gameCharacterClass,99999999,1,0,gameCharacterName,gameCharacterMetersSee,gameCharacterMetersTouch,false,null);
				gameCharacter0 = gameCharacterManager.insert(gameCharacter0);
				if(gameCharacter0 != null){
					account.gameCharacterSlot0 = gameCharacter0.id;
					account = accountManager.updateById(account);
				}
			}else if(accountSlot == 1 && gameCharacter1 == null){
				// TODO TESTING gameCharacter1 = new
				// GameCharacter("",0,0,account.id,gameCharacterTeam,gameCharacterClass,0,1,0,gameCharacterName,gameCharacterMetersSee,gameCharacterMetersTouch,false,null);
				gameCharacter1 = new GameCharacter("",0,0,account.id,gameCharacterTeam,gameCharacterClass,99999999,1,0,gameCharacterName,gameCharacterMetersSee,gameCharacterMetersTouch,false,null);
				gameCharacter1 = gameCharacterManager.insert(gameCharacter1);
				if(gameCharacter1 != null){
					account.gameCharacterSlot1 = gameCharacter1.id;
					account = accountManager.updateById(account);
				}
			}
			if(account != null){
				return new AccountInfo(account,gameCharacter0,gameCharacter1);
			}
		}
		return null;
	}

	public AccountInfo deleteGameCharacter(String token,String id,String password,int accountSlot){
		Account account = accountManager.get(id);
		if(account != null && account.validateEqual(token,id,password)){
			GameCharacter gameCharacter0 = gameCharacterManager.get(account.gameCharacterSlot0);
			GameCharacter gameCharacter1 = gameCharacterManager.get(account.gameCharacterSlot1);
			if(accountSlot == 0 && gameCharacter0 != null){
				String deletedGCharId = gameCharacterManager.delete(gameCharacter0.id);
				if(!isEmpty(deletedGCharId)){
					gameCharacter0 = null;
					if(account.currentGameCharacterSlot == accountSlot){
						account.currentGameCharacterSlot = -1;
					}
					account.gameCharacterSlot0 = "";
					account = accountManager.updateById(account);
					if(account != null){
						return new AccountInfo(account,gameCharacter0,gameCharacter1);
					}
				}
			}else if(accountSlot == 1 && gameCharacter1 != null){
				String deletedGCharId = gameCharacterManager.delete(gameCharacter1.id);
				if(!isEmpty(deletedGCharId)){
					gameCharacter1 = null;
					if(account.currentGameCharacterSlot == accountSlot){
						account.currentGameCharacterSlot = -1;
					}
					account.gameCharacterSlot1 = "";
					account = accountManager.updateById(account);
					if(account != null){
						return new AccountInfo(account,gameCharacter0,gameCharacter1);
					}
				}
			}
		}
		return null;
	}

	public GameCharacter getPlayingGameCharacter(String token,String id,String password){
		Account account = accountManager.get(id);
		if(account != null && account.validateEqual(token,id,password)){
			if(account.currentGameCharacterSlot == 0){
				return gameCharacterManager.get(account.gameCharacterSlot0);
			}else if(account.currentGameCharacterSlot == 1){
				return gameCharacterManager.get(account.gameCharacterSlot1);
			}
		}
		return null;
	}

	private GameCharacter getPlayingGameCharacter(Account account){
		if(account != null){
			if(account.currentGameCharacterSlot == 0){
				return gameCharacterManager.get(account.gameCharacterSlot0);
			}else if(account.currentGameCharacterSlot == 1){
				return gameCharacterManager.get(account.gameCharacterSlot1);
			}
		}
		return null;
	}

	/*
	 * Object[] data = new Object[] { gameCharacter, mapData };
	 */
	/*
	 * public Object[] movedGameCharacter(String token,String user,String
	 * password,double latitude,double longitude){ Account account =
	 * getAccount(token,user,password); GameCharacter gameCharacter =
	 * getPlayingGameCharacter(account);
	 * System.out.println("movedGameCharacter() account: " + account);
	 * System.out.println("movedGameCharacter() gameCharacter: " +
	 * gameCharacter); if(account != null && gameCharacter != null){
	 * gameCharacter.latitude = latitude; gameCharacter.longitude = longitude;
	 * // TODO update game character in map gameCharacter =
	 * updateGameCharacterInMap(gameCharacter,gameCharacter.rangeTouch);
	 * 
	 * if(gameCharacter != null){
	 * 
	 * // TODO get coins in the radius List<Coin> coins =
	 * getCoinsInMap(latitude,longitude,gameCharacter.rangeSee);
	 * 
	 * System.out.println("movedGameCharacter() coins.size(): " + coins.size());
	 * 
	 * // TODO get game characters in the radius List<GameCharacter>
	 * gameCharacters =
	 * getGameCharactersInMap(latitude,longitude,gameCharacter.rangeSee);
	 * 
	 * System.out.println("movedGameCharacter() gameCharacters.size(): " +
	 * gameCharacters.size());
	 * 
	 * // TODO get portals in the radius List<Portal> portals =
	 * getPortalsInMap(latitude,longitude,gameCharacter.rangeSee);
	 * 
	 * System.out.println("movedGameCharacter() portals.size(): " +
	 * portals.size());
	 * 
	 * // TODO return map data GCharMoveResult mapData = new GCharMoveResult();
	 * mapData.setCoins(coins); mapData.setGameCharacters(gameCharacters);
	 * mapData.setPortals(portals);
	 * 
	 * Object[] data = new Object[]{gameCharacter,mapData}; return data; } }
	 * return null; }
	 * 
	 * private GameCharacter updateGameCharacterInMap(GameCharacter
	 * gameCharacter,double captureRadius){ // gather nearby coins List<Coin>
	 * reachableCoins =
	 * getCoinsInMap(gameCharacter.latitude,gameCharacter.longitude
	 * ,captureRadius); String id; double levelProgressGainCoin; for(Coin
	 * coin:reachableCoins){ id = deleteCoin(coin.id); if(id != null){
	 * gameCharacter.money = gameCharacter.money + coin.value;
	 * 
	 * addBaseExpGameCharacter(LEVEL_PROGRESS_GAIN_COIN,gameCharacter); } }
	 * 
	 * List<Portal> reachablePortals =
	 * getPortalsInMap(gameCharacter.latitude,gameCharacter
	 * .longitude,captureRadius); for(Portal portal:reachablePortals){
	 * portal.progressCapture = 1; if(portal.teamOwner != gameCharacter.team){
	 * portal.teamOwner = gameCharacter.team;
	 * addBaseExpGameCharacter(LEVEL_PROGRESS_CAPTURE_PORTAL,gameCharacter); }
	 * System.out.println("portalUpdated: " + portal); Portal portal2 =
	 * updatePortalById(portal); System.out.println("portalUpdated: " +
	 * portal2); }
	 * 
	 * gameCharacter = updateGameCharacterById(gameCharacter); if(gameCharacter
	 * == null){ // TODO reverse coin deletion and portal }
	 * 
	 * return gameCharacter; }
	 * 
	 * private void addBaseExpGameCharacter(double baseExp,GameCharacter
	 * gameCharacter){ double exp = baseExp * (3 -
	 * Math.log10(gameCharacter.level)); while(exp > 0){ if(exp < 1 -
	 * gameCharacter.progress){ gameCharacter.progress = gameCharacter.progress
	 * + exp; exp = 0; }else if(exp == 1 - gameCharacter.progress){
	 * gameCharacter.progress = 0; gameCharacter.level =
	 * Math.min(gameCharacter.level + 1,MAX_EXP_LEVEL); exp = 0; }else{ exp =
	 * exp - (1 - gameCharacter.progress); gameCharacter.progress = 0;
	 * gameCharacter.level = Math.min(gameCharacter.level + 1,MAX_EXP_LEVEL); }
	 * } }
	 * 
	 * private List<GameCharacter> getGameCharactersInMap(double latitude,double
	 * longitude,double radius){ MongoCollection<Document> collection =
	 * getGameCharacterCollection(); double[] squareValues =
	 * getOffsetSquareValues(latitude,longitude,radius);
	 * 
	 * System.out.println("getGameCharactersInMap() squareValues: " +
	 * squareValues[0] + ", " + squareValues[1] + ", " + squareValues[2] + ", "
	 * + squareValues[3]);
	 * 
	 * FindIterable<Document> listDoc =
	 * collection.find(and(and(lte("latitude",squareValues
	 * [0]),gte("latitude",squareValues
	 * [2])),and(lte("longitude",squareValues[1])
	 * ,gte("longitude",squareValues[3]))));
	 * 
	 * MongoCursor<Document> cursor = listDoc.iterator(); List<GameCharacter>
	 * gameCharacters = new ArrayList<>(); try{ GameCharacter gameCharacter;
	 * Gson gson = new Gson(); while(cursor.hasNext()){
	 * System.out.println("getGameCharactersInMap() cursor.hasNext()");
	 * gameCharacter =
	 * gson.fromJson(cursor.next().toJson(),GameCharacter.class);
	 * if(distFrom(latitude
	 * ,longitude,gameCharacter.latitude,gameCharacter.longitude) <= radius){
	 * gameCharacters.add(gameCharacter); } } }finally{ cursor.close(); } return
	 * gameCharacters; }
	 * 
	 * private List<Portal> getPortalsInMap(double latitude,double
	 * longitude,double radius){ MongoCollection<Document> collection =
	 * getPortalsCollection(); double[] squareValues =
	 * getOffsetSquareValues(latitude,longitude,radius);
	 * 
	 * System.out.println("getPortalsInMap() squareValues: " + squareValues[0] +
	 * ", " + squareValues[1] + ", " + squareValues[2] + ", " +
	 * squareValues[3]);
	 * 
	 * FindIterable<Document> listDoc =
	 * collection.find(and(and(lte("latitude",squareValues
	 * [0]),gte("latitude",squareValues
	 * [2])),and(lte("longitude",squareValues[1])
	 * ,gte("longitude",squareValues[3]))));
	 * 
	 * MongoCursor<Document> cursor = listDoc.iterator(); List<Portal> portals =
	 * new ArrayList<>(); try{ Portal portal; Gson gson = new Gson();
	 * while(cursor.hasNext()){
	 * System.out.println("getPortalsInMap() cursor.hasNext()"); portal =
	 * gson.fromJson(cursor.next().toJson(),Portal.class);
	 * if(distFrom(latitude,longitude,portal.latitude,portal.longitude) <=
	 * radius){ portals.add(portal); } } }finally{ cursor.close(); } return
	 * portals; }
	 */

	public static float distFrom(double lat1,double lng1,double lat2,double lng2){
		double earthRadius = EARTH_RADIUS_1; // meters
		double dLat = Math.toRadians(lat2 - lat1);
		double dLng = Math.toRadians(lng2 - lng1);
		double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
		double c = 2 * Math.atan2(Math.sqrt(a),Math.sqrt(1 - a));
		float dist = (float)(earthRadius * c);

		return dist;
	}

	/**
	 * 
	 * Returns the square bounds offset +-----1-----+ | | | | | | |
	 * 4-----x-----2 | | | | | | | +-----3-----+
	 * 
	 * (clockwise) 1 -> top latitude 2 -> right longitude 3 -> bottom latitude 4
	 * -> left longitude
	 * 
	 * @param latitude
	 * @param longitude
	 * @param radius
	 * @return
	 */
	public static double[] getOffsetSquareValues(double latitude,double longitude,double radius){
		double[] offsetValues = new double[4];
		offsetValues[0] = getOffsetValue(latitude,longitude,radius,0)[0]; // top
																			// latitude
		offsetValues[1] = getOffsetValue(latitude,longitude,0,radius)[1]; // right
																			// longitude
		offsetValues[2] = getOffsetValue(latitude,longitude,-radius,0)[0]; // bottom
																			// latitude
		offsetValues[3] = getOffsetValue(latitude,longitude,0,-radius)[1]; // left
																			// longitude
		return offsetValues;
	}

	public static double[] getOffsetValue(double latitude,double longitude,double metersVertical,double metersHorizontal){
		// Position, decimal degrees
		double lat = latitude;
		double lon = longitude;
		// Earths radius, sphere
		double R = EARTH_RADIUS_2;
		// offsets in meters
		double dn = metersVertical;
		double de = metersHorizontal;
		// Coordinate offsets in radians
		double dLat = dn / R;
		double dLon = de / (R * Math.cos(Math.PI * lat / 180f));
		// OffsetPosition, decimal degrees
		double offsetValueLat = lat + dLat * 180f / Math.PI;
		double offsetValueLon = lon + dLon * 180f / Math.PI;
		return new double[]{offsetValueLat,offsetValueLon};
	}

	public boolean throwProjectile(String token,String id,String password,double angle,int type){
		Account account = accountManager.get(id);
		if(account.validateEqual(token,id,password)){
			GameCharacter gameCharacter = getPlayingGameCharacter(account);
			if(gameCharacter != null){
				if(type == 0){
					new Thread(new Runnable(){
						@Override
						public void run(){
							double numProjs = 10;
							double[] latLonDest = null;
							double radius = 75;
							double step = (radius * 2) * 0.85f;
							int moneyToGrab = (int)(gameCharacter.crown ? gameCharacter.level * 2.5f : gameCharacter.level);
							AreaEffect areaEffect;
							for(int i = 1;i < numProjs + 1;i++){
								latLonDest = getOffsetValue(gameCharacter.latitude,gameCharacter.longitude,(i * step) * Math.cos(Math.toRadians(angle)),(i * step) * Math.sin(Math.toRadians(angle)));
								areaEffect = new AreaEffect(areaEffectManager.getNewId(),latLonDest[0],latLonDest[1],radius * 1.05f,moneyToGrab,gameCharacter.id,System.currentTimeMillis() + 10000,type,(float)angle);
								areaEffectManager.insert(areaEffect);
								try{
									Thread.sleep(400);
								}catch(InterruptedException e){
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							}
						}
					}).start();
					return true;
				}else if(type == 1){
					new Thread(new Runnable(){
						@Override
						public void run(){
							double modAngle;
							for(int j=-4;j<5;j++){
								modAngle = angle+20*j;
								double numProjs = 30;
								double[] latLonDest = null;
								double radius = 25;
								double step = (radius * 2) * 0.95f;
								int moneyToGrab = (int)(gameCharacter.crown ? gameCharacter.level * 2.5f : gameCharacter.level);
								AreaEffect areaEffect;
								for(int i = 1;i < numProjs + 1;i++){
									latLonDest = getOffsetValue(gameCharacter.latitude,gameCharacter.longitude,(i * step) * Math.cos(Math.toRadians(modAngle)),(i * step) * Math.sin(Math.toRadians(modAngle)));
									areaEffect = new AreaEffect(areaEffectManager.getNewId(),latLonDest[0],latLonDest[1],radius * 1.05f,moneyToGrab,gameCharacter.id,System.currentTimeMillis() + 10000,type,(float)modAngle);
									areaEffectManager.insert(areaEffect);
									try{
										Thread.sleep(150);
									}catch(InterruptedException e){
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
								}
							}
						}
					}).start();
					return true;
				}else if(type == 2){
					new Thread(new Runnable(){
						@Override
						public void run(){
							double modAngle;
							for(int j=-6;j<6;j++){
								modAngle = angle+45*j;
								double numProjs = 60;
								double[] latLonDest = null;
								double radius = 10;
								double step = (radius * 2) * 0.95f;
								int moneyToGrab = (int)(gameCharacter.crown ? gameCharacter.level * 2.5f : gameCharacter.level);
								AreaEffect areaEffect;
								for(int i = 1;i < numProjs + 1;i++){
									latLonDest = getOffsetValue(gameCharacter.latitude,gameCharacter.longitude,(i * step) * Math.cos(Math.toRadians(modAngle)),(i * step) * Math.sin(Math.toRadians(modAngle)));
									areaEffect = new AreaEffect(areaEffectManager.getNewId(),latLonDest[0],latLonDest[1],radius * 1.05f,moneyToGrab,gameCharacter.id,System.currentTimeMillis() + 10000,type,(float)modAngle);
									areaEffectManager.insert(areaEffect);
									try{
										Thread.sleep(100);
									}catch(InterruptedException e){
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
								}
							}
						}
					}).start();
					return true;
				}else{
					return false;
				}
				
			}
		}
		return false;
	}

	public MoveGameCharacterInfo moveGameCharacter(String token,String id,String password,double latitude,double longitude){

		GameCharacter gameCharacter = getPlayingGameCharacter(token,id,password);
		if(gameCharacter == null){
			return null;
		}
		gameCharacter.latitude = latitude;
		gameCharacter.longitude = longitude;
		gameCharacter = gameCharacterManager.updateById(gameCharacter);
		if(gameCharacter == null){
			return null;
		}

		List<Reward> rewardsToFill = new ArrayList<>();
		List<Coin> coinsSee = checkCoins(gameCharacter,rewardsToFill);
		List<Crown> crownsSee = checkCrowns(gameCharacter,rewardsToFill);
		List<Key> keysSee = checkKeys(gameCharacter,rewardsToFill);
		List<Chest> chestsSee = checkChests(gameCharacter,rewardsToFill);
		List<GameCharacter> gameCharactersSee = checkGameCharacters(gameCharacter,rewardsToFill);
		List<Portal> portalsSee = checkPortals(gameCharacter,rewardsToFill);

		List<AreaEffect> areaEffects = checkAreaEffects(gameCharacter,gameCharactersSee);

		MoveGameCharacterInfo moveGameCharacterInfo = new MoveGameCharacterInfo();
		moveGameCharacterInfo.areaEffects = areaEffects;
		moveGameCharacterInfo.gameCharacter = gameCharacter;
		moveGameCharacterInfo.coins = coinsSee;
		moveGameCharacterInfo.crowns = crownsSee;
		moveGameCharacterInfo.keys = keysSee;
		moveGameCharacterInfo.chests = chestsSee;
		moveGameCharacterInfo.gameCharacters = gameCharactersSee;
		moveGameCharacterInfo.portals = portalsSee;
		moveGameCharacterInfo.rewards = rewardsToFill;

		gameCharacter = gameCharacterManager.updateById(gameCharacter);

		/*
		 * accountManager chestManager coinManager crownManager
		 * gameCharacterManager keyManager portalManager
		 */

		return moveGameCharacterInfo;
	}

	private List<AreaEffect> checkAreaEffects(GameCharacter gameCharacter,List<GameCharacter> gameCharactersSee){
		List<AreaEffect> areaEffects = areaEffectManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		for(GameCharacter gameCharacterOther:gameCharactersSee){
			AreaEffect areaEffect;
			for(int i = areaEffects.size() - 1;i >= 0;i--){
				areaEffect = areaEffects.get(i);
				if(areaEffect.hasExpired(System.currentTimeMillis())){
					areaEffectManager.delete(areaEffect.id);
					areaEffects.remove(i);
					continue;
				}else if(gameCharacterOther.inRange(areaEffect,gameCharacter.rangeSee)){
					gameCharacterOther = areaEffect.applyEffect(gameCharacterOther,gameCharacterManager);
				}
			}
		}
		return areaEffects;
	}

	private List<Coin> checkCoins(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<Coin> coins = coinManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		Coin coin;
		for(int i = coins.size() - 1;i >= 0;i--){
			coin = coins.get(i);
			if(gameCharacter.inRange(coin,gameCharacter.rangeTouch)){
				coin = coins.remove(i);
				gameCharacter.money += coin.value;
				int exp = coin.value * 5;
				if(gameCharacter.crown){
					gameCharacter.crown = false;
					exp *= 4;
				}
				gameCharacter.addExp(exp);
				rewardsToFill.add(new Reward(Reward.TYPE_MONEY,coin.value));
				rewardsToFill.add(new Reward(Reward.TYPE_EXP,exp));
				coinManager.delete(coin);
			}
		}
		return coins;
	}

	private List<Crown> checkCrowns(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<Crown> crowns = crownManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		Crown crown;
		for(int i = crowns.size() - 1;i >= 0;i--){
			crown = crowns.get(i);
			if(!gameCharacter.crown && gameCharacter.inRange(crown,gameCharacter.rangeTouch)){
				crown = crowns.remove(i);
				gameCharacter.crown = true;
				int exp = (int)(300 * gameCharacter.level);
				gameCharacter.addExp(exp);
				rewardsToFill.add(new Reward(Reward.TYPE_EXP,exp));
				crownManager.delete(crown);
			}
		}
		return crowns;
	}

	private List<Key> checkKeys(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<Key> keys = keyManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		Key key;
		for(int i = keys.size() - 1;i >= 0;i--){
			key = keys.get(i);
			if(gameCharacter.inRange(key,gameCharacter.rangeTouch)){
				key = keys.remove(i);
				gameCharacter.keys.add(key.type);
				int exp = (int)(50 * gameCharacter.level);
				if(gameCharacter.crown){
					gameCharacter.crown = false;
					exp *= 4;
				}
				gameCharacter.addExp(exp);
				rewardsToFill.add(new Reward(Reward.TYPE_EXP,exp));
				keyManager.delete(key);
			}
		}
		return keys;
	}

	private List<Chest> checkChests(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<Chest> chests = chestManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		Chest chest;
		Integer key;
		for(int i = chests.size() - 1;i >= 0;i--){
			chest = chests.get(i);
			if(gameCharacter.inRange(chest,gameCharacter.rangeTouch)){

				boolean keyFound = false;
				// remove one key of the same type
				for(int j = gameCharacter.keys.size() - 1;j >= 0;j--){
					key = gameCharacter.keys.get(j);
					if(key == chest.type){
						gameCharacter.keys.remove((Integer)key);
						keyFound = true;
						break;
					}
				}

				if(keyFound){
					// open the chest
					chest = chests.remove(i);

					// get the reward content
					int exp = (int)(100 * gameCharacter.level);
					if(chest.reward != null){
						if(gameCharacter.crown){
							gameCharacter.crown = false;
							chest.reward.setValue(chest.reward.getValue() * 4);
						}
						if(chest.reward.getType() == Reward.TYPE_EXP){
							exp = chest.reward.getValue();
							rewardsToFill.add(chest.reward);
						}else if(chest.reward.getType() == Reward.TYPE_MONEY){
							gameCharacter.money += chest.reward.getValue();
							rewardsToFill.add(chest.reward);
							if(gameCharacter.crown){
								gameCharacter.crown = false;
								exp *= 4;
							}
						}
					}else{
						if(gameCharacter.crown){
							gameCharacter.crown = false;
							exp *= 4;
						}
						rewardsToFill.add(new Reward(Reward.TYPE_EXP,exp));
					}
					gameCharacter.addExp(exp);
					chestManager.delete(chest);
				}
			}
		}
		return chests;
	}

	private List<GameCharacter> checkGameCharacters(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<GameCharacter> gameCharacters = gameCharacterManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		return gameCharacters;
	}

	private List<Portal> checkPortals(GameCharacter gameCharacter,List<Reward> rewardsToFill){
		List<Portal> portals = portalManager.get(gameCharacter.latitude,gameCharacter.longitude,gameCharacter.rangeSee);
		Portal portal;
		for(int i = portals.size() - 1;i >= 0;i--){
			portal = portals.get(i);
			if(gameCharacter.inRange(portal,gameCharacter.rangeTouch)){
				int exp = (int)(9 * gameCharacter.level);
				if(portal.teamOwner == gameCharacter.team){
					if(portal.progressCapture != 1){
						portal.progressCapture = Math.min(1,portal.progressCapture + 0.075f);
						if(portal.progressCapture == 1){
							portal.teamOwner = gameCharacter.team;
							if(gameCharacter.crown){
								gameCharacter.crown = false;
								exp *= 4;
							}
							exp = exp * 10;
						}else{
							exp = (int)(exp * 0.075f);
						}
					}else{
						portal.progressCapture = Math.min(1,portal.progressCapture + 0.075f);
						exp = 0;
					}
				}else{
					portal.progressCapture = Math.max(0,portal.progressCapture - 0.075f);
					if(portal.progressCapture <= 0){
						portal.progressCapture = 0;
						portal.teamOwner = gameCharacter.team;
						if(gameCharacter.crown){
							gameCharacter.crown = false;
							exp *= 4;
						}
						exp = (int)(exp * 2f);
					}else{
						exp = (int)(exp * 0.075f);
					}
				}
				gameCharacter.addExp(exp);
				rewardsToFill.add(new Reward(Reward.TYPE_EXP,exp));
				portalManager.updateById(portal);
			}
		}
		return portals;
	}

	public void addStuffMap(){
		dropbDB();
		final double latUp = 42.0,latDown = 41.25,longLeft = 1.879,longRight = 2.34;
		final Random random = new Random();

		new Thread(new Runnable(){

			@Override
			public void run(){
				System.out.println("addStuffMap Coin");
				Coin coin = new Coin("",0,0,random.nextInt(20) + 1);
				for(int i = 0;i < 50000;i++){
					if(i % 250 == 0){
						System.out.println("addStuffMap Coin i:" + i);
					}
					coin.value = random.nextInt(20) + 1;
					coinManager.insertInside(latUp,longLeft,latDown,longRight,coin);
				}
			}
		}).start();

		new Thread(new Runnable(){

			@Override
			public void run(){
				System.out.println("addStuffMap Portal");
				Portal portal = new Portal("",0,0);
				for(int i = 0;i < 6000;i++){
					if(i % 250 == 0){
						System.out.println("addStuffMap Portal i:" + i);
					}
					portalManager.insertInside(latUp,longLeft,latDown,longRight,portal);
				}
			}
		}).start();

		new Thread(new Runnable(){

			@Override
			public void run(){
				System.out.println("addStuffMap Chest");
				Reward reward = new Reward(0,1);
				Chest chest = new Chest("",0,0,random.nextInt(6),reward);
				for(int i = 0;i < 7000;i++){
					if(i % 250 == 0){
						System.out.println("addStuffMap Chest i:" + i);
					}
					chest.type = random.nextInt(6);
					reward.setType(random.nextInt(2));
					reward.setValue(random.nextInt(500));
					chest.reward = reward;
					chestManager.insertInside(latUp,longLeft,latDown,longRight,chest);
				}
			}
		}).start();

		new Thread(new Runnable(){

			@Override
			public void run(){
				System.out.println("addStuffMap Key");
				Key key = new Key("",0,0,random.nextInt(6));
				for(int i = 0;i < 8000;i++){
					if(i % 250 == 0){
						System.out.println("addStuffMap Key i:" + i);
					}
					key.type = random.nextInt(6);
					keyManager.insertInside(latUp,longLeft,latDown,longRight,key);
				}
			}
		}).start();

		new Thread(new Runnable(){

			@Override
			public void run(){
				System.out.println("addStuffMap Crown");
				Crown crown = new Crown("",0,0);
				for(int i = 0;i < 4000;i++){
					if(i % 250 == 0){
						System.out.println("addStuffMap Crown i:" + i);
					}
					crownManager.insertInside(latUp,longLeft,latDown,longRight,crown);
				}
			}
		}).start();

	}

	// CHAT

	/*
	 * private MongoCollection<Document> getChatCollection(int type){
	 * MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
	 * MongoCollection<Document> collectionMap = null; if(type ==
	 * CHAT_TYPE_GENERAL){ collectionMap =
	 * database.getCollection(CHAT_GEN_COLLECTION_NAME); }else if(type ==
	 * CHAT_TYPE_TEAM0){ collectionMap =
	 * database.getCollection(CHAT_TEAM0_COLLECTION_NAME); }else if(type ==
	 * CHAT_TYPE_TEAM1){ collectionMap =
	 * database.getCollection(CHAT_TEAM1_COLLECTION_NAME); } return
	 * collectionMap; }
	 * 
	 * public void deleteAllChats(){ deleteChatGen(); deleteChatTeam0();
	 * deleteChatTeam1(); }
	 * 
	 * public void deleteChatGen(){ try{ MongoCollection<Document> temp =
	 * getChatCollection(CHAT_TYPE_GENERAL); temp.drop(); }catch(Exception e){
	 * e.printStackTrace(); } }
	 * 
	 * public void deleteChatTeam0(){ try{ MongoCollection<Document> temp =
	 * getChatCollection(CHAT_TYPE_TEAM0); temp.drop(); }catch(Exception e){
	 * e.printStackTrace(); } }
	 * 
	 * public void deleteChatTeam1(){ try{ MongoCollection<Document> temp =
	 * getChatCollection(CHAT_TYPE_TEAM1); temp.drop(); }catch(Exception e){
	 * e.printStackTrace(); } }
	 * 
	 * private List<ChatMessage> getChatMessages(MongoCollection<Document>
	 * collection,Bson bson){ List<ChatMessage> messages = new ArrayList<>();
	 * MongoCursor<Document> cursor = null; try{ if(bson == null){ cursor =
	 * collection.find(bson).iterator(); }else{ cursor =
	 * collection.find().iterator(); } Gson gson = new Gson();
	 * while(cursor.hasNext()){ Document doc = cursor.next();
	 * System.out.println("CHAT doc.toJson(): " + doc.toJson());
	 * messages.add(gson.fromJson(doc.toJson(),ChatMessage.class)); } }finally{
	 * try{ cursor.close(); }catch(Exception e){ e.printStackTrace(); } } return
	 * messages; }
	 * 
	 * private void deleteOldChatMessages(MongoCollection<Document> collection){
	 * List<ChatMessage> chatMessagesAsc =
	 * getChatMessages(collection,ascending("time")); int messagesToDeleteLeft =
	 * (int)(chatMessagesAsc.size() - MAX_CHAT_MESSAGES_STORED);
	 * if(messagesToDeleteLeft > MAX_CHAT_MESSAGES_STORED){ for(int i = 0;i <
	 * messagesToDeleteLeft;i++){
	 * deleteChatMessage(chatMessagesAsc.get(i).id,collection); } } }
	 * 
	 * private String deleteChatMessage(String
	 * chatMessageId,MongoCollection<Document> collection){ Document document =
	 * new Document(); document.put("id",chatMessageId); DeleteResult
	 * deleteResult = collection.deleteMany(document); return
	 * deleteResult.getDeletedCount() > 0 ? chatMessageId : null; }
	 * 
	 * public ChatMessage addChatMessage(String token,String user,String
	 * password,int type,String message){
	 * System.out.println("CHAT addChatMessage"); GameCharacter gameCharacter =
	 * getCurrentGameCharacter(token,user,password); if(gameCharacter != null){
	 * ChatMessage chatMessage = createChatMessage(gameCharacter,message);
	 * MongoCollection<Document> collection = null; collection =
	 * getChatCollection(type); System.out.println("CHAT first: " + collection);
	 * try{ collection.insertOne(chatMessage.getDocument());
	 * System.out.println("CHAT chatMessage: " + chatMessage);
	 * System.out.println("CHAT collection.count(): " + collection.count());
	 * deleteOldChatMessages(collection); return chatMessage; }catch(Exception
	 * e){ e.printStackTrace(); } } return null; }
	 * 
	 * private ChatMessage createChatMessage(GameCharacter gameCharacter,String
	 * message){ ChatMessage chatMessage = new ChatMessage(); chatMessage.time =
	 * "" + System.currentTimeMillis(); chatMessage.gameCharacterId =
	 * gameCharacter.id; chatMessage.gameCharacterName = gameCharacter.name;
	 * chatMessage.message = message; chatMessage.id = gameCharacter.userOwner +
	 * gameCharacter.id + chatMessage.time; return chatMessage; }
	 * 
	 * public List<ChatMessage> getChatMessages(String token,String user,String
	 * password,int chatType){ Account account =
	 * getAccount(token,user,password); if(account != null){ if(chatType ==
	 * CHAT_TYPE_GENERAL){ return
	 * getChatMessages(getChatCollection(CHAT_TYPE_GENERAL),ascending("time"));
	 * }else if(chatType == CHAT_TYPE_TEAM0){ return
	 * getChatMessages(getChatCollection(CHAT_TYPE_TEAM0),ascending("time"));
	 * }else if(chatType == CHAT_TYPE_TEAM1){ return
	 * getChatMessages(getChatCollection(CHAT_TYPE_TEAM1),ascending("time")); }
	 * } return null; }
	 */

	// ////////

}