package org.dynmap.exporter;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.dynmap.DynmapCore;
import org.dynmap.DynmapLocation;
import org.dynmap.DynmapWorld;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.hdmap.HDShader;

/**
 * Handler for export commands (/dynmapexp)
 */
public class DynmapExpCommands {
    private HashMap<String, ExportContext> sessions = new HashMap<String, ExportContext>();

    private static class ExportContext {
        public String shader = "stdtexture";
        public int xmin = Integer.MIN_VALUE;
        public int ymin = Integer.MIN_VALUE;
        public int zmin = Integer.MIN_VALUE;
        public int xmax = Integer.MIN_VALUE;
        public int ymax = Integer.MIN_VALUE;
        public int zmax = Integer.MIN_VALUE;
        public String world;
        public boolean groupByChunk;
        public boolean groupByBlockID;
        public boolean groupByBlockIDData;
        public boolean groupByTexture;
    }

    private String getContextID(DynmapCommandSender sender) {
        String id = "<console>";
        if (sender instanceof DynmapPlayer) {
            id = ((DynmapPlayer)sender).getName();
        }
        return id;
    }
    
    private ExportContext getContext(DynmapCommandSender sender) {
        String id = getContextID(sender);
        
        ExportContext ctx = sessions.get(id);
        if (ctx == null) {
            ctx = new ExportContext();
            sessions.put(id, ctx);
        }
        return ctx;
    }
    
    public boolean processCommand(DynmapCommandSender sender, String cmd, String commandLabel, String[] args, DynmapCore core) {
        /* Re-parse args - handle doublequotes */
        args = DynmapCore.parseArgs(args, sender);
        if(args.length < 1)
            return false;
        if(!core.checkPlayerPermission(sender, "dynmapexp.export")) {
            return true;
        }
        cmd = args[0];
        boolean rslt = false;
        ExportContext ctx = getContext(sender);
        
        if(cmd.equalsIgnoreCase("set")) {
            rslt = handleSetExport(sender, args, ctx, core);
        }
        else if (cmd.equalsIgnoreCase("radius")) {
            rslt = handleRadius(sender, args, ctx, core);
        }
        else if (cmd.equalsIgnoreCase("pos0")) {
            rslt = handlePosN(sender, args, ctx, core, 0);
        }
        else if (cmd.equalsIgnoreCase("pos1")) {
            rslt = handlePosN(sender, args, ctx, core, 1);
        }
        else if (cmd.equalsIgnoreCase("export")) {
            rslt = handleDoExport(sender, args, ctx, core);
        }
        else if(cmd.equalsIgnoreCase("reset")) {
            rslt = handleResetExport(sender, args, ctx, core);
        }
        else if(cmd.equalsIgnoreCase("purge")) {
            rslt = handlePurgeExport(sender, args, ctx, core);
        }
        else if(cmd.equalsIgnoreCase("info")) {
            rslt = handleInfo(sender, args, ctx, core);
        }

        return rslt;
    }

    public List<String> getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) {
        /* Re-parse args - handle doublequotes */
		args = DynmapCore.parseArgs(args, sender, true);

		if (args == null || args.length <= 1) {
			return Collections.emptyList();
		}

		String cmd = args[0];

        if(cmd.equalsIgnoreCase("set")) {
            List<String> keys = new ArrayList<>(
                    Arrays.asList("x0", "x1", "y0", "y1", "z0", "z1", "world", "shader", "byChunk",
                                  "byBlockID", "byBlockIDData", "byTexture"));

            if (args.length % 2 == 0) { // Args contain only complete key value argument pairs (plus subcommand)
                // Remove previous used keys
                for (int i = 1; i < args.length; i += 2) {
                    keys.remove(args[i]);
                }

                return keys;
            } else { // Incomplete key value argument pair, suggest values
                final String lastKey = args[args.length - 2];
                final String lastValue = args[args.length - 1];

                switch(lastKey) {
                    case "world":
                        return core.getWorldSuggestions(lastValue);
                    case "shader":
                        return MapManager.mapman.hdmapman.shaders.keySet().stream()
                                .filter(value -> value.startsWith(lastValue))
                                .collect(Collectors.toList());
                    case "byChunk":
                    case "byBlockID":
                    case "byBlockIDData":
                    case "byTexture":
                        return Stream.of("true", "false")
                                .filter(value -> value.startsWith(lastValue))
                                .collect(Collectors.toList());
                }
            }
        }

        return Collections.emptyList();
    }

    private boolean handleInfo(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        sender.sendMessage(String.format("Bounds: <%s,%s,%s> - <%s,%s,%s> on world '%s'", val(ctx.xmin), val(ctx.ymin), val(ctx.zmin),
                val(ctx.xmax), val(ctx.ymax), val(ctx.zmax), ctx.world));
        sender.sendMessage(String.format("groups: byChunk: %b, byBlockID: %b, byBlockIDData: %b, byTexture: %b", ctx.groupByChunk, ctx.groupByBlockID, ctx.groupByBlockIDData, ctx.groupByTexture));
        return true;
    }
    
    private boolean handleSetExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        if (args.length < 3) {
            sender.sendMessage(String.format("Bounds: <%s,%s,%s> - <%s,%s,%s> on world '%s'", val(ctx.xmin), val(ctx.ymin), val(ctx.zmin),
                    val(ctx.xmax), val(ctx.ymax), val(ctx.zmax), ctx.world));
            return true;
        }
        for (int i = 1; i < (args.length-1); i += 2) {
            try {
                if (args[i].equals("x0")) {
                    ctx.xmin = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("x1")) {
                    ctx.xmax = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("y0")) {
                    ctx.ymin = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("y1")) {
                    ctx.ymax = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("z0")) {
                    ctx.zmin = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("z1")) {
                    ctx.zmax = Integer.parseInt(args[i+1]);
                }
                else if (args[i].equals("world")) {
                    DynmapWorld w = core.getWorld(args[i+1]);
                    if (w != null) {
                        ctx.world = args[i+1];
                    }
                    else {
                        sender.sendMessage("Invalid world '" + args[i+1] + "'");
                        return true;
                    }
                }
                else if (args[i].equals("shader")) {
                    HDShader s = MapManager.mapman.hdmapman.shaders.get(args[i+1]);
                    if (s == null) {
                        sender.sendMessage("Unknown shader '" + args[i+1] + "'");
                        return true;
                    }
                    ctx.shader = args[i+1];
                }
                else if (args[i].equals("byChunk")) {
                    ctx.groupByChunk = args[i+1].equalsIgnoreCase("true");
                }
                else if (args[i].equals("byBlockID")) {
                    ctx.groupByBlockID = args[i+1].equalsIgnoreCase("true");
                }
                else if (args[i].equals("byBlockIDData")) {
                    ctx.groupByBlockIDData = args[i+1].equalsIgnoreCase("true");
                }
                else if (args[i].equals("byTexture")) {
                    ctx.groupByTexture = args[i+1].equalsIgnoreCase("true");
                }
                else {  // Unknown setting
                    sender.sendMessage("Unknown setting '" + args[i] + "'");
                    return true;
                }
            } catch (NumberFormatException nfx) {
                sender.sendMessage("Invalid value for '" + args[i] + "' - " + args[i+1]);
                return true;
            }
        }
        return handleInfo(sender, args, ctx, core);
    }

    private boolean handleRadius(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        if ((sender instanceof DynmapPlayer) == false) {    // Not a player
            sender.sendMessage("Only usable by player");
            return true;
        }
        DynmapPlayer plyr = (DynmapPlayer) sender;
        DynmapLocation loc = plyr.getLocation();
        DynmapWorld world = null;
        if (loc != null) {
            world = core.getWorld(loc.world);
        }
        if (world == null) {
            sender.sendMessage("Location not found for player");
            return true;
        }
        int radius = 16;
        if (args.length >= 2) {
            try {
                radius = Integer.parseInt(args[1]);
                if (radius < 0) {
                    sender.sendMessage("Invalid radius - " + args[1]);
                    return true;
                }
            } catch (NumberFormatException nfx) {
                sender.sendMessage("Invalid radius - " + args[1]);
                return true;
            }
        }
        ctx.xmin = (int)Math.floor(loc.x) - radius;
        ctx.xmax = (int)Math.ceil(loc.x) + radius;
        ctx.zmin = (int)Math.floor(loc.z) - radius;
        ctx.zmax = (int)Math.ceil(loc.z) + radius;
        ctx.ymin = world.minY;
        ctx.ymax = world.worldheight - 1;
        ctx.world = world.getName();
        return handleInfo(sender, args, ctx, core);
    }

    private boolean handlePosN(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core, int n) {
        if ((sender instanceof DynmapPlayer) == false) {    // Not a player
            sender.sendMessage("Only usable by player");
            return true;
        }
        DynmapPlayer plyr = (DynmapPlayer) sender;
        DynmapLocation loc = plyr.getLocation();
        DynmapWorld world = null;
        if (loc != null) {
            world = core.getWorld(loc.world);
        }
        if (world == null) {
            sender.sendMessage("Location not found for player");
            return true;
        }
        if (n == 0) {
            ctx.xmin = (int)Math.floor(loc.x);
            ctx.ymin = (int)Math.floor(loc.y);
            ctx.zmin = (int)Math.floor(loc.z);
        }
        else {
            ctx.xmax = (int)Math.floor(loc.x);
            ctx.ymax = (int)Math.floor(loc.y);
            ctx.zmax = (int)Math.floor(loc.z);
        }
        ctx.world = world.getName();
        
        return handleInfo(sender, args, ctx, core);
    }
    
    private boolean handleDoExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        if ((ctx.world == null) || (ctx.xmin == Integer.MIN_VALUE) || (ctx.ymin == Integer.MIN_VALUE) || 
            (ctx.zmin == Integer.MIN_VALUE) || (ctx.xmax == Integer.MIN_VALUE) || (ctx.ymax == Integer.MIN_VALUE) ||
            (ctx.zmax == Integer.MIN_VALUE)) {
            sender.sendMessage("Bounds not set");
            return true;
        }
        DynmapWorld w = core.getWorld(ctx.world);
        if (w == null) {
            sender.sendMessage("Invalid world - " + ctx.world);
            return true;
        }
        HDShader s = MapManager.mapman.hdmapman.shaders.get(ctx.shader);
        if (s == null) {
            sender.sendMessage("Invalid shader - " + ctx.shader);
            return true;
        }
        
        String basename = "dynmapexp";
        if (args.length > 1) {
            basename = args[1];
        }
        basename = basename.replace('/', '_');
        basename = basename.replace('\\', '_');
        File f = new File(core.getExportFolder(), basename + ".zip");
        int idx = 0;
        String finalBasename = basename;
        while (f.exists()) {
            idx++;
            finalBasename = basename + "_" + idx;
            f = new File(core.getExportFolder(), finalBasename + ".zip");
        }
        sender.sendMessage("Exporting to " + f.getPath());
        
        OBJExport exp = new OBJExport(f, s, w, core, finalBasename);
        exp.setRenderBounds(ctx.xmin, ctx.ymin, ctx.zmin, ctx.xmax, ctx.ymax, ctx.zmax);
        exp.setGroupEnabled(OBJExport.GROUP_CHUNK, ctx.groupByChunk);
        exp.setGroupEnabled(OBJExport.GROUP_TEXTURE, ctx.groupByTexture);
        exp.setGroupEnabled(OBJExport.GROUP_BLOCKID, ctx.groupByBlockID);
        exp.setGroupEnabled(OBJExport.GROUP_BLOCKIDMETA, ctx.groupByBlockIDData);
        MapManager.mapman.startOBJExport(exp, sender);
        
        return true;
    }
    
    private boolean handleResetExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        sessions.remove(getContextID(sender));
        return true;
    }
    
    private boolean handlePurgeExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
        if (args.length > 1) {
            String basename = args[1];
            basename = basename.replace('/', '_');
            basename = basename.replace('\\', '_');
            File f = new File(core.getExportFolder(), basename + ".zip");
            if (f.exists()) {
                f.delete();
                sender.sendMessage("Removed " + f.getPath());
            }
            else {
                sender.sendMessage(f.getPath() + " not found");
            }
        }
        return true;
    }
    
    private String val(int v) {
        if (v == Integer.MIN_VALUE)
            return "N/A";
        else
            return Integer.toString(v);
    }
}
