package org.dynmap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.dynmap.common.DynmapListenerManager.EventType;
import org.dynmap.common.DynmapListenerManager.WorldEventListener;
import org.dynmap.common.DynmapListenerManager.PlayerEventListener;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.markers.AreaMarker;
import org.dynmap.markers.Marker;
import org.dynmap.markers.MarkerAPI;
import org.dynmap.markers.MarkerIcon;
import org.dynmap.markers.MarkerSet;
import org.dynmap.markers.impl.MarkerSignManager;
import org.dynmap.utils.Polygon;

/**
 * Markers component - ties in the component system, both on the server and client
 */
public class MarkersComponent extends ClientComponent {
    private MarkerAPI api;
    private MarkerSignManager signmgr;
    private MarkerIcon spawnicon;
    private String spawnlbl;
    private String worldborderlbl;
    private MarkerSet offlineset;
    private MarkerIcon offlineicon;
    private MarkerSet spawnbedset;
    private MarkerIcon spawnbedicon;
    private String spawnbedformat;
    private boolean removebedonplayerleave;
    private long maxofflineage;
    private boolean showSpawn;
    private boolean showBorder;
    private HashMap<String, Long> offline_times = new HashMap<String, Long>();
    private static final String OFFLINE_PLAYERS_SETID = "offline_players";
    private static final String PLAYER_SPAWN_BED_SETID = "spawn_beds";
    
    public MarkersComponent(final DynmapCore core, ConfigurationNode configuration) {
        super(core, configuration);
        
        api = core.getMarkerAPI();
        
        /* If configuration has enabled sign support, prime it too */
        if(configuration.getBoolean("enablesigns", false)) {
            signmgr = MarkerSignManager.initializeSignManager(core, configuration.getString("default-sign-set", MarkerSet.DEFAULT));
        }
        showBorder = configuration.getBoolean("showworldborder", false);
        showSpawn = configuration.getBoolean("showspawn", false);
        /* If we're posting spawn point markers, initialize and add world listener */
        if(showSpawn) {
            String ico = configuration.getString("spawnicon", MarkerIcon.WORLD);
            spawnlbl = configuration.getString("spawnlabel", "Spawn");
            spawnicon = api.getMarkerIcon(ico); /* Load it */
            if(spawnicon == null) {
                spawnicon = api.getMarkerIcon(MarkerIcon.WORLD);
            }
        }
        if (showBorder) {
            worldborderlbl = configuration.getString("worldborderlabel", "Border");
        }
        if (showSpawn || showBorder) {
            /* Add listener for world loads */
            WorldEventListener wel = new WorldEventListener() {
                @Override
                public void worldEvent(DynmapWorld w) {
                    DynmapLocation loc = w.getSpawnLocation();    /* Get location of spawn */
                    if(loc != null)
                        addUpdateWorld(w, loc);
                }
            };
            core.listenerManager.addListener(EventType.WORLD_LOAD, wel);
            /* Add listener for spawn changes */
            core.listenerManager.addListener(EventType.WORLD_SPAWN_CHANGE, wel);
            
            /* Initialize already loaded worlds */
            for(DynmapWorld w : core.getMapManager().getWorlds()) {
                DynmapLocation loc = w.getSpawnLocation();
                if(loc != null)
                    addUpdateWorld(w, loc);
            }
        }
        /* If showing offline players as markers */
        if(configuration.getBoolean("showofflineplayers", false)) {
            /* Make set, if needed */
            offlineset = api.getMarkerSet(OFFLINE_PLAYERS_SETID);
            if(offlineset == null) {
                offlineset = api.createMarkerSet(OFFLINE_PLAYERS_SETID, configuration.getString("offlinelabel", "Offline"), null, true);
            }
            offlineset.setHideByDefault(configuration.getBoolean("offlinehidebydefault", true));
            offlineset.setMinZoom(configuration.getInteger("offlineminzoom", 0));
            maxofflineage = 60000L * configuration.getInteger("maxofflinetime", 30); /* 30 minutes */
            /* Now, see if existing offline markers - check for last login on their users */
            if(maxofflineage > 0) {
                Set<Marker> prev_m = offlineset.getMarkers();
                for(Marker m : prev_m) {
                    DynmapPlayer p = core.getServer().getOfflinePlayer(m.getMarkerID());
                    if(p != null) {
                        long ageout = p.getLastLoginTime() + maxofflineage;
                        if(ageout < System.currentTimeMillis()) {
                            m.deleteMarker();
                        }
                        else {
                           offline_times.put(p.getName(), ageout);
                        }
                    }
                    else {
                        m.deleteMarker();
                    }
                }
            }
            offlineicon = api.getMarkerIcon(configuration.getString("offlineicon", "offlineuser"));
            if(maxofflineage > 0) {
                core.getServer().scheduleServerTask(new Runnable() {
                    public void run() {
                        long ts = System.currentTimeMillis();
                        ArrayList<String> deleted = new ArrayList<String>();
                        for(Map.Entry<String,Long> me : offline_times.entrySet()) {
                            if(ts > me.getValue()) {
                                deleted.add(me.getKey());
                            }
                        }
                        for(String id : deleted) {
                            Marker m = offlineset.findMarker(id);
                            if(m != null)
                                m.deleteMarker();
                        }
                        core.getServer().scheduleServerTask(this, 30 * 20);
                    }
                }, 30 * 20);    /* Check every 30 seconds */
            }
            /* Add listener for players coming and going */
            core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() {
                @Override
                public void playerEvent(DynmapPlayer p) {
                    Marker m = offlineset.findMarker(p.getName());
                    if(m != null) {
                        m.deleteMarker();
                        offline_times.remove(p.getName());
                    }
                }
            });
            core.listenerManager.addListener(EventType.PLAYER_QUIT, new PlayerEventListener() {
                @Override
                public void playerEvent(DynmapPlayer p) {
                    String pname = p.getName();
                    Marker m = offlineset.findMarker(pname);
                    if(m != null) {
                        m.deleteMarker();
                        offline_times.remove(p.getName());
                    }
                    if(core.playerList.isVisiblePlayer(pname)) {
                        DynmapLocation loc = p.getLocation();
                        m = offlineset.createMarker(p.getName(), core.getServer().stripChatColor(p.getDisplayName()), false,
                                                loc.world, loc.x, loc.y, loc.z, offlineicon, true);
                        if(maxofflineage > 0)
                            offline_times.put(p.getName(), System.currentTimeMillis() + maxofflineage);
                    }
                }
            });
        }
        else {
            /* Make set, if needed */
            offlineset = api.getMarkerSet(OFFLINE_PLAYERS_SETID);
            if(offlineset != null) {
                offlineset.deleteMarkerSet();
            }
        }
        /* If showing player spawn bed locations as markers */
        if(configuration.getBoolean("showspawnbeds", false)) {
            /* Make set, if needed */
            spawnbedset = api.getMarkerSet(PLAYER_SPAWN_BED_SETID);
            if(spawnbedset == null) {
                spawnbedset = api.createMarkerSet(PLAYER_SPAWN_BED_SETID, configuration.getString("spawnbedlabel", "Spawn Beds"), null, true);
            }
            spawnbedset.setHideByDefault(configuration.getBoolean("spawnbedhidebydefault", true));
            spawnbedset.setMinZoom(configuration.getInteger("spawnbedminzoom", 0));
            
            spawnbedicon = api.getMarkerIcon(configuration.getString("spawnbedicon", "bed"));
            spawnbedformat = configuration.getString("spawnbedformat", "%name%'s bed");
            removebedonplayerleave = configuration.getBoolean("spawnbedremoveonplayerleave", true);
            /* Add listener for players coming and going */
            core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() {
                @Override
                public void playerEvent(DynmapPlayer p) {                    
                    updatePlayer(p);
                }
            });
            if (removebedonplayerleave) {
                core.listenerManager.addListener(EventType.PLAYER_QUIT, new PlayerEventListener() {
                    @Override
                    public void playerEvent(DynmapPlayer p) {
                        Marker m = spawnbedset.findMarker(p.getName() + "_bed");
                        if (m != null) {
                            m.deleteMarker();
                        }
                    }
                });
            }
            core.listenerManager.addListener(EventType.PLAYER_BED_LEAVE, new PlayerEventListener() {
                @Override
                public void playerEvent(final DynmapPlayer p) {                    
                    core.getServer().scheduleServerTask(new Runnable() {
                        public void run() {
                            updatePlayer(p);
                        }
                    }, 0);
                }
            });
        }
        else {
            /* Make set, if needed */
            spawnbedset = api.getMarkerSet(PLAYER_SPAWN_BED_SETID);
            if(spawnbedset != null) {
                spawnbedset.deleteMarkerSet();
            }
        }
    }
    
    private void updatePlayer(DynmapPlayer p) {
        DynmapLocation bl = p.getBedSpawnLocation();
        Marker m = spawnbedset.findMarker(p.getName()+"_bed");
        if(bl == null) {    /* No bed location */
            if(m != null) {
                m.deleteMarker();
            }
        }
        else {
            if(m != null)
                m.setLocation(bl.world, bl.x, bl.y, bl.z);
            else
                m = spawnbedset.createMarker(p.getName()+"_bed", spawnbedformat.replace("%name%", core.getServer().stripChatColor(p.getDisplayName())), false,
                        bl.world, bl.x, bl.y, bl.z,
                        spawnbedicon, true);
        }
    }

    
    private void addUpdateWorld(DynmapWorld w, DynmapLocation loc) {
        MarkerSet ms = api.getMarkerSet(MarkerSet.DEFAULT);
        if(ms != null) {
            String spawnid = "_spawn_" + w.getName();
            Marker m = ms.findMarker(spawnid);    /* See if defined */
            if (showSpawn) {
                if(m == null) { /* Not defined yet, add it */
                    ms.createMarker(spawnid, spawnlbl, w.getName(), loc.x, loc.y, loc.z,
                                spawnicon, false);
                }
                else {
                    m.setLocation(w.getName(), loc.x, loc.y, loc.z);
                }
            }
            else {
                if (m != null) {
                    m.deleteMarker();
                }
            }
            String borderid = "_worldborder_" + w.getName();
            AreaMarker am = ms.findAreaMarker(borderid);
            Polygon p = null;
            if (showBorder && w.showborder) {
                p = w.getWorldBorder();
            }
            if ((p != null) && (p.size() > 1)) {
                double[] x;
                double[] z;
                if (p.size() == 2) {
                    x = new double[4];
                    z = new double[4];
                    Polygon.Point2D p0 = p.getVertex(0);
                    Polygon.Point2D p1 = p.getVertex(1);
                    x[0] = p0.x; z[0] = p0.y;
                    x[1] = p0.x; z[1] = p1.y;
                    x[2] = p1.x; z[2] = p1.y;
                    x[3] = p1.x; z[3] = p0.y;
                }
                else {
                    int sz = p.size();
                    x = new double[sz];
                    z = new double[sz];
                    for (int i = 0; i < sz; i++) {
                        Polygon.Point2D pi = p.getVertex(i);
                        x[i] = pi.x; z[i] = pi.y;
                    }
                }
                if (am == null) {
                    am = ms.createAreaMarker(borderid, worldborderlbl, false, w.getName(), x, z, false);
                }
                else {
                    am.setCornerLocations(x, z);
                }
                am.setFillStyle(0.0, 0);
            }
            else {
                if (am != null) {
                    am.deleteMarker();
                }
            }
        }
    }

    @Override
    public void dispose() {
        if(signmgr != null) {
            MarkerSignManager.terminateSignManager(this.core);
            signmgr = null;
        }
        /* Don't unregister API - other plugins might be using it, and we want to keep non-persistent markers */
    }
}
