
import java.util.*;

public class BlobWarsWorld
{
    public static final double SHOT_SPEED = 0.4;
    public static final double PLAYER_SPEED = 0.2;
    public static final double PLAYER_SPIN_SPEED = Math.PI * 0.6;
    
    private HashMap<String, Player> m_players;
    private HashMap<Integer, Shot> m_shots;
    private long m_lastStepTime;
    private int m_shotID;

    public BlobWarsWorld()
    {
        m_players = new HashMap<String, Player>();
        m_shots = new HashMap<Integer, Shot>();
    }

    public void clear()
    {
        m_players.clear();
        m_shots.clear();
    }

    public void resetPlayers()
    {
        for (Player p : m_players.values())
        {
            p.health = 1.0;
            p.dx = 0;
            p.dy = 0;
        }
        m_shots.clear();
    }

    public boolean playerExists(String name)
    {
        return m_players.containsKey(name);
    }

    public boolean shotExists(Integer id)
    {
        return m_shots.containsKey(id);
    }

    public Player getPlayer(String name)
    {
        return m_players.get(name);
    }

    public Shot getShot(Integer id)
    {
        return m_shots.get(id);
    }

    public void updatePlayer(String playerInfo)
    {
        StringTokenizer st = new StringTokenizer(playerInfo, ":");
        if (st.hasMoreTokens())
        {
            String name = st.nextToken();
            if (!playerExists(name))
                addPlayer(name);
            getPlayer(name).fromString(playerInfo);
        }
    }

    public void updateShot(String shotInfo)
    {
        StringTokenizer st = new StringTokenizer(shotInfo, ":");
        if (st.hasMoreTokens())
        {
            String idstr = st.nextToken();
            Integer id = Integer.parseInt(idstr);
            if (!shotExists(id))
                addShot(id);
            getShot(id).fromString(shotInfo);
        }
    }

    public void addShot(Integer id)
    {
        Shot s = new Shot(id, 0, 0, 0);
        m_shots.put(id, s);
    }

    public boolean addPlayer(String name)
    {
        if (playerExists(name))
            return false;
        Player p = new Player(name);
        m_players.put(name, p);
        do
        {
            p.x = (1.0 - 2 * Player.DEFAULT_RADIUS) * Math.random()
                + Player.DEFAULT_RADIUS;
            p.y = (1.0 - 2 * Player.DEFAULT_RADIUS) * Math.random()
                + Player.DEFAULT_RADIUS;
        } while (collidesWith(p).size() != 0);

        return true;
    }

    public void removePlayer(String name)
    {
        m_players.remove(name);
    }

    public void removeShot(Integer id)
    {
        m_shots.remove(id);
    }

    public void shoot(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
        if (p.health <= 0.0)
            return;
        long curtime = (new Date()).getTime();
        if ( ((curtime - p.lastShotTime) / 1000.0)
                > Player.SHOT_DELAY)
        {
            /* calculate X & Y coordinates of shot */
            double x = p.x + Math.cos(p.r) *
                (0.01 + p.radius + Shot.DEFAULT_RADIUS);
            double y = p.y + Math.sin(p.r) *
                (0.01 + p.radius + Shot.DEFAULT_RADIUS);
            m_shotID++;
            Shot s = new Shot(m_shotID, curtime, x, y);
            s.dx = Math.cos(p.r) * SHOT_SPEED;
            s.dy = Math.sin(p.r) * SHOT_SPEED;
            m_shots.put(m_shotID, s);
            p.lastShotTime = curtime;
        }
    }

    public void moveUp(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
        if (p.health <= 0.0)
            return;
        p.dx = Math.cos(p.r) * PLAYER_SPEED;
        p.dy = Math.sin(p.r) * PLAYER_SPEED;
    }

    public void moveDown(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
//        p.dx -= Math.cos(p.r) * PLAYER_SPEED;
//        p.dy -= Math.sin(p.r) * PLAYER_SPEED;
        p.dx = p.dy = p.dr = 0;
    }

    public void moveLeft(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
        if (p.health <= 0.0)
            return;
        p.dr = PLAYER_SPIN_SPEED;
        if (Math.abs(p.dr) < 0.01)
            p.dr = 0;
    }

    public void moveRight(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
        if (p.health <= 0.0)
            return;
        p.dr = -PLAYER_SPIN_SPEED;
        if (Math.abs(p.dr) < 0.01)
            p.dr = 0;
    }

    public void stopSpinning(String playerName)
    {
        if (!m_players.containsKey(playerName))
            return;
        Player p = m_players.get(playerName);
        if (p.health <= 0.0)
            return;
        p.dr = 0;
    }

    public Vector<GameItem> collidesWith(GameItem gi)
    {
        Vector<GameItem> collideList = new Vector<GameItem>();
        for (Player p : m_players.values())
        {
            if (p != gi && p.health > 0.0 && collidesWith(gi, p))
                collideList.add(p);
        }
        for (Shot s : m_shots.values())
        {
            if (s != gi && collidesWith(gi, s))
                collideList.add(s);
        }
        return collideList;
    }

    public boolean collidesWith(GameItem i1, GameItem i2)
    {
        double dx = i2.x - i1.x;
        double dy = i2.y - i1.y;
        double d2 = dx*dx + dy*dy;
        double r = i1.radius + i2.radius;
        double r2 = r*r;
        return (d2 < r2);
    }

    public HashMap<String, Player> getPlayers()
    {
        return m_players;
    }

    public HashMap<Integer, Shot> getShots()
    {
        return m_shots;
    }

    public void step()
    {
        long curtime = (new Date()).getTime();
        double elapsed = (curtime - m_lastStepTime) / 1000.0;
        if (m_lastStepTime == 0)
            elapsed = 0.001;
        for (Player p : m_players.values())
        {
            p.x += p.dx * elapsed;
            p.y += p.dy * elapsed;
            p.r += p.dr * elapsed;
            if (p.x < 0.0)  p.x += 1.0;
            if (p.x > 1.0)  p.x -= 1.0;
            if (p.y < 0.0)  p.y += 1.0;
            if (p.y > 1.0)  p.y -= 1.0;
            if (p.r < 0.0)  p.r += Math.PI*2;
            if (p.r > Math.PI*2)  p.r -= Math.PI*2;
        }
        Vector<Integer> shotIDs = new Vector<Integer>();
        shotIDs.addAll(m_shots.keySet());
        for (Integer i : shotIDs)
        {
            Shot s = m_shots.get(i);
            if (((curtime - s.createTime) / 1000.0) >= Shot.SHOT_DURATION)
            {
                m_shots.remove(i);
                i++;
            }
            else
            {
                s.x += s.dx * elapsed;
                s.y += s.dy * elapsed;
                if (s.x < 0.0)  s.x += 1.0;
                if (s.x > 1.0)  s.x -= 1.0;
                if (s.y < 0.0)  s.y += 1.0;
                if (s.y > 1.0)  s.y -= 1.0;
            }
        }

        /* calculate damage against players */
        for (Player p : m_players.values())
        {
            Vector<GameItem> objs = collidesWith(p);
            for (GameItem gi : objs)
            {
                if (gi instanceof Player)
                {
                    p.health -= Player.COLLIDE_DAMAGE;
                }
                if (gi instanceof Shot)
                {
                    p.health -= Shot.SHOT_DAMAGE;
                    Integer ifound = null;
                    for (Integer i : m_shots.keySet())
                    {
                        if (m_shots.get(i) == gi)
                        {
                            ifound = i;
                            break;
                        }
                    }
                    if (ifound != null)
                        m_shots.remove(ifound);
                }
                if (p.health < 0.0)
                {
                    /* player is "dead" */
                    p.health = 0.0;
                    p.dx = p.dy = p.dr = 0;
                }
            }
        }
        m_lastStepTime = curtime;
    }
}

