content: package com.alecgorge.minecraft.jsonapi.adminium;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.logging.Handler;
  import java.util.logging.Level;
  import java.util.logging.LogRecord;
  import java.util.logging.Logger;
  
  import org.bukkit.command.CommandSender;
  import org.bukkit.configuration.ConfigurationSection;
  import org.bukkit.configuration.InvalidConfigurationException;
  import org.bukkit.configuration.file.YamlConfiguration;
  import org.json.simpleForBukkit.JSONObject;
  
  import com.alecgorge.java.http.MutableHttpRequest;
  import com.alecgorge.minecraft.jsonapi.JSONAPI;
  import com.alecgorge.minecraft.jsonapi.api.APIMethodName;
  import com.alecgorge.minecraft.jsonapi.api.JSONAPICallHandler;
  import com.alecgorge.minecraft.jsonapi.api.JSONAPIStream;
  import com.alecgorge.minecraft.jsonapi.api.JSONAPIStreamListener;
  import com.alecgorge.minecraft.jsonapi.api.JSONAPIStreamMessage;
  import com.alecgorge.minecraft.jsonapi.streams.ConnectionMessage;
  import com.alecgorge.minecraft.jsonapi.util.FixedSizeArrayList;
  
  public class PushNotificationDaemon implements JSONAPIStreamListener, JSONAPICallHandler {
  	public class AdminiumPushNotification {
  		Date dateSent;
  		String notification;
  		
  		public AdminiumPushNotification(Date d, String message) {
  			dateSent = d;
  			notification = message;
  		}
  		
  		public Date getDateSent() {
  			return dateSent;
  		}
  
  		public void setDateSent(Date dateSent) {
  			this.dateSent = dateSent;
  		}
  
  		public String getMessage() {
  			return notification;
  		}
  
  		public void setMessage(String notification) {
  			this.notification = notification;
  		}
  		
  		public String getPushNotification() {
  			JSONAPI api = JSONAPI.instance;
  			
  			String messager = getMessage();
  			if (api.serverName != null && !api.serverName.isEmpty()) {
  				messager = (api.serverName.equals("default") ? api.getServer().getServerName() : api.serverName) + ": " + messager;
  			}
  
  			return messager.length() > 210 ? messager.substring(0, 208) + "\u2026" : messager;
  		}		
  	}
  	
  	YamlConfiguration deviceConfig = new YamlConfiguration();
  	File configFile;
  
  	List<String> devices = new ArrayList<String>();
  	Map<String, Boolean> settings = new HashMap<String, Boolean>();
  
  	Map<String, Map<String, Boolean>> deviceOverrides = new HashMap<String, Map<String, Boolean>>();
  
  	// log /calladmin & severes
  	List<String> calladmins = Collections.synchronizedList(new FixedSizeArrayList<String>(50));
  	List<String> severeLogs = Collections.synchronizedList(new FixedSizeArrayList<String>(50));
  	List<AdminiumPushNotification> notifications = Collections.synchronizedList(new FixedSizeArrayList<AdminiumPushNotification>(150));
  
  	private final String APNS_PUSH_ENDPOINT = "http://push.adminiumapp.com/push-message";
  
  	private JSONAPI api;
  	public boolean init = false;
  
  	public final boolean doTrace = false;
  
  	private Logger mcLog = Logger.getLogger("Minecraft");
  
  	private void trace(Object... args) {
  		if (doTrace) {
  			String[] na = new String[args.length];
  			for (int i = 0; i < args.length; i++)
  				na[i] = args[i] == null ? "NULL_VALUE" : args[i].toString();
  
  			mcLog.info("'" + api.join(Arrays.asList(na), "' '") + "'");
  		}
  	}
  
  	private List<String> pushTypes = new ArrayList<String>();
  	private List<String> pushTypeDescriptions;
  
  	public PushNotificationDaemon(File configFile, JSONAPI api) throws FileNotFoundException, IOException, InvalidConfigurationException {
  		this.configFile = configFile;
  		this.api = api;
  
  		api.registerAPICallHandler(this);
  		if (configFile.exists()) {
  			initalize();
  		}
  	}
  
  	public boolean calladmin(CommandSender from, String message) {
  		if (api.anyoneCanUseCallAdmin || from.hasPermission("jsonapi.calladmin")) {
  			String push = "Admin request from " + from.getName() + ": " + message;
  
  			if (!(settings.get("admin_call") != null && settings.get("admin_call"))) {
  				from.sendMessage("The admin has disabled /calladmin.");
  
  				return true;
  			}
  
  			calladmins.add(0, push);
  			pushNotification(push, "admin_call");
  			from.sendMessage("A message was sent to the admin(s).");
  		} else if (!from.hasPermission("jsonapi.calladmin")) {
  			from.sendMessage("You don't have the jsonapi.calladmin permission to call for an admin.");
  		}
  
  		return true;
  	}
  
  	public class ConsoleHandler extends Handler {
  		PushNotificationDaemon p;
  		long lastNotification;
  
  		public ConsoleHandler(PushNotificationDaemon d) {
  			p = d;
  			lastNotification = 0;
  		}
  
  		@Override
  		public void close() throws SecurityException {
  			// TODO Auto-generated method stub
  
  		}
  
  		@Override
  		public void flush() {
  			// TODO Auto-generated method stub
  
  		}
  
  		@Override
  		public void publish(LogRecord arg0) {
  			if (settings != null && arg0 != null && arg0.getLevel().equals(Level.SEVERE)) {
  				String message = "SEVERE message logged in the console: " + arg0.getMessage();
  				severeLogs.add(0, message);
  
  				if (settings.get("severe_log") != null && settings.get("severe_log")) {
  					long time = (new Date()).getTime();
  					if (time - lastNotification > 60 * 1000) {
  						lastNotification = time;
  
  						p.pushNotification(message, "severe_log");
  					}
  				}
  			}
  		}
  	}
  
  	public void addDeviceIfNotExist(String device) throws IOException {
  		if (!devices.contains(device)) {
  			registerDevice(device);
  		}
  	}
  
  	private void registerDevice(final String device) {
  		trace("Attempting to register", device);
  
  		if (device.length() != 64 || devices.contains(device)) {
  			return;
  		}
  
  		devices.add(device);
  		deviceConfig.set("devices", devices);
  		try {
  			deviceConfig.save(configFile);
  		} catch (IOException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}
  	}
  
  	private void deregisterDevice(final String device) {
  		trace("Attempting to deregister", device);
  
  		if (device.length() != 64 || !devices.contains(device)) {
  			return;
  		}
  
  		devices.remove(device);
  		deviceConfig.set("devices", devices);
  		try {
  			deviceConfig.save(configFile);
  		} catch (IOException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}
  	}
  
  	@Override
  	public void onMessage(JSONAPIStreamMessage message, JSONAPIStream sender) {
  		trace(message);
  
  		if (message instanceof ConnectionMessage) {
  			ConnectionMessage c = (ConnectionMessage) message;
  			if (settings.get("player_joined") != null && settings.get("player_joined") && c.TrueIsConnectedFalseIsDisconnected) {
  				pushNotification(c.player + " joined!", "player_joined");
  			} else if (settings.get("player_quit") != null && settings.get("player_quit") && !c.TrueIsConnectedFalseIsDisconnected) {
  				pushNotification(c.player + " quit!", "player_quit");
  			}
  		}
  	}
  
  	public void pushNotification(String messager, final String type) {
  		if (devices.size() < 1) {
  			return;
  		}
  
  		final AdminiumPushNotification not = new AdminiumPushNotification(new Date(), messager);
  		notifications.add(not);
  
  		new Thread(new Runnable() {
  
  			@Override
  			public void run() {
  				String msg  = not.getPushNotification();
  				trace("pushing", msg);
  				MutableHttpRequest r = null;
  				try {
  					r = new MutableHttpRequest(new URL(APNS_PUSH_ENDPOINT));
  					for (String d : devices) {
  						if (deviceOverrides.containsKey(d) && deviceOverrides.get(d).containsKey(type) && deviceOverrides.get(d).get(type) || !deviceOverrides.containsKey(d))
  							r.addPostValue("devices[]", d);
  					}
  					r.addPostValue("message", msg);
  
  					trace("Sending Post Args:", devices, msg, r.getPostKeys(), r.getPostValues());
  
  					trace("Complete", r.post().getReponse());
  				} catch (Exception e) {
  					e.printStackTrace();
  				} finally {
  					if (r != null)
  						r.close();
  				}
  			}
  		}).start();
  	}
  
  	@Override
  	public boolean willHandle(APIMethodName methodName) {
  		return (methodName.getNamespace().equals("adminium") && (methodName.getMethodName().equals("registerDevice") || methodName.getMethodName().equals("listPushTypes") || methodName.getMethodName().equals("setPushTypeEnabled") || methodName.getMethodName().equals("triggerSevere") || methodName.getMethodName().equals("getCallAdmins") || methodName.getMethodName().equals("getSeveres") || methodName.getMethodName().equals("deregisterDevice")));
  	}
  
  	private void initalize() {
  		if (!this.init) {
  			mcLog.addHandler(new ConsoleHandler(this));
  
  			boolean initialSetup = !configFile.exists();
  
  			try {
  				configFile.createNewFile();
  				deviceConfig.load(configFile);
  
  				if (initialSetup) {
  					deviceConfig.set("devices", null);
  					deviceConfig.set("device-map", null);
  					deviceConfig.set("settings", "");
  					deviceConfig.set("settings.player_joined", false);
  					deviceConfig.set("settings.player_quit", false);
  					deviceConfig.set("settings.admin_call", true);
  					deviceConfig.set("settings.severe_log", false);
  
  					deviceConfig.save(configFile);
  					deviceConfig.load(configFile);
  				}
  
  				if (!deviceConfig.contains("group_assignments")) {
  					deviceConfig.set("group_assignments", null);
  					deviceConfig.save(configFile);
  				}
  
  				devices = deviceConfig.getStringList("devices");
  				if (devices == null)
  					devices = new ArrayList<String>();
  
  				trace("Current Devices", devices);
  
  				Map<String, Object> tempDefaults = ((ConfigurationSection) deviceConfig.get("settings")).getValues(false);
  				for (String key : tempDefaults.keySet()) {
  					settings.put(key, Boolean.valueOf(tempDefaults.get(key).toString()));
  				}
  
  				// load device specific overrides
  				Object m = deviceConfig.get("device-map");
  
  				if (m != null) {
  					Map<String, Object> tempOverrides = ((ConfigurationSection) m).getValues(false);
  					for (String device : tempOverrides.keySet()) {
  						Map<String, Boolean> map = new HashMap<String, Boolean>();
  
  						Map<String, Object> tempBools = ((ConfigurationSection) deviceConfig.get("device-map." + device)).getValues(false);
  
  						for (String key : tempBools.keySet()) {
  							deviceOverrides.put(key, new HashMap<String, Boolean>());
  
  							map.put(key, Boolean.valueOf(tempDefaults.get(key).toString()));
  						}
  						deviceOverrides.put(device, map);
  					}
  				}
  
  				if (settings != null && settings.get("player_joined") || settings.get("player_quit")) {
  					api.getStreamManager().getStream("connections").registerListener(this, false);
  				}
  			} catch (Exception e) {
  				e.printStackTrace();
  			}
  
  			pushTypes.addAll(settings.keySet());
  			Collections.sort(pushTypes);
  			pushTypeDescriptions = Arrays.asList(new String[] { "On /calladmin", "On player join", "On player quit", "On SEVERE logs" });
  
  			this.init = true;
  		}
  	}
  
  	public void saveConfig() throws IOException {
  		/*deviceConfig.set("group_assignments", groupAssignments);
  		deviceConfig.set("group_permissions", groupPerms);*/
  
  		deviceConfig.save(configFile);
  	}
  
  	@Override
  	public Object handle(APIMethodName methodName, Object[] args) {
  		if (methodName.getNamespace().equals("adminium")) {
  			initalize();
  		}
  
  		if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("registerDevice") && args.length == 1) {
  			String deviceToken = args[0].toString();
  
  			registerDevice(deviceToken);
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("deregisterDevice") && args.length == 1) {
  			String deviceToken = args[0].toString();
  
  			deregisterDevice(deviceToken);
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("listPushTypes")) {
  			JSONObject o = new JSONObject();
  
  			int i = 0;
  			for (String k : pushTypes) {
  				JSONObject oo = new JSONObject();
  				oo.put("enabled", settings.get(k));
  				oo.put("description", pushTypeDescriptions.get(i));
  				o.put(k, oo);
  				i++;
  			}
  
  			return o;
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("setPushTypeEnabled")) {
  			settings.put(args[0].toString(), Boolean.valueOf(args[1].toString()));
  			deviceConfig.set("settings." + args[0].toString(), settings.get(args[0].toString()));
  			try {
  				deviceConfig.save(configFile);
  			} catch (IOException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  
  			return true;
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("triggerSevere")) {
  			for (int i = 0; i < 10; i++)
  				mcLog.severe("This is a severe log: " + i);
  
  			return true;
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("getCallAdmins")) {
  			return calladmins;
  		} else if (methodName.getNamespace().equals("adminium") && methodName.getMethodName().equals("getSeveres")) {
  			return severeLogs;
  		}
  
  		return null;
  	}
  }
  