package com.demigodsrpg.demigods.engine.data;

import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import org.bukkit.configuration.ConfigurationSection;

import java.util.*;
import java.util.concurrent.TimeUnit;

public class TimedData extends DataAccess<UUID, TimedData>
{
	private UUID id;
	private String key;
	private String subKey;
	private String data;
	private String type;
	private long expiration;

	public TimedData()
	{}

	public TimedData(UUID id, ConfigurationSection conf)
	{
		this.id = id;
		key = conf.getString("key");
		subKey = conf.getString("subKey");
		data = conf.getString("data");
		type = conf.getString("type");
		expiration = conf.getLong("expiration");
	}

	@Override
	public Map<String, Object> serialize()
	{
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("key", key);
		map.put("subKey", subKey);
		map.put("data", data);
		map.put("type", type);
		map.put("expiration", expiration);
		return map;
	}

	public void generateId()
	{
		id = UUID.randomUUID();
	}

	public void setKey(String key)
	{
		this.key = key;
	}

	public void setSubKey(String subKey)
	{
		this.subKey = subKey;
	}

	public void setData(Object data)
	{
		this.data = data.toString();
		this.type = data.getClass().getName();
	}

	public void setMilliSeconds(long millis)
	{
		this.expiration = System.currentTimeMillis() + millis;
	}

	public UUID getId()
	{
		return this.id;
	}

	public String getKey()
	{
		return this.key;
	}

	public String getSubKey()
	{
		return this.subKey;
	}

	public Object getData()
	{
		if(this.type.equalsIgnoreCase("string")) return this.data;
		if(this.type.equalsIgnoreCase("integer")) return Integer.parseInt(this.data);
		if(this.type.equalsIgnoreCase("boolean")) return Boolean.parseBoolean(this.data);
		return this.data;
	}

	public long getExpiration()
	{
		return this.expiration;
	}

	@Override
	public boolean equals(final Object obj)
	{
		if(this == obj) return true;
		if(obj == null || getClass() != obj.getClass()) return false;
		final TimedData other = (TimedData) obj;
		return Objects.equal(this.id, other.id) && Objects.equal(this.key, other.key) && Objects.equal(this.subKey, other.subKey) && Objects.equal(this.data, other.data);
	}

	@Override
	public int hashCode()
	{
		return Objects.hashCode(this.id, this.key, this.subKey, this.data);
	}

	@Override
	public String toString()
	{
		return Objects.toStringHelper(this).add("id", this.id).add("key", this.key).add("subkey", this.subKey).add("data", this.data).toString();
	}

	private static final DataAccess<UUID, TimedData> DATA_ACCESS = new TimedData();

	public static Collection<TimedData> all()
	{
		return DATA_ACCESS.getAll();
	}

	/*
	 * Timed data
	 */
	public static void saveTimed(String key, String subKey, Object data, long time, TimeUnit unit)
	{
		// Remove the data if it exists already
		remove(key, subKey);

		// Create and save the timed data
		TimedData timedData = new TimedData();
		timedData.generateId();
		timedData.setKey(key);
		timedData.setSubKey(subKey);
		timedData.setData(data.toString());
		timedData.setMilliSeconds(unit.toMillis(time));
		timedData.save();
	}

	public static boolean exists(String key, String subKey)
	{
		return find(key, subKey) != null;
	}

	public static Object value(String key, String subKey)
	{
		return find(key, subKey).getData();
	}

	public static long getExpiration(String key, String subKey)
	{
		return find(key, subKey).getExpiration();
	}

	public static TimedData find(String key, String subKey)
	{
		if(findByKey(key) == null) return null;

		for(TimedData data : findByKey(key))
			if(data.getSubKey().equals(subKey)) return data;

		return null;
	}

	public static Set<TimedData> findByKey(final String key)
	{
		return Sets.newHashSet(Collections2.filter(all(), new Predicate<TimedData>()
		{
			@Override
			public boolean apply(TimedData serverData)
			{
				return serverData.getKey().equals(key);
			}
		}));
	}

	public static void remove(String key, String subKey)
	{
		if(find(key, subKey) != null) find(key, subKey).remove();
	}

	/**
	 * Clears all expired timed data.
	 */
	public static void clearExpired()
	{
		for(TimedData data : Collections2.filter(all(), new Predicate<TimedData>()
		{
			@Override
			public boolean apply(TimedData data)
			{
				return data.getExpiration() <= System.currentTimeMillis();
			}
		}))
			data.remove();
	}
}

@@ -457,43 +457,21 @@
 ate 
-String
+Object
  data;%0A
-%09private String type;%0A
 %09pri
@@ -676,22 +676,16 @@
 conf.get
-String
 (%22data%22)
@@ -690,41 +690,8 @@
 %22);%0A
-%09%09type = conf.getString(%22type%22);%0A
 %09%09ex
@@ -927,33 +927,8 @@
 a);%0A
-%09%09map.put(%22type%22, type);%0A
 %09%09ma
@@ -1206,74 +1206,256 @@
 %7B%0A%09%09
-this.data = data.toString();%0A%09%09
+if(data instanceof String %7C%7C data instanceof Integer %7C%7C data instanceof Boolean %7C%7C data instanceof Double %7C%7C data instanceof Map %7C%7C data instanceof List) 
 this.
-type
+data
  = data
-.getClass().getName
+;%0A%09%09else if(data == null) this.data = %22null%22;%0A%09%09else this.data = data.toString
 ();%0A
@@ -1748,233 +1748,8 @@
 %0A%09%7B%0A
-%09%09if(this.type.equalsIgnoreCase(%22string%22)) return this.data;%0A%09%09if(this.type.equalsIgnoreCase(%22integer%22)) return Integer.parseInt(this.data);%0A%09%09if(this.type.equalsIgnoreCase(%22boolean%22)) return Boolean.parseBoolean(this.data);%0A
 %09%09re
@@ -2994,24 +2994,24 @@
 ey(subKey);%0A
+
 %09%09timedData.
@@ -3022,27 +3022,16 @@
 ata(data
-.toString()
 );%0A%09%09tim

Allow TimedData to safely store Lists and Maps. Even though we'll probably never use this outside of quick fixes and testing, it's still probably a good idea to prevent it from giving errors up the wazzo.