package com.vogella.jersey.first.util;

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

import org.bson.Document;

import com.google.gson.Gson;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.vogella.jersey.first.model.Coin;
import com.vogella.jersey.first.model.GameCharacter;
import com.vogella.jersey.first.model.GameObjectDO;
import com.vogella.jersey.first.model.Account;
import com.vogella.jersey.first.model.PlayerInfoDO;
import com.vogella.jersey.first.model.MapData;
import com.vogella.jersey.first.model.Portal;

import static com.mongodb.client.model.Filters.*;

public class GameMongoDBUtils {

	private static final int MIN_CHARACTER_SLOT = 0;
	private static final int MAX_CHARACTER_SLOT = 1;
	private static final long MIN_EXP_LEVEL = 1;
	private static final long MAX_EXP_LEVEL = 1000;
	private static final long MIN_MONEY = 0;
	private static final long MIN_EXP_PROGRESS_GENERAL = 0;
	private static final String DATABASE_NAME = "geogame";
	private static final String ACCOUNT_COLLECTION_NAME = "accounts";
	private static final String GAME_CHARACTER_COLLECTION_NAME = "gameCharacters";
	private static final String COINS_COLLECTION_NAME = "coins";
	private static final String PORTALS_COLLECTION_NAME = "portals";
	private static final double EARTH_RADIUS_1 = 6371000;
	private static final double EARTH_RADIUS_2 = 6378137;
	private static final double LEVEL_PROGRESS_GAIN_COIN = 0.075;

	private MongoClient mongoClient;

	/*
	 * public List<Account> getAllPlayers(MongoCollection<Document> collection)
	 * { MongoCursor<Document> cursor = collection.find().iterator();
	 * List<Account> players = new ArrayList<Account>(); try { Gson gson = new
	 * Gson(); while (cursor.hasNext()) { Document doc = cursor.next();
	 * players.add(gson.fromJson(doc.toJson(), Account.class));
	 * System.out.println(doc.toJson()); } } finally { cursor.close(); } return
	 * players; }
	 * 
	 * public Account getPlayerAccount(MongoCollection<Document> collection,
	 * String user) { Document doc = collection.find(eq("user", user)).first();
	 * if (doc != null) { Gson gson = new Gson(); return
	 * gson.fromJson(doc.toJson(), Account.class); } return null; }
	 * 
	 * public Account getPlayerAccount(MongoCollection<Document> collection,
	 * String user, String password) { Document doc = collection.find(
	 * and(eq("user", user), eq("password", password))).first(); if (doc !=
	 * null) { Gson gson = new Gson(); return gson.fromJson(doc.toJson(),
	 * Account.class); } return null; }
	 * 
	 * public boolean insertOrUpdate(MongoCollection<Document> collection,
	 * Account playerAccountInfoDO) { Document document = new Document();
	 * document.put("user", playerAccountInfoDO.user); UpdateResult updateResult
	 * = collection.updateOne( eq("user", playerAccountInfoDO.user), new
	 * Document("$set", playerAccountInfoDO.getDocument())); if
	 * (updateResult.getMatchedCount() == 0) {
	 * collection.insertOne(playerAccountInfoDO.getDocument()); return true; }
	 * return false; }
	 * 
	 * public boolean insertOrUpdate(MongoCollection<Document> collection,
	 * PlayerInfoDO playerInfoDO) { Document document = new Document();
	 * document.put("id", playerInfoDO.id); UpdateResult updateResult =
	 * collection.updateOne( eq("id", playerInfoDO.id), new Document("$set",
	 * playerInfoDO.getDocument())); if (updateResult.getMatchedCount() == 0) {
	 * collection.insertOne(playerInfoDO.getDocument()); return true; } return
	 * false; }
	 * 
	 * public boolean insertOrUpdate(MongoCollection<Document> collection,
	 * GameObjectDO gameObjectDO) { Document document = new Document();
	 * document.put("id", gameObjectDO.id); UpdateResult updateResult =
	 * collection.updateOne( eq("id", gameObjectDO.id), new Document("$set",
	 * gameObjectDO.getDocument())); if (updateResult.getMatchedCount() == 0) {
	 * collection.insertOne(gameObjectDO.getDocument()); return true; } return
	 * false; }
	 * 
	 * public long deletePlayersByUser(MongoCollection<Document> collection,
	 * String userPlayerAccount) { Document document = new Document();
	 * document.put("user", userPlayerAccount); DeleteResult deleteResult =
	 * collection.deleteMany(document); return deleteResult.getDeletedCount(); }
	 * 
	 * public long deletePlayersByPassword(MongoCollection<Document> collection,
	 * String passwordPlayerAccount) { Document document = new Document();
	 * document.put("password", passwordPlayerAccount); DeleteResult
	 * deleteResult = collection.deleteMany(document); return
	 * deleteResult.getDeletedCount(); }
	 * 
	 * public long deletePlayersByEmail(MongoCollection<Document> collection,
	 * String emailPlayerAccount) { Document document = new Document();
	 * document.put("email", emailPlayerAccount); DeleteResult deleteResult =
	 * collection.deleteMany(document); return deleteResult.getDeletedCount(); }
	 * 
	 * public void drop(MongoCollection<Document> collection) {
	 * collection.drop(); }
	 * 
	 * public MongoCollection<Document> getMapCollection() { MongoDatabase
	 * database = mongoClient.getDatabase("geogame"); MongoCollection<Document>
	 * collectionMap = database.getCollection("map"); return collectionMap; }
	 * 
	 * public MongoCollection<Document> getPlayersAccountCollection() {
	 * MongoDatabase database = mongoClient.getDatabase("geogame");
	 * MongoCollection<Document> collectionMap = database
	 * .getCollection("playerAccounts"); return collectionMap; }
	 * 
	 * public MongoCollection<Document> getPlayersInfoCollection() {
	 * MongoDatabase database = mongoClient.getDatabase("geogame");
	 * MongoCollection<Document> collectionMap = database
	 * .getCollection("playerInfos"); return collectionMap; }
	 * 
	 * public MongoCollection<Document> getCoinsCollection() { MongoDatabase
	 * database = mongoClient.getDatabase("geogame"); MongoCollection<Document>
	 * collectionMap = database .getCollection("coins"); return collectionMap; }
	 * 
	 * public MongoCollection<Document> getPortalsCollection() { MongoDatabase
	 * database = mongoClient.getDatabase("geogame"); MongoCollection<Document>
	 * collectionMap = database .getCollection("portals"); return collectionMap;
	 * }
	 * 
	 * public void dropbDB() { MongoDatabase database =
	 * mongoClient.getDatabase("geogame"); database.drop(); }
	 * 
	 * public double distFrom(double lat1, double lng1, double lat2, double
	 * lng2) { double earthRadius = 6371000; // 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; }
	 */

	// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// TODO

	public GameMongoDBUtils() {
		mongoClient = new MongoClient("localhost", 20206);
	}

	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 Account login(String user, String password) {
		System.out.println("login START user:" + user + ", password:"
				+ password);
		Account accountToUpdate = getAccount(user, password);
		if (accountToUpdate != null) {
			accountToUpdate.token = "" + System.currentTimeMillis();
			Account updatedAccount = updateAccountByUser(accountToUpdate);
			if (updatedAccount != null) {
				System.out.println("login END user:" + user + ", password:"
						+ password + ", OK");
				return accountToUpdate;
			} else {
				System.out.println("login END user:" + user + ", password:"
						+ password + ", error updating account");
			}
		} else {
			System.out.println("login END user:" + user + ", password:"
					+ password + ", account not found");
		}
		System.out.println("login END user:" + user + ", password:" + password
				+ ", ERROR");
		return null;
	}

	public boolean logout(String token, String user, String password) {
		System.out.println("logout START token:" + token + ", user:" + user
				+ ", password:" + password + ", ");
		Account accountToLogOut = getAccount(token, user, password);
		if (accountToLogOut != null) {
			if (!isEmptyNull(accountToLogOut.token)) {
				accountToLogOut.token = null;
				Account updatedAccount = updateAccountByUser(accountToLogOut);
				if (updatedAccount != null) {
					System.out.println("logout END token:" + token + ", user:"
							+ user + ", password:" + password
							+ ", accountToLogOut:" + accountToLogOut + " OK");
					return true;
				} else {
					System.out.println("logout END token:" + token + ", user:"
							+ user + ", password:" + password
							+ ", failed to update account");
				}
			} else {
				System.out.println("logout END token:" + token + ", user:"
						+ user + ", password:" + password
						+ ", account logged out already");
			}
		} else {
			System.out.println("logout END token:" + token + ", user:" + user
					+ ", password:" + password + ", account doesn't exist");
		}
		return false;
	}

	private boolean tokensEqual(String token1, String token2) {
		if (token1 != null && token1.isEmpty()) {
			token1 = null;
		}
		if (token2 != null && token2.isEmpty()) {
			token2 = null;
		}
		if (token1 == null && token2 == null) {
			return true;
		} else if (token1 != null && token2 != null && token1.equals(token2)) {
			return true;
		}
		return false;
	}

	public Account retrieveAccountData(String token, String user,
			String password) {
		Account account = getAccount(token, user, password);
		return account;

	}

	public Account register(String user, String password, String email) {
		System.out.println("register START user:" + user + ", password:"
				+ password + ", email:" + email);
		if (!isEmpty(user) && !isEmpty(password) && !isEmpty(email)) {
			Account accountDB = getAccount(user);
			if (accountDB == null) {
				Account accountToInsert = new Account();
				accountToInsert.user = user;
				accountToInsert.password = password;
				accountToInsert.email = email;
				accountToInsert.gameCharacterSlot1 = null;
				accountToInsert.gameCharacterSlot2 = null;
				accountToInsert.currentGameCharacterSlot = -1;
				accountToInsert.token = null;
				Account insertedAccount = insertAccount(accountToInsert);
				if (insertedAccount != null) {
					System.out.println("register END user:" + user
							+ ", password:" + password + ", email:" + email
							+ ", OK");
					return accountToInsert;
				} else {
					System.out.println("register END user:" + user
							+ ", password:" + password + ", email:" + email
							+ ", failed to insert user to database");
				}
			} else {
				System.out.println("register END user:" + user + ", password:"
						+ password + ", email:" + email
						+ ", account already exists");
			}
		} else {
			System.out.println("register END user:" + user + ", password:"
					+ password + ", email:" + email
					+ ", wrong user, password or email");
		}
		System.out.println("register END user:" + user + ", password:"
				+ password + ", email:" + email + ", ERROR");
		return null;
	}

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

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

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

	public Account updateEmail(String token, String user, String password,
			String newEmail) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			Account accountToUpdate = new Account();
			accountToUpdate.token = token;
			accountToUpdate.user = user;
			accountToUpdate.password = password;
			accountToUpdate.email = newEmail;
			accountToUpdate.currentGameCharacterSlot = account.currentGameCharacterSlot;
			accountToUpdate.gameCharacterSlot1 = account.gameCharacterSlot1;
			accountToUpdate.gameCharacterSlot2 = account.gameCharacterSlot2;
			Account updatedAccount = updateAccountByUser(accountToUpdate);
			if (updatedAccount != null) {
				return updatedAccount;
			}
		}
		return null;
	}

	public GameCharacter getCurrentGameCharacter(String token, String user,
			String password) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			if (account.currentGameCharacterSlot >= MIN_CHARACTER_SLOT
					&& account.currentGameCharacterSlot <= MAX_CHARACTER_SLOT) {
				String gameCharacterId = null;
				if (account.currentGameCharacterSlot == MIN_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot1;
				} else if (account.currentGameCharacterSlot == MAX_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot2;
				}
				if (gameCharacterId != null && !gameCharacterId.isEmpty()) {
					GameCharacter gameCharacter = getGameCharacter(gameCharacterId);
					return gameCharacter;
				}
			}
		}
		return null;
	}

	public GameCharacter getGameCharacter(String gameCharacterId) {
		GameCharacter gameCharacter = null;
		MongoCollection<Document> collection = getGameCharacterCollection();
		try {
			Document doc = collection.find(eq("id", gameCharacterId)).first();
			if (doc != null) {
				Gson gson = new Gson();
				gameCharacter = gson
						.fromJson(doc.toJson(), GameCharacter.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return gameCharacter;
	}

	private GameCharacter getGameCharacter(String gameCharacterId,
			String gameCharacterUserOwner) {
		GameCharacter gameCharacter = null;
		MongoCollection<Document> collection = getGameCharacterCollection();
		try {
			Document doc = collection.find(
					and(eq("id", gameCharacterId),
							eq("userOwner", gameCharacterUserOwner))).first();
			if (doc != null) {
				Gson gson = new Gson();
				gameCharacter = gson
						.fromJson(doc.toJson(), GameCharacter.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return gameCharacter;
	}

	public GameCharacter getGameCharacter(String user, int gameCharacterSlot) {
		Account account = getAccount(user);
		if (account != null) {
			int slot = -1;
			String gameCharacterId = "";
			if (gameCharacterSlot == MIN_CHARACTER_SLOT) {
				slot = MIN_CHARACTER_SLOT;
				gameCharacterId = account.gameCharacterSlot1;
			} else if (gameCharacterSlot == MAX_CHARACTER_SLOT) {
				slot = MAX_CHARACTER_SLOT;
				gameCharacterId = account.gameCharacterSlot2;
			}
			System.out
					.println("##################################################");
			System.out.println("slot:" + slot + "#");
			System.out.println("gameCharacterId:" + gameCharacterId + "#");
			if (slot >= MIN_CHARACTER_SLOT && slot <= MAX_CHARACTER_SLOT
					&& !isEmptyNull(gameCharacterId)) {
				GameCharacter gameCharacter = getGameCharacter(gameCharacterId);
				return gameCharacter;
			}
		}
		return null;
	}

	public List<GameCharacter> getGameCharacters(String user) {
		List<GameCharacter> gameCharacters = new ArrayList<>();
		Account account = getAccount(user);
		if (account != null) {
			String gameCharacterId = null;
			GameCharacter gameCharacter = null;
			for (int i = MIN_CHARACTER_SLOT; i < MAX_CHARACTER_SLOT + 1; i++) {
				if (i == MIN_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot1;
				} else if (i == MAX_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot2;
				}
				if (gameCharacterId != null && !gameCharacterId.isEmpty()) {
					gameCharacter = getGameCharacter(gameCharacterId);
					gameCharacters.add(gameCharacter);
				}
			}
		}
		return gameCharacters;
	}

	public GameCharacter selectGameCharacter(String token, String user,
			String password, int gameCharacterSlot) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			if (gameCharacterSlot >= MIN_CHARACTER_SLOT
					&& gameCharacterSlot <= MAX_CHARACTER_SLOT) {
				String gameCharacterId = null;
				if (gameCharacterSlot == MIN_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot1;
				} else if (gameCharacterSlot == MAX_CHARACTER_SLOT) {
					gameCharacterId = account.gameCharacterSlot2;
				}
				if (gameCharacterId != null && !gameCharacterId.isEmpty()) {
					GameCharacter gameCharacter = getGameCharacter(gameCharacterId);
					if (gameCharacter != null) {
						account.currentGameCharacterSlot = gameCharacterSlot;
						Account updatedAccount = updateAccountByUser(account);
						if (updatedAccount != null) {
							return gameCharacter;
						}
					}
				}
			}
		}
		return null;
	}

	public GameCharacter addGameCharacter(String token, String user,
			String password, int accountSlot, int gameCharacterTeam,
			int gameCharacterClass, String gameCharacterName,
			double gameCharacterLatitude, double gameCharacterLongitude) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			boolean found = false;
			String gameCharacterId = null; // TODO
			if (accountSlot == MIN_CHARACTER_SLOT) {
				gameCharacterId = account.gameCharacterSlot1;
				found = true;
			} else if (accountSlot == MAX_CHARACTER_SLOT) {
				gameCharacterId = account.gameCharacterSlot2;
				found = true;
			}
			if (isEmptyNull(gameCharacterId) && found
					&& !isEmptyNull(gameCharacterName)
			/*
			 * && !isEmptyNull(gameCharacterLatitude) &&
			 * !isEmptyNull(gameCharacterLongitude)
			 */) {
				GameCharacter gameCharacter = createGameCharacter(user
						+ accountSlot, user, gameCharacterTeam,
						gameCharacterClass, gameCharacterName,
						gameCharacterLatitude, gameCharacterLongitude);
				GameCharacter inserted = insertGameCharacter(gameCharacter);
				if (inserted != null) {
					if (accountSlot == MIN_CHARACTER_SLOT) {
						account.gameCharacterSlot1 = gameCharacter.id;
					} else if (accountSlot == MAX_CHARACTER_SLOT) {
						account.gameCharacterSlot2 = gameCharacter.id;
					}
					Account accountUpdated = updateAccountByUser(account);
					if (accountUpdated != null) {
						return gameCharacter;
					} else {
						deleteGameCharacter(token, user, password,
								gameCharacter.id);
					}
				}
			} else {
				// exists another gameCharacter in the slot
			}
		} else {
			// account not found
		}
		return null;
	}

	public GameCharacter updateGameCharacterName(String token, String user,
			String password, String characterId, String gameCharacterName) {
		if (isEmptyNull(gameCharacterName)) {
			return null;
		}
		Account account = getAccount(token, user, password);
		if (account != null) {
			GameCharacter gameCharacter = getGameCharacter(characterId);
			if (gameCharacter != null
					&& account.user.equals(gameCharacter.userOwner)) {
				int gameCharacterSlot = -1;
				if (!isEmptyNull(account.gameCharacterSlot1)
						&& account.gameCharacterSlot1.equals(gameCharacter.id)) {
					gameCharacterSlot = MIN_CHARACTER_SLOT;
				} else if (!isEmptyNull(account.gameCharacterSlot2)
						&& account.gameCharacterSlot2.equals(gameCharacter.id)) {
					gameCharacterSlot = MAX_CHARACTER_SLOT;
				}
				if (gameCharacterSlot >= MIN_CHARACTER_SLOT
						&& gameCharacterSlot <= MAX_CHARACTER_SLOT) {
					gameCharacter.gameCharacterName = gameCharacterName;
					updateGameCharacterById(gameCharacter);
					return gameCharacter;
				}
			}
		}
		return null;
	}

	public GameCharacter updateGameCharacterMoney(String token, String user,
			String password, String characterId, long gameCharacterMoney) {
		if (gameCharacterMoney < MIN_MONEY) {
			return null;
		}
		Account account = getAccount(token, user, password);
		if (account != null) {
			GameCharacter gameCharacter = getGameCharacter(characterId);
			if (gameCharacter != null
					&& account.user.equals(gameCharacter.userOwner)) {
				int gameCharacterSlot = -1;
				if (!isEmptyNull(account.gameCharacterSlot1)
						&& account.gameCharacterSlot1.equals(gameCharacter.id)) {
					gameCharacterSlot = MIN_CHARACTER_SLOT;
				} else if (!isEmptyNull(account.gameCharacterSlot2)
						&& account.gameCharacterSlot2.equals(gameCharacter.id)) {
					gameCharacterSlot = MAX_CHARACTER_SLOT;
				}
				if (gameCharacterSlot >= MIN_CHARACTER_SLOT
						&& gameCharacterSlot <= MAX_CHARACTER_SLOT) {
					gameCharacter.money = gameCharacterMoney;
					updateGameCharacterById(gameCharacter);
					return gameCharacter;
				}
			}
		}
		return null;
	}

	public GameCharacter updateGameCharacterExpLevel(String token, String user,
			String password, String characterId, long gameCharacterExpLevel) {
		if (gameCharacterExpLevel < MIN_EXP_LEVEL
				|| gameCharacterExpLevel > MAX_EXP_LEVEL) {
			return null;
		}
		Account account = getAccount(token, user, password);
		if (account != null) {
			GameCharacter gameCharacter = getGameCharacter(characterId);
			if (gameCharacter != null
					&& account.user.equals(gameCharacter.userOwner)) {
				int gameCharacterSlot = -1;
				if (!isEmptyNull(account.gameCharacterSlot1)
						&& account.gameCharacterSlot1.equals(gameCharacter.id)) {
					gameCharacterSlot = MIN_CHARACTER_SLOT;
				} else if (!isEmptyNull(account.gameCharacterSlot2)
						&& account.gameCharacterSlot2.equals(gameCharacter.id)) {
					gameCharacterSlot = MAX_CHARACTER_SLOT;
				}
				if (gameCharacterSlot >= MIN_CHARACTER_SLOT
						&& gameCharacterSlot <= MAX_CHARACTER_SLOT) {
					gameCharacter.expLevel = gameCharacterExpLevel;
					gameCharacter.expProgress = MIN_EXP_PROGRESS_GENERAL; // TODO
					updateGameCharacterById(gameCharacter);
					return gameCharacter;
				}
			}
		}
		return null;
	}

	private GameCharacter createGameCharacter(String id, String userOwner,
			int gameCharacterTeam, int gameCharacterClass,
			String gameCharacterName, double gameCharacterLatitude,
			double gameCharacterLongitude) {
		GameCharacter gameCharacter = new GameCharacter();
		gameCharacter.id = id;
		gameCharacter.userOwner = userOwner;
		gameCharacter.gameCharacterTeam = gameCharacterTeam;
		gameCharacter.gameCharacterClass = gameCharacterClass;
		gameCharacter.money = MIN_MONEY;
		gameCharacter.expLevel = MIN_EXP_LEVEL;
		gameCharacter.expProgress = MIN_EXP_PROGRESS_GENERAL;
		gameCharacter.gameCharacterName = gameCharacterName;
		gameCharacter.latitude = gameCharacterLatitude;
		gameCharacter.longitude = gameCharacterLongitude;
		return gameCharacter;
	}

	public boolean deleteGameCharacter(String token, String user,
			String password, String gameCharacterId) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			System.out.println("1");
			GameCharacter gameCharacter = getGameCharacter(gameCharacterId);
			if (gameCharacter != null && gameCharacter.userOwner.equals(user)) {
				System.out.println("2");
				int selection = account.currentGameCharacterSlot;
				int slot = -1;
				if (!isEmptyNull(account.gameCharacterSlot1)
						&& account.gameCharacterSlot1.equals(gameCharacter.id)) {
					slot = MIN_CHARACTER_SLOT;
					if (selection == slot) {
						account.currentGameCharacterSlot = -1;
					}
					account.gameCharacterSlot1 = "";
				} else if (!isEmptyNull(account.gameCharacterSlot2)
						&& account.gameCharacterSlot2.equals(gameCharacter.id)) {
					slot = MAX_CHARACTER_SLOT;
					if (selection == slot) {
						account.currentGameCharacterSlot = -1;
					}
					account.gameCharacterSlot2 = "";
				}
				if (slot >= MIN_CHARACTER_SLOT && slot <= MAX_CHARACTER_SLOT) {
					System.out.println("3");
					account = updateAccountByUser(account);
					if (account != null) {
						System.out.println("4");
						MongoCollection<Document> collection = getGameCharacterCollection();
						try {
							Document document = new Document();
							document.put("id", gameCharacter.id);
							document.put("userOwner", gameCharacter.userOwner);
							DeleteResult deleteResult = collection
									.deleteMany(document);
							if (deleteResult.getDeletedCount() > 0) {
								System.out.println("5");
								if (isEmptyNull(account.gameCharacterSlot1)
										&& account.gameCharacterSlot1
												.equals(gameCharacter.id)) {
									account.gameCharacterSlot1 = gameCharacter.id;
									if (selection == slot) {
										account.currentGameCharacterSlot = selection;
									}
								} else if (isEmptyNull(account.gameCharacterSlot2)
										&& account.gameCharacterSlot2
												.equals(gameCharacter.id)) {
									account.gameCharacterSlot2 = gameCharacter.id;
									if (selection == slot) {
										account.currentGameCharacterSlot = selection;
									}
								}
								updateAccountByUser(account);
								return true;
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
		return false;
	}

	public boolean deleteGameCharacter(String token, String user,
			String password, int gameCharacterSlot) {
		Account account = getAccount(token, user, password);
		System.out.println("deleteGameCharacter:account" + account + "#");
		if (account != null) {
			GameCharacter gameCharacter = getGameCharacter(user,
					gameCharacterSlot);
			System.out.println("deleteGameCharacter:" + gameCharacter + "#");
			if (gameCharacter != null) {
				return deleteGameCharacter(token, user, password,
						gameCharacter.id);
			}
		}
		return false;
	}

	public Account getAccount(String token, String user, String password) {
		if (token != null && token.isEmpty()) {
			token = null;
		}
		Account account = null;
		MongoCollection<Document> collection = getAccountCollection();
		try {
			Document doc = collection.find(
					and(eq("user", user), eq("password", password))).first();
			if (doc != null) {
				Gson gson = new Gson();
				account = gson.fromJson(doc.toJson(), Account.class);
				if (!tokensEqual(account.token, token)) {
					account = null;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return account;
	}

	public Account getAccount(String user, String password) {
		Account account = null;
		MongoCollection<Document> collection = getAccountCollection();
		try {
			Document doc = collection.find(
					and(eq("user", user), eq("password", password))).first();
			if (doc != null) {
				Gson gson = new Gson();
				account = gson.fromJson(doc.toJson(), Account.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return account;
	}

	public Account getAccount(String user) {
		Account account = null;
		MongoCollection<Document> collection = getAccountCollection();
		try {
			Document doc = collection.find(eq("user", user)).first();
			if (doc != null) {
				Gson gson = new Gson();
				account = gson.fromJson(doc.toJson(), Account.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return account;
	}

	public List<Account> getAccounts() {
		List<Account> accounts = new ArrayList<Account>();
		MongoCollection<Document> collection = getAccountCollection();
		try {
			MongoCursor<Document> cursor = collection.find().iterator();
			try {
				Gson gson = new Gson();
				while (cursor.hasNext()) {
					Document doc = cursor.next();
					accounts.add(gson.fromJson(doc.toJson(), Account.class));
				}
			} finally {
				try {
					cursor.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return accounts;
	}

	public List<GameCharacter> getGameCharacters() {
		List<GameCharacter> gameCharacters = new ArrayList<>();
		MongoCollection<Document> collection = getGameCharacterCollection();
		try {
			MongoCursor<Document> cursor = collection.find().iterator();
			try {
				Gson gson = new Gson();
				while (cursor.hasNext()) {
					Document doc = cursor.next();
					gameCharacters.add(gson.fromJson(doc.toJson(),
							GameCharacter.class));
				}
			} finally {
				try {
					cursor.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return gameCharacters;
	}

	public MongoCollection<Document> getAccountCollection() {
		MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
		MongoCollection<Document> collectionMap = database
				.getCollection(ACCOUNT_COLLECTION_NAME);
		return collectionMap;
	}

	public MongoCollection<Document> getGameCharacterCollection() {
		MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
		MongoCollection<Document> collectionMap = database
				.getCollection(GAME_CHARACTER_COLLECTION_NAME);
		return collectionMap;
	}

	public MongoCollection<Document> getCoinsCollection() {
		MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
		MongoCollection<Document> collectionMap = database
				.getCollection(COINS_COLLECTION_NAME);
		return collectionMap;
	}

	public MongoCollection<Document> getPortalsCollection() {
		MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME);
		MongoCollection<Document> collectionMap = database
				.getCollection(PORTALS_COLLECTION_NAME);
		return collectionMap;
	}

	private GameCharacter insertGameCharacter(
			GameCharacter gameCharacterToInsert) {
		if (isEmptyNull(gameCharacterToInsert.id)
				&& isEmptyNull(gameCharacterToInsert.userOwner)) {
			return null;
		}
		GameCharacter gameCharacterDB = getGameCharacter(
				gameCharacterToInsert.id, gameCharacterToInsert.userOwner);
		GameCharacter insertedGameCharacter = null;
		gameCharacterToInsert.setNullToEmptyStrings();
		if (gameCharacterDB == null) {
			MongoCollection<Document> collection = getGameCharacterCollection();
			try {
				collection.insertOne(gameCharacterToInsert.getDocument());
				insertedGameCharacter = gameCharacterToInsert;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return insertedGameCharacter;
	}

	private Account updateAccountByUser(Account accountToUpdateByUser) {
		Account updatedAccount = null;
		MongoCollection<Document> collection = getAccountCollection();
		accountToUpdateByUser.setNullToEmptyStrings();
		try {
			UpdateResult updateResult = collection.updateOne(
					eq("user", accountToUpdateByUser.user), new Document(
							"$set", accountToUpdateByUser.getDocument()));
			if (updateResult.getMatchedCount() > 0) {
				updatedAccount = accountToUpdateByUser;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return updatedAccount;
	}

	private GameCharacter updateGameCharacterById(
			GameCharacter gameCharacterToUpdate) {
		GameCharacter updatedGameCharacter = null;
		MongoCollection<Document> collection = getGameCharacterCollection();
		gameCharacterToUpdate.setNullToEmptyStrings();
		try {
			UpdateResult updateResult = collection.updateOne(
					eq("id", gameCharacterToUpdate.id), new Document("$set",
							gameCharacterToUpdate.getDocument()));
			if (updateResult.getMatchedCount() > 0) {
				updatedGameCharacter = gameCharacterToUpdate;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return updatedGameCharacter;
	}

	private Account insertAccount(Account accountToInsert) {
		System.out.println("insertAccount START accountToInsert:"
				+ accountToInsert + "");
		Account accountDB = getAccount(accountToInsert.user);
		Account insertedAccount = null;
		accountToInsert.setNullToEmptyStrings();
		if (accountDB == null) {
			MongoCollection<Document> collection = getAccountCollection();
			try {
				collection.insertOne(accountToInsert.getDocument());
				insertedAccount = accountToInsert;
				System.out.println("insertAccount END accountToInsert:"
						+ accountToInsert + ", OK");
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println("insertAccount END accountToInsert:"
						+ accountToInsert
						+ ", failed to insert the user into the database");
			}
		} else {
			System.out.println("insertAccount END accountToInsert:"
					+ accountToInsert + ", account already exists");
		}
		return insertedAccount;
	}

	private boolean checkAccount(Account account, String token, String user,
			String password) {
		if (checkUser(account, user) && checkPassword(account, password)
				&& checkToken(account, token)) {
			return true;
		}
		return false;
	}

	private boolean checkAccount(Account account, String user, String password) {
		if (checkUser(account, user) && checkPassword(account, password)) {
			return true;
		}
		return false;
	}

	private boolean checkUser(Account account, String user) {
		if (account != null && account.user != null && user != null
				&& account.user.equals(user)) {
			return true;
		}
		return false;
	}

	private boolean checkPassword(Account account, String password) {
		if (account != null && account.password != null && password != null
				&& account.password.equals(password)) {
			return true;
		}
		return false;
	}

	private boolean checkToken(Account account, String token) {
		if ((account.token != null && token != null && account.token
				.equals(token))
				|| ((account.token == null || account.token.isEmpty()) && (token == null || token
						.isEmpty()))) {
			return true;
		}
		return false;
	}

	public Coin addCoinOrCoinValue(double latitude, double longitude, int value) {
		Coin coin = null;
		if (value > 0) {
			MongoCollection<Document> collection = getCoinsCollection();
			String id = "" + System.currentTimeMillis();
			Coin coinDB = getCoin(id);
			if (coinDB != null) {
				coinDB.value += value;
				coin = updateCoinById(coinDB);
				System.out.println("2");
			} else {
				try {
					Coin coinTemp = createCoin(id, latitude, longitude, value);
					collection.insertOne(coinTemp.getDocument());
					coin = coinTemp;
					System.out.println("3");
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("4");
			}
		}
		System.out.println("5");
		return coin;
	}

	public Portal addPortal(double latitude, double longitude, int teamOwner,
			double progressCaptured) {
		Portal portal = null;
		MongoCollection<Document> collection = getPortalsCollection();
		String id;
		while (portal == null) {
			id = "" + System.currentTimeMillis();
			Portal portalDB = getPortal(id);
			if (portalDB == null) {
				try {
					portal = createPortal(id, latitude, longitude, teamOwner,
							progressCaptured);
					collection.insertOne(portal.getDocument());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return portal;
	}

	public Coin addCoinOrCoinValueTest(int value, long numCoinsToAdd,
			double fromLat, double toLat, double fromLng, double toLng) {
		MongoCollection<Document> collection = getCoinsCollection();
		Document doc;
		Coin coinTemp;
		Gson gson = new Gson();
		String id;
		Random random = new Random(System.currentTimeMillis());

		for (int i = 0; i < numCoinsToAdd; i++) {

			id = "" + System.currentTimeMillis();
			doc = null;
			try {
				doc = collection.find(eq("id", id)).first();
			} catch (Exception e) {
			}
			if (doc == null) {
				try {
					coinTemp = createCoin(id, random.nextDouble()
							* (toLat - fromLat) + fromLat, random.nextDouble()
							* (toLng - fromLng) + fromLng, value);
					collection.insertOne(coinTemp.getDocument());
				} catch (Exception e) {
				}
			} else {
				i--;
				/*
				 * coinTemp = gson.fromJson(doc.toJson(), Coin.class);
				 * coinTemp.value += value; updateCoinById(coinTemp);
				 */
			}
		}
		System.out.println("end");
		return null;
	}

	public Coin addPortalsTest(double progressCaptured, int teamOwner,
			long numPortalsToAdd, double fromLat, double toLat, double fromLng,
			double toLng) {
		MongoCollection<Document> collection = getPortalsCollection();
		Document doc;
		Portal portal;
		Gson gson = new Gson();
		String id;
		Random random = new Random(System.currentTimeMillis());

		for (int i = 0; i < numPortalsToAdd; i++) {

			id = "" + System.currentTimeMillis();
			doc = null;
			try {
				doc = collection.find(eq("id", id)).first();
			} catch (Exception e) {
			}
			if (doc == null) {
				try {
					portal = createPortal(id, random.nextDouble()
							* (toLat - fromLat) + fromLat, random.nextDouble()
							* (toLng - fromLng) + fromLng, teamOwner,
							progressCaptured);
					collection.insertOne(portal.getDocument());
				} catch (Exception e) {
				}
			} else {
				i--;
				/*
				 * coinTemp = gson.fromJson(doc.toJson(), Coin.class);
				 * coinTemp.value += value; updateCoinById(coinTemp);
				 */
			}
		}
		System.out.println("end");
		return null;
	}

	private Coin updateCoinById(Coin coinToUpdate) {
		Coin updatedCoin = null;
		MongoCollection<Document> collection = getCoinsCollection();
		coinToUpdate.setNullToEmptyStrings();
		try {
			UpdateResult updateResult = collection.updateOne(
					eq("id", coinToUpdate.id), new Document("$set",
							coinToUpdate.getDocument()));
			if (updateResult.getMatchedCount() > 0) {
				updatedCoin = coinToUpdate;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return updatedCoin;
	}

	private Portal updatePortalById(Portal portalToUpdate) {
		Portal updatedPortal = null;
		MongoCollection<Document> collection = getCoinsCollection();
		portalToUpdate.setNullToEmptyStrings();
		try {
			UpdateResult updateResult = collection.updateOne(
					eq("id", portalToUpdate.id), new Document("$set",
							portalToUpdate.getDocument()));
			if (updateResult.getMatchedCount() > 0) {
				updatedPortal = portalToUpdate;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return updatedPortal;
	}

	public Coin getCoin(String id) {
		Coin coin = null;
		MongoCollection<Document> collection = getCoinsCollection();
		try {
			Document doc = collection.find(eq("id", id)).first();
			if (doc != null) {
				Gson gson = new Gson();
				coin = gson.fromJson(doc.toJson(), Coin.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return coin;
	}

	public Portal getPortal(String id) {
		Portal portal = null;
		MongoCollection<Document> collection = getPortalsCollection();
		try {
			Document doc = collection.find(eq("id", id)).first();
			if (doc != null) {
				Gson gson = new Gson();
				portal = gson.fromJson(doc.toJson(), Portal.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return portal;
	}

	private Coin createCoin(String id, double latitude, double longitude,
			int value) {
		Coin coin = new Coin();
		coin.id = id;
		coin.latitude = latitude;
		coin.longitude = longitude;
		coin.value = value;
		return coin;
	}

	private Portal createPortal(String id, double latitude, double longitude,
			int teamOwner, double progressCaptured) {
		Portal portal = new Portal();
		portal.id = id;
		portal.latitude = latitude;
		portal.longitude = longitude;
		portal.teamOwner = teamOwner;
		portal.progressCaptured = progressCaptured;
		return portal;
	}

	public String deleteCoin(String coinId) {
		MongoCollection<Document> collection = getCoinsCollection();
		Document document = new Document();
		document.put("id", coinId);
		DeleteResult deleteResult = collection.deleteMany(document);
		return deleteResult.getDeletedCount() > 0 ? coinId : null;
	}

	public String deletePortal(String portalId) {
		MongoCollection<Document> collection = getPortalsCollection();
		Document document = new Document();
		document.put("id", portalId);
		DeleteResult deleteResult = collection.deleteMany(document);
		return deleteResult.getDeletedCount() > 0 ? portalId : null;
	}

	public long deletePlayersByUser(MongoCollection<Document> collection,
			String userPlayerAccount) {
		Document document = new Document();
		document.put("user", userPlayerAccount);
		DeleteResult deleteResult = collection.deleteMany(document);
		return deleteResult.getDeletedCount();
	}

	public boolean deleteAllCoins() {
		try {
			MongoCollection<Document> collection = getCoinsCollection();
			collection.drop();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public boolean deleteAllPortals() {
		try {
			MongoCollection<Document> collection = getPortalsCollection();
			collection.drop();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public List<Coin> getCoins() {
		List<Coin> coins = new ArrayList<>();
		MongoCollection<Document> collection = getCoinsCollection();
		try {
			MongoCursor<Document> cursor = collection.find().iterator();
			try {
				Gson gson = new Gson();
				while (cursor.hasNext()) {
					Document doc = cursor.next();
					coins.add(gson.fromJson(doc.toJson(), Coin.class));
				}
			} finally {
				try {
					cursor.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return coins;
	}

	public List<Portal> getPortals() {
		List<Portal> portals = new ArrayList<>();
		MongoCollection<Document> collection = getPortalsCollection();
		try {
			MongoCursor<Document> cursor = collection.find().iterator();
			try {
				Gson gson = new Gson();
				while (cursor.hasNext()) {
					Document doc = cursor.next();
					portals.add(gson.fromJson(doc.toJson(), Portal.class));
				}
			} finally {
				try {
					cursor.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return portals;
	}

	public long getCoinsSize() {
		MongoCollection<Document> collection = getCoinsCollection();
		long numCoins = -1;
		try {
			numCoins = collection.count();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return numCoins;
	}

	public long getPortalsSize() {
		MongoCollection<Document> collection = getPortalsCollection();
		long numPortals = -1;
		try {
			numPortals = collection.count();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return numPortals;
	}

	public Coin updateCoin(String coinId, double latitude, double longitude,
			int value) {
		Coin coin = createCoin(coinId, latitude, longitude, value);
		coin = updateCoinById(coin);
		return coin;
	}

	public Account selectGameCharacter(String token, String user,
			String password, String gameCharacterId) {
		Account account = getAccount(token, user, password);
		if (account != null) {
			int slotSelected = -1;
			if (!isEmptyNull(account.gameCharacterSlot1)
					&& !isEmptyNull(gameCharacterId)
					&& account.gameCharacterSlot1.equals(gameCharacterId)) {
				slotSelected = 0;
			} else if (!isEmptyNull(account.gameCharacterSlot2)
					&& !isEmptyNull(gameCharacterId)
					&& account.gameCharacterSlot2.equals(gameCharacterId)) {
				slotSelected = 1;
			}
			if (slotSelected != -1) {
				Account accountToUpdate = new Account();
				accountToUpdate.token = account.token;
				accountToUpdate.user = account.user;
				accountToUpdate.password = account.password;
				accountToUpdate.email = account.email;
				accountToUpdate.currentGameCharacterSlot = slotSelected;
				accountToUpdate.gameCharacterSlot1 = account.gameCharacterSlot1;
				accountToUpdate.gameCharacterSlot2 = account.gameCharacterSlot2;
				Account updatedAccount = updateAccountByUser(accountToUpdate);
				if (updatedAccount != null) {
					return updatedAccount;
				}
			}
		}
		return null;
	}

	public Object[] movedGameCharacter(String token, String user,
			String password, double latitude, double longitude, double radius) {
		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);

			if (gameCharacter != null) {

				// TODO get coins in the radius
				List<Coin> coins = getCoinsInMap(latitude, longitude, radius);

				System.out.println("movedGameCharacter() coins.size(): "
						+ coins.size());

				// TODO get game characters in the radius
				List<GameCharacter> gameCharacters = getGameCharactersInMap(
						latitude, longitude, radius);

				System.out
						.println("movedGameCharacter() gameCharacters.size(): "
								+ gameCharacters.size());

				// TODO get portals in the radius
				List<Portal> portals = getPortalsInMap(latitude, longitude,
						radius);

				System.out.println("movedGameCharacter() portals.size(): "
						+ portals.size());

				// TODO return map data
				MapData mapData = new MapData();
				mapData.setCoins(coins);
				mapData.setGameCharacters(gameCharacters);
				mapData.setPortals(portals);

				Object[] data = new Object[]{gameCharacter,mapData};
				return data;
			}
		}
		return null;
	}

	private GameCharacter updateGameCharacterInMap(GameCharacter gameCharacter) {
		// gather nearby coins
		List<Coin> reachableCoins = getCoinsInMap(gameCharacter.latitude,
				gameCharacter.longitude, 20); // TODO 20 meters
		String id;
		double levelProgressGainCoin;
		for (Coin coin : reachableCoins) {
			id = deleteCoin(coin.id);
			if (id != null) {
				gameCharacter.money = gameCharacter.money + coin.value;

				levelProgressGainCoin = LEVEL_PROGRESS_GAIN_COIN *  (3-Math.log10(gameCharacter.expLevel));
				while(levelProgressGainCoin > 0){
					if(levelProgressGainCoin < 1-gameCharacter.expProgress){
						gameCharacter.expProgress = gameCharacter.expProgress + levelProgressGainCoin;
						levelProgressGainCoin = 0;
					}else if(levelProgressGainCoin == 1-gameCharacter.expProgress){
						gameCharacter.expProgress = 0;
						gameCharacter.expLevel = Math.min(gameCharacter.expLevel+1,MAX_EXP_LEVEL);
						levelProgressGainCoin = 0;
					}else{
						levelProgressGainCoin = levelProgressGainCoin - (1-gameCharacter.expProgress);
						gameCharacter.expProgress = 0;
						gameCharacter.expLevel = Math.min(gameCharacter.expLevel+1,MAX_EXP_LEVEL);
					}
				}
			}
		}
		
		List<Portal> reachablePortals = getPortalsInMap(gameCharacter.latitude, gameCharacter.longitude, 20); // TODO 20 meters
		for (Portal portal : reachablePortals) {
			portal.progressCaptured = 1;
			portal.teamOwner = gameCharacter.gameCharacterTeam;
			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;
	}

	public GameCharacter getPlayingGameCharacter(Account account) {
		String gameCharacterId = null;
		if (account.currentGameCharacterSlot == 0) {
			gameCharacterId = account.gameCharacterSlot1;
		} else if (account.currentGameCharacterSlot == 1) {
			gameCharacterId = account.gameCharacterSlot2;
		}
		if (!isEmptyNull(gameCharacterId)) {
			GameCharacter gameCharacter = getGameCharacter(gameCharacterId);
			if (gameCharacter != null) {
				return gameCharacter;
			}
		}
		return null;
	}

	private List<Coin> getCoinsInMap(double latitude, double longitude,
			double radius) {
		MongoCollection<Document> collection = getCoinsCollection();
		double[] squareValues = getOffsetSquareValues(latitude, longitude,
				radius);
		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<Coin> coins = new ArrayList<>();
		try {
			Coin coin;
			Gson gson = new Gson();
			while (cursor.hasNext()) {
				coin = gson.fromJson(cursor.next().toJson(), Coin.class);
				coins.add(coin);
			}
		} finally {
			cursor.close();
		}
		return coins;
	}

	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);
				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);
				portals.add(portal);
			}
		} finally {
			cursor.close();
		}
		return portals;
	}

	public static float distFrom(float lat1, float lng1, float lat2, float 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
	 */
	private 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;
	}

	private 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 };
	}
	/*
	 * private List<Portal> getPortalsInMap(double latitude, double longitude,
	 * double radius) { // TODO Auto-generated method stub return null; }
	 */

	/*
	 * public MapData position(token,user,password,latitude,longitude,radius){
	 * Account account = getAccount(token,user,password); GameCharacter
	 * gameCharacter = getPlayingGameCharacter(account); if(account != null &&
	 * gameCharacter != null){ gameCharacter.setLatitude(latitude);
	 * gameCharacter.setLongitude(longitude)
	 * if(updateGameCharacter(gameCharacter)){ // TODO merge <-* // TODO update
	 * gameCharacter events, for example when over a portal, update the portal
	 * capture rate, a coin,... // updateGameCharacterInMap(gameCharacter); //
	 * TODO merge <-*
	 * 
	 * // TODO get coins in the radius List<Coin> coins =
	 * getCoinsInRadius(latitude,longitude,radius);
	 * 
	 * // TODO get game characters in the radius List<GameCharacter>
	 * gameCharacters = getGameCharactersInRadius(latitude,longitude,radius);
	 * 
	 * // TODO get portals in the radius List<Portal> portals =
	 * getPortalsInRadius(latitude,longitude,radius);
	 * 
	 * // TODO return map data MapData mapData = new MapData();
	 * mapData.setCoins(coins); mapData.setGameCharacters(gameCharacters);
	 * mapData.setPortals(portals);
	 * 
	 * return mapData; } } return null; }
	 * 
	 * public GameCharacter getPlayingGameCharacter(account){ String
	 * gameCharacterId = null; if(account.currentGameCharacterSelected == 0){
	 * gameCharacterId = account.gameCharacterSlot1; }else
	 * if(account.currentGameCharacterSelected == 1){ gameCharacterId =
	 * account.gameCharacterSlot2; } if(!isEmptyNull(gameCharacterId)){
	 * GameCharacter gameCharacter = getGameCharacter(gameCharacterId); return
	 * gameCharacter; } return null; }
	 */

}