package mapwriter.gui;

import java.awt.Point;

import mapwriter.Mw;
import mapwriter.MwUtil;
import mapwriter.api.IMwDataProvider;
import mapwriter.api.MwAPI;
import mapwriter.forge.MwKeyHandler;
import mapwriter.map.MapRenderer;
import mapwriter.map.MapView;
import mapwriter.map.Marker;
import mapwriter.map.mapmode.FullScreenMapMode;
import mapwriter.map.mapmode.MapMode;
import mapwriter.tasks.MergeTask;
import mapwriter.tasks.RebuildRegionsTask;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

@SideOnly(Side.CLIENT)
public class MwGui extends GuiScreen {
	private Mw mw;
    private MapMode mapMode;
    private MapView mapView;
    private MapRenderer map;
    
	private final static double PAN_FACTOR = 0.3D;
    
    private static final int menuY = 5;
    private static final int menuX = 5;
    
    private int mouseLeftHeld = 0;
    //private int mouseRightHeld = 0;
    //private int mouseMiddleHeld = 0;
    private int mouseLeftDragStartX = 0;
    private int mouseLeftDragStartY = 0;
    private double viewXStart;
    private double viewZStart;
    private Marker movingMarker = null;
    private int movingMarkerXStart = 0;
    private int movingMarkerZStart = 0;
    private int mouseBlockX = 0;
    private int mouseBlockY = 0;
    private int mouseBlockZ = 0;

    private int exit = 0;
    
    private Label helpLabel;
    private Label optionsLabel;
    private Label dimensionLabel;
    private Label groupLabel;
    private Label overlayLabel;
    
    class Label {
    	int x = 0, y = 0, w = 1, h = 12;
    	public Label() {
    	}
    	
    	public void draw(int x, int y, String s) {
    		this.x = x;
    		this.y = y;
    		this.w = MwGui.this.fontRendererObj.getStringWidth(s) + 4;
    		MwGui.drawRect(this.x, this.y, this.x + this.w, this.y + this.h, 0x80000000);
    		MwGui.this.drawString(MwGui.this.fontRendererObj, s, this.x + 2, this.y + 2, 0xffffff);
    	}
    	
    	public void drawToRightOf(Label label, String s) {
    		this.draw(label.x + label.w + 5, label.y, s);
    	}
    	
    	public boolean posWithin(int x, int y) {
    		return (x >= this.x) && (y >= this.y) && (x <= (this.x + this.w)) && (y <= (this.y + this.h));
    	}
    }
    
    public MwGui(Mw mw) {
    	this.mw = mw;
    	this.mapMode = new FullScreenMapMode(mw.config);
    	this.mapView = new MapView(this.mw);
    	this.map = new MapRenderer(this.mw, this.mapMode, this.mapView);
    	
    	this.mapView.setDimension(this.mw.miniMap.view.getDimension());
    	this.mapView.setViewCentreScaled(this.mw.playerX, this.mw.playerZ, this.mw.playerDimension);
    	this.mapView.setZoomLevel(0);
    	
    	this.helpLabel = new Label();
    	this.optionsLabel = new Label();
    	this.dimensionLabel = new Label();
    	this.groupLabel = new Label();
    	this.overlayLabel = new Label();
    }

    public MwGui(Mw mw, int dim, int x, int z){
    	this(mw);
    	this.mapView.setDimension(dim);
    	this.mapView.setViewCentreScaled(x, z, dim);
    	this.mapView.setZoomLevel(0);    	
    }
    
    // called when gui is displayed and every time the screen
    // is resized
    public void initGui() {
    }

    // called when a button is pressed
    protected void actionPerformed(GuiButton button) {
    	
    }
    
    public void exitGui() {
    	//MwUtil.log("closing GUI");
    	// set the mini map dimension to the GUI map dimension when closing
    	this.mw.miniMap.view.setDimension(this.mapView.getDimension());
    	this.mapMode.close();
    	Keyboard.enableRepeatEvents(false);
    	this.mc.displayGuiScreen((GuiScreen) null);
        this.mc.setIngameFocus();
        this.mc.getSoundHandler().resumeSounds();
    }
    
    // get a marker near the specified block pos if it exists.
    // the maxDistance is based on the view width so that you need to click closer
    // to a marker when zoomed in to select it.
    public Marker getMarkerNearScreenPos(int x, int y) {
    	Marker nearMarker = null;
        for (Marker marker : this.mw.markerManager.visibleMarkerList) {
        	if (marker.screenPos != null) {
	            if (marker.screenPos.distanceSq(x, y) < 6.0) {
	            	nearMarker = marker;
	            }
        	}
        }
        return nearMarker;
    }
    
    public int getHeightAtBlockPos(int bX, int bZ) {
    	int bY = 0;
    	int worldDimension = this.mw.mc.theWorld.provider.dimensionId;
    	if ((worldDimension == this.mapView.getDimension()) && (worldDimension != -1)) {
    		bY = this.mw.mc.theWorld.getChunkFromBlockCoords(bX, bZ).getHeightValue(bX & 0xf, bZ & 0xf);
    	}
    	return bY;
    }
    
    public boolean isPlayerNearScreenPos(int x, int y) {
    	Point.Double p = this.map.playerArrowScreenPos;
        return p.distanceSq(x, y) < 9.0;
    }
    
    public void deleteSelectedMarker() {
    	if (this.mw.markerManager.selectedMarker != null) {
    		//MwUtil.log("deleting marker %s", this.mw.markerManager.selectedMarker.name);
    		this.mw.markerManager.delMarker(this.mw.markerManager.selectedMarker);
    		this.mw.markerManager.update();
    		this.mw.markerManager.selectedMarker = null;
    	}
    }
    
    public void mergeMapViewToImage() {
			this.mw.chunkManager.saveChunks();
			this.mw.executor.addTask(new MergeTask(this.mw.regionManager,
					(int) this.mapView.getX(),
					(int) this.mapView.getZ(),
					(int) this.mapView.getWidth(),
					(int) this.mapView.getHeight(),
					this.mapView.getDimension(),
					this.mw.worldDir,
					this.mw.worldDir.getName()));
			
			MwUtil.printBoth("merging to '" + this.mw.worldDir.getAbsolutePath() + "'");
    }
    
    public void regenerateView() {
    	MwUtil.printBoth(String.format("regenerating %dx%d blocks starting from (%d, %d)",
				(int) this.mapView.getWidth(),
				(int) this.mapView.getHeight(),
				(int) this.mapView.getMinX(),
				(int) this.mapView.getMinZ()));
		this.mw.reloadBlockColours();
		this.mw.executor.addTask(new RebuildRegionsTask(
				this.mw,
				(int) this.mapView.getMinX(),
				(int) this.mapView.getMinZ(),
				(int) this.mapView.getWidth(),
				(int) this.mapView.getHeight(),
				this.mapView.getDimension()));
    }
    
    // c is the ascii equivalent of the key typed.
    // key is the lwjgl key code.
    protected void keyTyped(char c, int key) {
    	//MwUtil.log("MwGui.keyTyped(%c, %d)", c, key);
		switch(key) {
		case Keyboard.KEY_ESCAPE:
			this.exitGui();
			break;
			
		case Keyboard.KEY_DELETE:
        	this.deleteSelectedMarker();	        	
        	break;
        	
		case Keyboard.KEY_SPACE:
        	// next marker group
        	this.mw.markerManager.nextGroup();
        	this.mw.markerManager.update();
        	break;
        	
		case Keyboard.KEY_C:
        	// cycle selected marker colour
        	if (this.mw.markerManager.selectedMarker != null) {
        		this.mw.markerManager.selectedMarker.colourNext();
        	}
        	break;
        
		case Keyboard.KEY_N:
        	// select next visible marker
        	this.mw.markerManager.selectNextMarker();
        	break;
        	
		case Keyboard.KEY_HOME:
        	// centre map on player
        	this.mapView.setViewCentreScaled(this.mw.playerX, this.mw.playerZ, this.mw.playerDimension);
        	break;
        
		case Keyboard.KEY_END:
        	// centre map on selected marker
        	if (this.mw.markerManager.selectedMarker != null) {
        		this.mapView.setViewCentreScaled(
        			this.mw.markerManager.selectedMarker.x,
        			this.mw.markerManager.selectedMarker.z,
        			0
        		);
        	}
        	break;
        	
		case Keyboard.KEY_P:
        	this.mergeMapViewToImage();
			this.exitGui();
			break;
			
		case Keyboard.KEY_T:
        	if (this.mw.markerManager.selectedMarker != null) {
        		this.mw.teleportToMarker(this.mw.markerManager.selectedMarker);
        		this.exitGui();
        	} else {
        		this.mc.displayGuiScreen(
        			new MwGuiTeleportDialog(
        				this,
        				this.mw,
        				this.mapView,
        				this.mouseBlockX,
        				this.mw.defaultTeleportHeight,
        				this.mouseBlockZ
        			)
        		);
        	}
        	break;
		
		case Keyboard.KEY_LEFT:
			this.mapView.panView(-PAN_FACTOR, 0);
			break;
		case Keyboard.KEY_RIGHT:
			this.mapView.panView(PAN_FACTOR, 0);
			break;
		case Keyboard.KEY_UP:
			this.mapView.panView(0, -PAN_FACTOR);
			break;
		case Keyboard.KEY_DOWN:
			this.mapView.panView(0, PAN_FACTOR);
			break;
		
		case Keyboard.KEY_R:
			this.regenerateView();
			this.exitGui();
			break;
			
		//case Keyboard.KEY_9:
		//	MwUtil.log("refreshing maptexture");
		//	this.mw.mapTexture.updateTexture();
		//	break;
		
		default:
			if (key == MwKeyHandler.keyMapGui.getKeyCode()) {
				// exit on the next tick
    			this.exit = 1;
    		} else if (key == MwKeyHandler.keyZoomIn.getKeyCode()) {
    			this.mapView.adjustZoomLevel(-1);
    		} else if (key == MwKeyHandler.keyZoomOut.getKeyCode()) {
    			this.mapView.adjustZoomLevel(1);
    		} else if (key == MwKeyHandler.keyNextGroup.getKeyCode()) {
    			this.mw.markerManager.nextGroup();
	        	this.mw.markerManager.update();
    		} else if (key == MwKeyHandler.keyUndergroundMode.getKeyCode()) {
    			this.mw.toggleUndergroundMode();
    			this.mapView.setUndergroundMode(this.mw.undergroundMode);
			}
			break;
        }
    }
    
    // override GuiScreen's handleMouseInput to process
    // the scroll wheel.
    @Override
    public void handleMouseInput() {
    	if (MwAPI.getCurrentDataProvider() != null && MwAPI.getCurrentDataProvider().onMouseInput(this.mapView, this.mapMode))
    		return;
    	
    	int x = Mouse.getEventX() * this.width / this.mc.displayWidth;
        int y = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1;
    	int direction = Mouse.getEventDWheel();
    	if (direction != 0) {
    		this.mouseDWheelScrolled(x, y, direction);
    	}
    	super.handleMouseInput();
    }
    
    // mouse button clicked. 0 = LMB, 1 = RMB, 2 = MMB
    protected void mouseClicked(int x, int y, int button) {
    	//MwUtil.log("MwGui.mouseClicked(%d, %d, %d)", x, y, button);
    	
    	//int bX = this.mouseToBlockX(x);
		//int bZ = this.mouseToBlockZ(y);
		//int bY = this.getHeightAtBlockPos(bX, bZ);
    	
    	Marker marker = this.getMarkerNearScreenPos(x, y);
    	Marker prevMarker = this.mw.markerManager.selectedMarker;
    	
    	if (button == 0) {
    		if (this.dimensionLabel.posWithin(x, y)) {
    			this.mc.displayGuiScreen(
        			new MwGuiDimensionDialog(
        				this,
        				this.mw,
        				this.mapView,
        				this.mapView.getDimension()
        			)
        		);
    		} else if (this.optionsLabel.posWithin(x, y)) {
    			this.mc.displayGuiScreen(new MwGuiOptions(this, this.mw));
    		} else {
	    		this.mouseLeftHeld = 1;
	    		this.mouseLeftDragStartX = x;
	    		this.mouseLeftDragStartY = y;
	    		this.mw.markerManager.selectedMarker = marker;
	    		
	    		if ((marker != null) && (prevMarker == marker)) {
	    			// clicked previously selected marker.
	    			// start moving the marker.
	    			this.movingMarker = marker;
	    			this.movingMarkerXStart = marker.x;
	    			this.movingMarkerZStart = marker.z;
	    		}
    		}
    		
    	} else if (button == 1) {
    		//this.mouseRightHeld = 1;
			if ((marker != null) && (prevMarker == marker)) {
    			// right clicked previously selected marker.
    			// edit the marker
				this.mc.displayGuiScreen(
        			new MwGuiMarkerDialog(
        				this,
        				this.mw.markerManager,
        				marker
        			)
        		);
    			
    		} else if (marker == null) {
    			// open new marker dialog
    			String group = this.mw.markerManager.getVisibleGroupName();
        		if (group.equals("none")) {
        			group = "group";
        		}
        		
        		int mx, my, mz;
        		if (this.isPlayerNearScreenPos(x, y)) {
        			// marker at player's locations
        			mx = this.mw.playerXInt;
        			my = this.mw.playerYInt;
        			mz = this.mw.playerZInt;
        		
        		} else {
        			// marker at mouse pointer location
        			mx = this.mouseBlockX;
        			my = (this.mouseBlockY > 0) ? this.mouseBlockY : this.mw.defaultTeleportHeight;
        			mz = this.mouseBlockZ;
        		}
        		this.mc.displayGuiScreen(
        			new MwGuiMarkerDialog(
        				this,
        				this.mw.markerManager,
        				"",
        				group,
        				mx, my, mz,
        				this.mapView.getDimension()
        			)
        		);
    		}
    	}

    	else if (button == 2) {
    		Point blockPoint = this.mapMode.screenXYtoBlockXZ(this.mapView, x, y);
	
    		IMwDataProvider provider = MwAPI.getCurrentDataProvider();
    			if (provider != null)    			
    				provider.onMiddleClick(this.mapView.getDimension(), blockPoint.x, blockPoint.y, this.mapView);
    	}
    	
    	this.viewXStart = this.mapView.getX();
		this.viewZStart = this.mapView.getZ();
		//this.viewSizeStart = this.mapManager.getViewSize();
    }

    // mouse button released. 0 = LMB, 1 = RMB, 2 = MMB
    // not called on mouse movement.
    protected void mouseMovedOrUp(int x, int y, int button) {
    	//MwUtil.log("MwGui.mouseMovedOrUp(%d, %d, %d)", x, y, button);
    	if (button == 0) {
    		this.mouseLeftHeld = 0;
    		this.movingMarker = null;
    	} else if (button == 1) {
    		//this.mouseRightHeld = 0;
    	}
    }
    
    // zoom on mouse direction wheel scroll
    public void mouseDWheelScrolled(int x, int y, int direction) {
    	Marker marker = this.getMarkerNearScreenPos(x, y);
    	if ((marker != null) && (marker == this.mw.markerManager.selectedMarker)) {
    		if (direction > 0) {
    			marker.colourNext();
    		} else {
    			marker.colourPrev();
    		}
    		
    	} else if (this.dimensionLabel.posWithin(x, y)) {
    		int n = (direction > 0) ? 1 : -1;
	    	this.mapView.nextDimension(this.mw.dimensionList, n);
	    	
    	} else if (this.groupLabel.posWithin(x, y)) {
    		int n = (direction > 0) ? 1 : -1;
    		this.mw.markerManager.nextGroup(n);
    		this.mw.markerManager.update();
    	} else if (this.overlayLabel.posWithin(x, y)) {
    		int n = (direction > 0) ? 1 : -1;
			if (MwAPI.getCurrentDataProvider() != null)
				MwAPI.getCurrentDataProvider().onOverlayDeactivated(this.mapView);    		
    		
    		if (n == 1)
    			MwAPI.setNextProvider();
    		else
    			MwAPI.setPrevProvider();

			if (MwAPI.getCurrentDataProvider() != null)
				MwAPI.getCurrentDataProvider().onOverlayActivated(this.mapView);    		
    		
    	} else {
    		int zF = (direction > 0) ? -1 : 1;
    		this.mapView.zoomToPoint(this.mapView.getZoomLevel() + zF, this.mouseBlockX, this.mouseBlockZ);
    	}
    }

    // called every frame
    public void updateScreen() {
    	//MwUtil.log("MwGui.updateScreen() " + Thread.currentThread().getName());
    	// need to wait one tick before exiting so that the game doesn't
    	// handle the 'm' key and re-open the gui.
    	// there should be a better way.
    	if (this.exit > 0) {
    		this.exit++;
    	}
    	if (this.exit > 2) {
    		this.exitGui();
    	}
        super.updateScreen();
    }
    
    public void drawStatus(int bX, int bY, int bZ) {
    	 String s;
    	 if (bY != 0) {
          	s = String.format("cursor: (%d, %d, %d)", bX, bY, bZ);
          } else {
          	s = String.format("cursor: (%d, ?, %d)", bX, bZ);
          }
    	 if (this.mc.theWorld != null) {
    		 if (!this.mc.theWorld.getChunkFromBlockCoords(bX, bZ).isEmpty()) {
    			 s += String.format(", biome: %s", this.mc.theWorld.getBiomeGenForCoords(bX, bZ).biomeName);
    		 }
    	 }
         
         /*if (this.mw.markerManager.selectedMarker != null) {
         	s += ", current marker: " + this.mw.markerManager.selectedMarker.name;
         }*/
    	 
    	 IMwDataProvider provider = MwAPI.getCurrentDataProvider();
 			if (provider != null)    	 
 				s += provider.getStatusString(this.mapView.getDimension(), bX, bY, bZ);
    	 
         drawRect(10, this.height - 21, this.width - 20, this.height - 6, 0x80000000);
         this.drawCenteredString(this.fontRendererObj,
         		s, this.width / 2, this.height - 18, 0xffffff);
    }
    
    public void drawHelp() {
    	drawRect(10, 20, this.width - 20, this.height - 30, 0x80000000);
    	this.fontRendererObj.drawSplitString(
    			"Keys:\n\n" + 
    			"  Space\n" +
    			"  Delete\n" +
    			"  C\n" +
    			"  Home\n" +
    			"  End\n" +
    			"  N\n" +
    			"  T\n" +
    			"  P\n" +
    			"  R\n" +
    			"  U\n\n" +
    			"Left click drag or arrow keys pan the map.\n" +
    			"Mouse wheel or Page Up/Down zooms map.\n" +
    			"Right click map to create a new marker.\n" +
    			"Left click drag a selected marker to move it.\n" +
    			"Mouse wheel over selected marker to cycle colour.\n" + 
    			"Mouse wheel over dimension or group box to cycle.\n",
    			15, 24, this.width - 30, 0xffffff);
    	this.fontRendererObj.drawSplitString(
    			"| Next marker group\n" +
    			"| Delete selected marker\n" +
    			"| Cycle selected marker colour\n" +
    			"| Centre map on player\n" +
    			"| Centre map on selected marker\n" +
    			"| Select next marker\n" +
    			"| Teleport to cursor or selected marker\n" +
    			"| Save PNG of visible map area\n" +
    			"| Regenerate visible map area from region files\n" +
    			"| Underground map mode\n",
    			75, 42, this.width - 90, 0xffffff);
    }
    
    public void drawMouseOverHint(int x, int y, String title, int mX, int mY, int mZ) {
    	String desc = String.format("(%d, %d, %d)", mX, mY, mZ);
    	int stringW = Math.max(
    			this.fontRendererObj.getStringWidth(title),
    			this.fontRendererObj.getStringWidth(desc));
    	
    	x = Math.min(x, this.width - (stringW + 16));
    	y = Math.min(Math.max(10, y), this.height - 14);
    	
    	drawRect(x + 8, y - 10, x + stringW + 16, y + 14, 0x80000000);
    	this.drawString(this.fontRendererObj,
    			title,
    			x + 10, y - 8, 0xffffff);
    	this.drawString(this.fontRendererObj,
    			desc,
    			x + 10, y + 4, 0xcccccc);
    }
    
    // also called every frame
    public void drawScreen(int mouseX, int mouseY, float f) {
    	
        this.drawDefaultBackground();
        double xOffset = 0.0;
        double yOffset = 0.0;
        //double zoomFactor = 1.0;

    	if (this.mouseLeftHeld > 2) {
    		xOffset = (this.mouseLeftDragStartX - mouseX) * this.mapView.getWidth() / this.mapMode.w;
    		yOffset = (this.mouseLeftDragStartY - mouseY) * this.mapView.getHeight() / this.mapMode.h;
    		
    		if (this.movingMarker != null) {
    			double scale = this.mapView.getDimensionScaling(this.movingMarker.dimension);
        		this.movingMarker.x = this.movingMarkerXStart - (int) (xOffset / scale);
        		this.movingMarker.z = this.movingMarkerZStart - (int) (yOffset / scale);
    		} else {
	    		this.mapView.setViewCentre(this.viewXStart + xOffset, this.viewZStart + yOffset);
    		}
    	}
    	
        if (this.mouseLeftHeld > 0) {
        	this.mouseLeftHeld++;
        }
        
        // draw the map
        this.map.draw();
        
        // let the renderEngine know we have changed the texture.
    	//this.mc.renderEngine.resetBoundTexture();
        
        // get the block the mouse is currently hovering over
    	Point p = this.mapMode.screenXYtoBlockXZ(this.mapView, mouseX, mouseY);
        this.mouseBlockX = p.x;
        this.mouseBlockZ = p.y;
        this.mouseBlockY = this.getHeightAtBlockPos(this.mouseBlockX, this.mouseBlockZ);
        
        // draw name of marker under mouse cursor
        Marker marker = this.getMarkerNearScreenPos(mouseX, mouseY);
        if (marker != null) {
        	this.drawMouseOverHint(mouseX, mouseY, marker.name, marker.x, marker.y, marker.z);
        }
        
        // draw name of player under mouse cursor
        if (this.isPlayerNearScreenPos(mouseX, mouseY)) {
        	this.drawMouseOverHint(mouseX, mouseY, this.mc.thePlayer.getDisplayName(),
        			this.mw.playerXInt,
					this.mw.playerYInt,
					this.mw.playerZInt);
        }
        
        // draw status message
       this.drawStatus(this.mouseBlockX, this.mouseBlockY, this.mouseBlockZ);
        
        // draw labels
       this.helpLabel.draw(menuX, menuY, "[help]");
       this.optionsLabel.drawToRightOf(this.helpLabel, "[options]");
       String dimString = String.format("[dimension: %d]", this.mapView.getDimension());
       this.dimensionLabel.drawToRightOf(this.optionsLabel, dimString);
       String groupString = String.format("[group: %s]", this.mw.markerManager.getVisibleGroupName());
       this.groupLabel.drawToRightOf(this.dimensionLabel, groupString);
       String overlayString = String.format("[overlay : %s]", MwAPI.getCurrentProviderName());
       this.overlayLabel.drawToRightOf(this.groupLabel, overlayString);
        
        // help message on mouse over
		if (this.helpLabel.posWithin(mouseX, mouseY)) {
		    this.drawHelp();
		}
        
        super.drawScreen(mouseX, mouseY, f);
    }
}