/*
Copyright (C) 2005 David Green <green@couchpotato.net>
All Rights Reserved.

This file is part of Aelfengard.

Aelfengard is proprietary software. You may not redistribute it without
prior written permission from the copyright holder.
*/

package common.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JPanel;

import common.CommonUtils;
import common.Direction;
import common.PubRoom;
import common.RoomType;

/**
 * The visual component that displays the map for the user.
 * 
 * @author David Green <green@couchpotato.net>
 *
 */
public class GameMap extends JPanel {

    private static final long serialVersionUID = 3833468396569506614L;
    
    //                               |--CELLSIZE--||--CELLSIZE--|
    //                                 |ROOMSIZE|    |ROOMSIZE|
    //                                 **********    **********
    //                              -- *  ----  *    *   __   * --
    //          TRACKINGOVALSIZE --|   * /    \ *----*  /  \  *   |-- OVALSIZE
    //                             |   * \    / *    *  \__/  * --
    //                              -- *  ----  *    *        *
    //    TRACKINGOVALBORDERSIZE --|   **********    **********
    //                              --          |_||___| 
    //                                           |   |
    //                                  BORDERSIZE   OVALBORDERSIZE
    
    private static final int ROOM_SIZE = 15;
    private static final int CELL_SIZE = 25;
    private static final int OVAL_SIZE = 9;
    private static final int TRACKING_OVAL_SIZE = 13;
    private static final int BORDER_SIZE = (CELL_SIZE - ROOM_SIZE) / 2;
    private static final int OVAL_BORDER_SIZE = (CELL_SIZE - OVAL_SIZE) / 2;
    private static final int TRACKING_OVAL_BORDER_SIZE = (CELL_SIZE - TRACKING_OVAL_SIZE) / 2;
    private static final Color LINE_COLOR = new Color(142, 105, 62);
    
    // This much space will be left around the outermost rooms.
    private static final int MAP_BORDER = 100;
    
    private int minX;
    private int minY;
    private int maxX;
    private int maxY;
    private Map<Point,PubRoom> rooms = new HashMap<Point,PubRoom>();
    private Point currentLocation;
    private String areaName;
    
    private Map<String,Color> areaColors = new HashMap<String,Color>();
    
    private Map<Integer,Point> roomForPlayer;
    private Map<Point,Integer> countForRoom;
    
    public GameMap() {
        setBackground(Color.white);
        areaColors.put("cshome", new Color(200, 255, 200));
        areaColors.put("exile", Color.red);
    }
    
    public void init(String newAreaName, int newMinX, int newMinY, int newMaxX, int newMaxY, Map<Point,PubRoom> newRooms) {
        roomForPlayer = new HashMap<Integer,Point>();
        countForRoom = new HashMap<Point,Integer>();
        this.areaName = newAreaName;
        this.minX = newMinX;
        this.minY = newMinY;
        this.maxX = newMaxX;
        this.maxY = newMaxY;
        this.rooms = newRooms;
        Color color = areaColors.get(areaName);
        if (color != null) {
            setBackground(color);
        }
        else {
            setBackground(Color.white);
        }
        setSizes();
        repaint();
    }
    
    private void setSizes() {
        // TODO: Are these needed?
        int width = (maxX - minX + 1) * CELL_SIZE + MAP_BORDER * 2;
        int height = (maxY - minY + 1) * CELL_SIZE + MAP_BORDER * 2;
        setMinimumSize(new Dimension(width, height));
        setMaximumSize(new Dimension(width, height));
        setPreferredSize(new Dimension(width, height));
        setSize(new Dimension(width, height));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        // TODO: Only draw within clipping area
        g2.setFont(g2.getFont().deriveFont(Font.BOLD, 10.0f));
        for (int x = minX; x <= maxX; x++) {
            for (int y = minY; y <= maxY; y++) {
                Point p = new Point(x, y);
                PubRoom room = rooms.get(p);
                if (room != null) {
                    int dx = x - minX;
                    int dy = y - minY;
                    //g2.setColor(Color.cyan);
                    //g2.fillRect(dx * CELL_SIZE, dy * CELL_SIZE, CELL_SIZE, CELL_SIZE);
                    g2.setColor(room.roomType.getColor());
                    g2.fillRect(dx * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                ROOM_SIZE, ROOM_SIZE);
                    g2.setColor(LINE_COLOR);
                    g2.drawRect(dx * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                            dy * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                            ROOM_SIZE, ROOM_SIZE);
                    if (currentLocation != null && 
                            x == currentLocation.x &&
                            y == currentLocation.y) {
                        g2.setColor(Color.blue);
                        g2.fillOval(dx * CELL_SIZE + 
                                OVAL_BORDER_SIZE + 
                                MAP_BORDER,
                                dy * CELL_SIZE + 
                                OVAL_BORDER_SIZE + 
                                MAP_BORDER,
                                OVAL_SIZE, 
                                OVAL_SIZE);
                        g2.setColor(Color.white);
                        g2.drawOval(dx * CELL_SIZE + 
                                OVAL_BORDER_SIZE + 
                                MAP_BORDER,
                                dy * CELL_SIZE + 
                                OVAL_BORDER_SIZE + 
                                MAP_BORDER,
                                OVAL_SIZE, 
                                OVAL_SIZE);
                    }
                    else {
                        Integer count = countForRoom.get(p);
                        if (count != null) {
                            int rx = dx * CELL_SIZE + TRACKING_OVAL_BORDER_SIZE + MAP_BORDER;
                            int ry = dy * CELL_SIZE + TRACKING_OVAL_BORDER_SIZE + MAP_BORDER;
                            g2.setColor(Color.white);
                            g2.fillOval(rx, ry, TRACKING_OVAL_SIZE, TRACKING_OVAL_SIZE);
                            g2.setColor(Color.darkGray);
                            g2.drawOval(rx, ry, TRACKING_OVAL_SIZE, TRACKING_OVAL_SIZE);

                            String s = count.toString();
                            FontMetrics metrics = g2.getFontMetrics();
                            Rectangle2D bounds = metrics.getStringBounds(s, g2);
                            bounds.setRect(
                                    bounds.getX(), 
                                    bounds.getY(), 
                                    bounds.getWidth(), 
                                    metrics.getAscent());
                            double sx = (CELL_SIZE - bounds.getWidth()) / 2.0;
                            double sy = (CELL_SIZE - bounds.getHeight()) / 2.0;
                            sy += bounds.getHeight();
                            
                            sx += dx * CELL_SIZE + MAP_BORDER;
                            sy += dy * CELL_SIZE + MAP_BORDER; 
                            //System.out.println("sx = " + sx + ", sy = " + sy);
                            g2.setColor(Color.black);
                            g2.drawString(s, (int) sx, (int) sy);
                            //g2.setColor(Color.red);
                            //g2.drawRect((int) sx, (int) (sy - bounds.getHeight()), (int) bounds.getWidth(), (int) bounds.getHeight());
                        }
                    }
                    // Now draw exits
                    g2.setColor(LINE_COLOR);
                    RoomType northType = roomType(x, y, Direction.NORTH); 
                    RoomType northeastType = roomType(x, y, Direction.NORTHEAST); 
                    RoomType eastType = roomType(x, y, Direction.EAST); 
                    RoomType southeastType = roomType(x, y, Direction.SOUTHEAST); 
                    RoomType southType = roomType(x, y, Direction.SOUTH); 
                    RoomType southwestType = roomType(x, y, Direction.SOUTHWEST); 
                    RoomType westType = roomType(x, y, Direction.WEST); 
                    RoomType northwestType = roomType(x, y, Direction.NORTHWEST);
                    
                    // test for existance of adjacent rooms
                    boolean north = northType != null;
                    boolean northeast = northeastType != null;
                    boolean east = eastType != null;
                    boolean southeast = southeastType != null;
                    boolean south = southType != null;
                    boolean southwest = southwestType != null;
                    boolean west = westType != null;
                    boolean northwest = northwestType != null;
                    
                    // draw connecting lines where valid
                    if (north) {
                        g2.drawLine(dx * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                    dy * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                    dx * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                    dy * CELL_SIZE + MAP_BORDER);
                    }
                    if (northeast && (CommonUtils.isConnected(room.roomType, northeastType) || !(north ^ east))) {
                        g2.drawLine(dx * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                    dy * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                    dx * CELL_SIZE + CELL_SIZE + MAP_BORDER,
                                    dy * CELL_SIZE + MAP_BORDER);
                    }
                    if (east) {
                        g2.drawLine(
                                dx * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                dx * CELL_SIZE + CELL_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER);
                    }
                    if (southeast && (CommonUtils.isConnected(room.roomType, southeastType) || !(south ^ east))) {
                        g2.drawLine(
                                dx * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                dx * CELL_SIZE + CELL_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE + MAP_BORDER);
                    }
                    if (south) {
                        g2.drawLine(dx * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                    dy * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                    dx * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                    dy * CELL_SIZE + CELL_SIZE + MAP_BORDER);
                    }
                    if (southwest && (CommonUtils.isConnected(room.roomType, southwestType) || !(south ^ west))) {
                        g2.drawLine(
                                dx * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE - BORDER_SIZE + MAP_BORDER,
                                dx * CELL_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE + MAP_BORDER);
                    }
                    if (west) {
                        g2.drawLine(
                                dx * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER,
                                dx * CELL_SIZE + MAP_BORDER,
                                dy * CELL_SIZE + CELL_SIZE / 2 + MAP_BORDER);
                    }
                    if (northwest && (CommonUtils.isConnected(room.roomType, northwestType) || !(north ^ west))) {
                        g2.drawLine(dx * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                    dy * CELL_SIZE + BORDER_SIZE + MAP_BORDER,
                                    dx * CELL_SIZE + MAP_BORDER,
                                    dy * CELL_SIZE + MAP_BORDER);
                    }
                }
            }
        }
    }

    /**
     * Fetch the room type of the room in the specified direction.
     * @param x the x coordinate of the current room
     * @param y the y coordinate of the current room
     * @param direction the direction to test
     * @return the room type of the room in the specified direction
     */
    private RoomType roomType(int x, int y, Direction direction) {
        Point offsets = direction.getOffsets();
        Point p = new Point(x + offsets.x, y + offsets.y);
        PubRoom room = rooms.get(p);
        return room == null ? null : room.roomType;
    }

    /**
     * Changes the current room (moving the blue oval).
     * @param p the new room
     */
    public void setCurrentRoom(Point p) {
        currentLocation = p;
        PubRoom room = rooms.get(p);
        GameClient.setRoomName(room.name);
        centerCurrentRoom();
    }
    
    /**
     * Centers the current room on the map.
     */
    public void centerCurrentRoom() {
        Rectangle rect = getVisibleRect();
        rect.x = (currentLocation.x - minX) * CELL_SIZE + CELL_SIZE / 2 - rect.width / 2 + MAP_BORDER;
        rect.y = (currentLocation.y - minY) * CELL_SIZE + CELL_SIZE / 2 - rect.height / 2 + MAP_BORDER;
        if (rect.x < 0) {
            rect.x = 0;
        }
        if (rect.y < 0) {
            rect.y = 0;
        }
        scrollRectToVisible(rect);
        repaint();
    }

    /**
     * Processes a change to a room. 
     * @param room the room that was changed
     */
    public void roomUpdated(PubRoom room) {
        Point p = new Point(room.x, room.y);
        rooms.put(p, room);
        minX = Math.min(minX, room.x);
        minY = Math.min(minY, room.y);
        maxX = Math.max(maxX, room.x);
        maxY = Math.max(maxY, room.y);
        setSizes();
        repaint();
        if (p.equals(currentLocation)) {
            // current room changed
            GameClient.setRoomName(room.name);
        }
    }

    /**
     * Removes a room from the map.
     * @param p the room to remove
     */
    public void roomDestroyed(Point p) {
        rooms.remove(p);
        repaint();
    }

    /**
     * Changes tracking info for a player.
     * 
     * @param player the player whose tracking has changed
     * @param room the player's new room
     */
    public void track(int player, Point room) {
        // remove from old room
        Point oldRoom = roomForPlayer.remove(player);
        if (oldRoom != null) {
            int count = countForRoom.get(oldRoom) - 1;
            if (count == 0) {
                countForRoom.remove(oldRoom);
            }
            else {
                countForRoom.put(oldRoom, count);
            }
        }
        // put player in new room
        if (room != null) {
            roomForPlayer.put(player, room);
            Integer count = countForRoom.get(room);
            if (count == null) {
                count = 0;
            }
            countForRoom.put(room, count + 1);
        }
        repaint();
    }

}