package net.cpprograms.minecraft.TravelPortals.storage;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.cpprograms.minecraft.TravelPortals.WarpLocation;
import org.bukkit.Location;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public abstract class PortalStorage {

    private Map<String, WarpLocation> portals = new LinkedHashMap<>();
    private Cache<String, WarpLocation> locationCache = CacheBuilder.newBuilder()
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .weakValues()
            .build();

    /**
     * Load the data from the storage
     * @return true if it succeeded; false if it failed
     */
    public abstract boolean load();

    /**
     * Save data of a portal to the storage
     * @return true if it succeeded; false if it failed
     */
    public abstract boolean save(WarpLocation portal);

    /**
     * Save data of a all portals in a world
     * @return true if it succeeded; false if it failed
     */
    public abstract boolean save(String worldName);

    /**
     * Save all data to the storage
     * @return true if it succeeded; false if it failed
     */
    public abstract boolean save();

    /**
     * Get the type of this storage
     * @return The type of this storage
     */
    public abstract StorageType getType();

    /**
     * This gets all portals
     * @return A list with all portals
     */
    public Map<String, WarpLocation> getPortals() {
        return portals;
    }

    /**
     * Add a new portal
     * @param portal The info of the portal
     */
    public void addPortal(WarpLocation portal) {
        portals.put(portal.getName().toLowerCase(), portal);
    }

    /**
     * Get portal by it's name
     * @param name The name of the portal
     * @return The portal info or null if none was found
     */
    public WarpLocation getPortal(String name) {
        return portals.get(name.toLowerCase());
    }

    /**
     * Get a nearby portal
     * @param location
     * @return
     */
    public WarpLocation getPortal(Location location) {
        String locStr = location.getWorld() + "," + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ();
        WarpLocation portal = locationCache.getIfPresent(locStr);
        if (portal != null && !portals.containsValue(portal)) {
            locationCache.invalidate(locStr);
            portal = null;
        }
        if (portal == null) {
            for (WarpLocation p : getPortals().values()) {
                if (p.getWorld().isEmpty() || location.getWorld().getName().equals(p.getWorld())) {
                    if (Math.abs(p.getX() - location.getBlockX()) <= 1 && Math.abs(p.getZ() - location.getBlockZ()) <= 1) {
                        portal = p;
                        locationCache.put(locStr, portal);
                        break;
                    }
                }
            }
        }
        return portal;
    }

    /**
     * Remove a portal
     * @param portal The portal to remove
     */
    public void namePortal(WarpLocation portal, String name) {
        portals.remove(portal.getName().toLowerCase());
        portal.setName(name);
        addPortal(portal);
        save(portal);
    }

    /**
     * Remove a portal
     * @param portal The portal to remove
     */
    public void removePortal(WarpLocation portal) {
        portals.remove(portal.getName().toLowerCase());
        locationCache.invalidate(portal.getWorld() + "," + portal.getX() + "," + portal.getY() + "," + portal.getZ());
    }

    /**
     * Rename a world for all existing portals.
     * @param oldWorld The name of the old world.
     * @param newWorld The name of the new world.
     * @return true if it succeeded; false if it failed
     */
    public boolean renameWorld(String oldWorld, String newWorld) {
        for (WarpLocation portal : portals.values()) {
            if (oldWorld.equalsIgnoreCase(portal.getWorld())) {
                portal.setWorld(newWorld);
            }
        }
        return save(newWorld);
    }

    /**
     * Delete all portals linking to a world.
     * @param world The world to delete all portals to.
     * @return true if it succeeded; false if it failed
     */
    public boolean deleteWorld(String world) {
        for (WarpLocation portal : portals.values()) {
            if (world.equalsIgnoreCase(portal.getWorld())) {
                removePortal(portal);
            }
        }
        return save(world);
    }

    /**
     * This deletes all portals from the cache
     */
    public void clearCache() {
        portals.clear();
        locationCache.invalidateAll();
    }
}

@@ -451,28 +451,22 @@
 String, 
-WarpLocation
+String
 %3E locati
@@ -558,34 +558,8 @@
 DS)%0A
-            .weakValues()%0A
     
@@ -2241,35 +2241,33 @@
         
-WarpLocation
+String
  portal
+Name
  = locat
@@ -2315,16 +2315,20 @@
  (portal
+Name
  != null
@@ -2352,20 +2352,22 @@
 ains
-Value
+Key
 (portal
+Name
 )) %7B
@@ -2431,16 +2431,20 @@
   portal
+Name
  = null;
@@ -2472,16 +2472,20 @@
  (portal
+Name
  == null
@@ -2775,44 +2775,8 @@
 ) %7B%0A
-                        portal = p;%0A
     
@@ -2822,21 +2822,26 @@
 ocStr, p
-ortal
+.getName()
 );%0A     
@@ -2863,13 +2863,16 @@
     
-b
 re
-ak
+turn p
 ;%0A  
@@ -2915,32 +2915,75 @@
 %7D%0A            %7D%0A
+            locationCache.put(locStr, %22%22);%0A
         %7D%0A      
@@ -2991,21 +2991,19 @@
  return 
-porta
+nul
 l;%0A    %7D

Make sure to cache locations without portals too