/*
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 server;

import java.awt.Point;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import common.CommonUtils;
import common.PubRoom;
import common.RoomType;
import common.gameevent.MapGameEvent;



public class Area implements Externalizable {

    private static final long serialVersionUID = 3762253019898722361L;
    
    private static Map<String,Area> areas = new HashMap<String,Area>();
    private static Map<String,Point> defaultRooms = new HashMap<String,Point>();
    
    private static Set<AreaListener> listeners = new HashSet<AreaListener>();
    
    static {
        defaultRooms.put("main", new Point(3, 1));
    }

    private String name;
    private Map<Point,Room> rooms = new HashMap<Point,Room>();
    private Map<Point,PubRoom> pubrooms = new HashMap<Point,PubRoom>();
    private int minX, minY, maxX, maxY;
    
    public Area() {
        // for deserialization
    }
    
    public Area(String name) {
        this.name = name.intern();
        areas.put(name, this);
        new Room(this, 0, 0, null, "Default Room", RoomType.ROAD);
    }
    
    public String getName() {
        return name;
    }
    
    public static Area findByName(String name) {
        return areas.get(name);
    }
    
    public static Collection<Area> getAreas() {
        return new ArrayList<Area>(areas.values());
    }
    
    public MapGameEvent makeMapGameEvent() {
        return new MapGameEvent(name, minX, minY, maxX, maxY, pubrooms);
    }

    public Room getDefaultRoom() {
        Point loc = defaultRooms.get(name);
        if (loc != null) {
            Room room = findByPoint(loc);
            if (room != null) {
                return room;
            }
        }
        loc = new Point(0, 0);
        Room room = findByPoint(loc);
        if (room != null) {
            return room;
        }
        return getRandomRoom();
    }
    
    public Room findByPoint(Point point) {
        return rooms.get(point);
    }

    private Room getRandomRoom() {
        int size = rooms.size();
        if (size == 0) {
            throw new FatalError("No rooms in area " + name);
        }
        int roomNumber = new Random().nextInt(size);
        Iterator<Room> iter = rooms.values().iterator();
        for (int i = 0; i < roomNumber; i++) {
            iter.next();
        }
        return iter.next();
    }

    public void addRoom(Room room) {
        assert room.getArea() == this;
        int x = room.getX();
        int y = room.getY();
        minX = Math.min(x, minX);
        minY = Math.min(y, minY);
        maxX = Math.max(x, maxX);
        maxY = Math.max(y, maxY);
        Point location = new Point(x, y);
        PubRoom pubroom = room.makePubRoom();
        rooms.put(location, room);
        pubrooms.put(location, pubroom);
        notifyRoomChanged(pubroom);
    }
    
    void removeRoom(Room room) {
        assert room.getArea() == this;
        Point location = new Point(room.getX(), room.getY());
        rooms.remove(location);
        pubrooms.remove(location);
        notifyRoomDeleted(location);
    }

    public void updateRoom(Room room) {
        assert room.getArea() == this;
        PubRoom pubroom = room.makePubRoom();
        Point loc = new Point(room.getX(), room.getY());
        rooms.put(loc, room);
        pubrooms.put(loc, pubroom);
        notifyRoomChanged(pubroom);
    }

    public int getSize() {
        return rooms.size();
    }

    public void destroy() {
        if (name.equals("main")) {
            throw new FatalError("Attempt to destroy 'main' area.");
        }
        if (rooms.size() != 1) {
            throw new FatalError("Attempt to destroy area with more than one room.");
        }
        Room other = Area.findByName("main").getDefaultRoom();
        Room room = getRandomRoom(); // should be the only room
        for (Player player : room.getPlayers()) {
            player.moveTo(other);
        }
        removeRoom(room);
        areas.remove(name);
    }

    public static void doSave(ObjectOutputStream oos) throws IOException {
        oos.writeInt(1); // protocol version
        oos.writeObject(areas);
    }
    
    public static void doLoad(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.readInt(); // protocol version
        areas = CommonUtils.uncheckedCast(ois.readObject());
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(1); // protocol version
        out.writeObject(name);
        out.writeObject(rooms);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        in.readInt(); // protocol version
        name = (String) in.readObject();
        rooms = CommonUtils.uncheckedCast(in.readObject());
        for (Map.Entry<Point,Room> entry : rooms.entrySet()) {
            Point point = entry.getKey();
            Room room = entry.getValue();
            pubrooms.put(point, room.makePubRoom());
            minX = Math.min(minX, room.getX());
            minY = Math.min(minY, room.getY());
            maxX = Math.max(maxX, room.getX());
            maxY = Math.max(maxY, room.getY());
        }
    }
    
    public void addAreaListener(AreaListener l) {
        listeners.add(l);
    }
    
    public void removeAreaListener(AreaListener l) {
        listeners.remove(l);
    }
    
    private void notifyRoomChanged(PubRoom pr) {
        for (AreaListener l : listeners) {
            l.roomChanged(pr);
        }
    }

    private void notifyRoomDeleted(Point p) {
        for (AreaListener l : listeners) {
            l.roomDeleted(p);
        }
    }
}