package codechicken.chunkloader;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;

import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.multiplayer.WorldClient;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.vec.Vector3;

@SuppressWarnings("serial")
public class PlayerChunkViewer extends JFrame
{
    public static class TicketInfo
    {
        int ID;
        String modId;
        String player;
        net.minecraftforge.common.ForgeChunkManager.Type type;
        Entity entity;
        Set<ChunkCoordIntPair> chunkSet;

        public TicketInfo(PacketCustom packet, WorldClient world)
        {
            ID = packet.readInt();
            modId = packet.readString();
            if(packet.readBoolean())
                player = packet.readString();
            type = net.minecraftforge.common.ForgeChunkManager.Type.values()[packet.readUByte()];
            if(type == net.minecraftforge.common.ForgeChunkManager.Type.ENTITY)
                entity = world.getEntityByID(packet.readInt());
            int chunks = packet.readUShort();
            chunkSet = new HashSet<ChunkCoordIntPair>(chunks);
            for(int i = 0; i < chunks; i++)
            {
                chunkSet.add(new ChunkCoordIntPair(packet.readInt(), packet.readInt()));
            }
        }
    }

    public static class PlayerInfo
    {
        public PlayerInfo(String username2)
        {
            this.username = username2;
        }

        final String username;
        Vector3 position;
        int dimension;
    }

    public static class DimensionChunkInfo
    {
        public final int dimension;
        public HashSet<ChunkCoordIntPair> allchunks = new HashSet<ChunkCoordIntPair>();
        public HashMap<Integer, TicketInfo> tickets = new HashMap<Integer, TicketInfo>();

        public DimensionChunkInfo(int dim)
        {
            dimension = dim;
        }
    }

    public class TicketInfoDialog extends JDialog implements LayoutManager
    {
        private LinkedList<TicketInfo> tickets;

        private JTextPane infoPane;
        private JScrollPane infoScrollPane;
        private JTextPane chunkPane;
        private JScrollPane chunkScrollPane;
        private JComboBox<String> ticketComboBox;

        public TicketInfoDialog(LinkedList<TicketInfo> tickets)
        {
            super(PlayerChunkViewer.this);
            setModalityType(ModalityType.DOCUMENT_MODAL);
            this.tickets = tickets;

            infoPane = new JTextPane();
            infoPane.setEditable(false);
            infoPane.setOpaque(false);
            infoPane.setContentType("text/html");
            
            infoScrollPane = new JScrollPane(infoPane);
            infoScrollPane.setOpaque(false);
            add(infoScrollPane);
            
            chunkPane = new JTextPane();
            chunkPane.setEditable(false);
            chunkPane.setOpaque(false);
            chunkPane.setContentType("text/html");
            
            chunkScrollPane = new JScrollPane(chunkPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            add(chunkScrollPane);

            ticketComboBox = new JComboBox<String>();
            for(TicketInfo ticket : tickets)
            {
                String ident = ticket.modId;
                if(ticket.player != null)
                    ident += ", " + ticket.player;
                ident += " #" + ticket.ID;
                ticketComboBox.addItem(ident);
            }
            add(ticketComboBox);

            addWindowListener(new WindowAdapter()
            {
                @Override
                public void windowClosing(WindowEvent e)
                {
                    dialog = null;
                }
            });

            setLayout(this);
            setSize(getPreferredSize());
            setLocationRelativeTo(null);
            pack();

            dialog = this;

            setVisible(true);
        }

        public void update()
        {
            TicketInfo ticket = tickets.get(ticketComboBox.getSelectedIndex());
            String info = "<span style=\"font-family:Tahoma; font-size:10px\">";
            info += "Mod: " + ticket.modId;
            if(ticket.player != null)
                info += "<br>Player: " + ticket.player;
            info += "<br>Type: " + ticket.type.name();
            if(ticket.entity != null)
                info += "<br>Entity: " + EntityList.classToStringMapping.get(ticket.entity) + "#" + ticket.entity.getEntityId() + " (" + String.format("%.2f", ticket.entity.posX) + ", " + String.format("%.2f", ticket.entity.posY) + ", " + String.format("%.2f", ticket.entity.posZ) + ")";
            info+="</span><p style=\"text-align:center; font-family:Tahoma; font-size:10px\">ForcedChunks</p>";
            String chunks = "<span style=\"font-family:Tahoma; font-size:10px\">";
            for(ChunkCoordIntPair coord : ticket.chunkSet)
                chunks += coord.chunkXPos + ", " + coord.chunkZPos + "<br>";
            chunks += "</span>";
            infoPane.setText(info);
            chunkPane.setText(chunks);
            repaint();
        }

        @Override
        public void layoutContainer(Container parent)
        {
            Dimension size = parent.getSize();
            ticketComboBox.setBounds(40, 20, size.width - 80, 20);
            int w = size.width - 40;
            int y = 60;
            infoPane.setBounds(5, 5, w-10, 5);
            chunkPane.setBounds(5, 5, w-10, 5);
            infoScrollPane.setBounds(20, y, w, Math.min(size.height - 80, infoPane.getPreferredSize().height+10));
            y += 10+infoScrollPane.getHeight();
            chunkScrollPane.setBounds(20, y, w, Math.max(0, size.height-40-y));
        }

        @Override
        public void addLayoutComponent(String name, Component comp)
        {
        }

        @Override
        public Dimension minimumLayoutSize(Container parent)
        {
            return new Dimension(250, 300);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent)
        {
            return new Dimension(250, 300);
        }

        @Override
        public void removeLayoutComponent(Component comp)
        {
        }
    }

    private HashMap<String, PlayerInfo> players = new HashMap<String, PlayerInfo>();
    private final HashMap<Integer, DimensionChunkInfo> dimensionChunks = new HashMap<Integer, DimensionChunkInfo>();

    private int dimension = 0;
    private int xCenter = 0;
    private int zCenter = 0;
    private DisplayArea displayArea;
    private JComboBox<Integer> dimComboBox;
    private JLabel dimLabel;
    private JTextField xArea;
    private JLabel xLabel;
    private JTextField zArea;
    private JLabel zLabel;
    public TicketInfoDialog dialog;

    public static void openViewer(int x, int z, int dim)
    {
        instance = new PlayerChunkViewer(x, z, dim);
    }

    private PlayerChunkViewer(int x, int z, int dim)
    {
        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                if(Minecraft.getMinecraft().getNetHandler() != null)
                    ChunkLoaderCPH.sendGuiClosing();
            }
        });

        setLayout(new LayoutManager()
        {
            @Override
            public void removeLayoutComponent(Component paramComponent)
            {
            }

            @Override
            public Dimension preferredLayoutSize(Container paramContainer)
            {
                return new Dimension(500, 500);
            }

            @Override
            public Dimension minimumLayoutSize(Container paramContainer)
            {
                return null;
            }

            @Override
            public void layoutContainer(Container paramContainer)
            {
                int width = getRootPane().getWidth();
                int height = getRootPane().getHeight();

                xLabel.setBounds(20, 10, 50, 20);
                xArea.setBounds(70, 10, 60, 20);
                zLabel.setBounds(140, 10, 50, 20);
                zArea.setBounds(190, 10, 60, 20);
                dimLabel.setBounds(260, 10, 60, 20);
                dimComboBox.setBounds(330, 10, 70, 20);
                displayArea.setBounds(0, 40, width, height - 40);
            }

            @Override
            public void addLayoutComponent(String paramString, Component paramComponent)
            {
            }
        });

        ToolTipManager.sharedInstance().setEnabled(true);
        ToolTipManager.sharedInstance().setInitialDelay(0);
        ToolTipManager.sharedInstance().setReshowDelay(100);
        addComponents();
        pack();
        setTitle("Chunk Viewer");

        Point p = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
        int width = 500;
        int height = 500;
        setCenter(x, z);
        dimension = dim;
        setBounds(p.x - width / 2, p.y - height / 2, width, height);

        setBackground(new Color(1F, 1, 1));

        setVisible(true);

        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                startUpdateThread();
            }
        });
    }

    protected void startUpdateThread()
    {
        new Thread("Info Frame Update Thread")
        {
            public void run()
            {
                while(true)
                {
                    if(Minecraft.getMinecraft().currentScreen instanceof GuiMainMenu)
                        dispose();

                    if(instance == null || !isVisible())
                        return;

                    update();
                    if(dialog != null)
                        dialog.update();

                    try
                    {
                        Thread.sleep(50);
                    }
                    catch(InterruptedException e)
                    {
                    }
                }
            }
        }.start();
    }

    protected void update()
    {
        int selectedDim = dimension;
        boolean needsReset = false;
        LinkedList<Integer> dims = new LinkedList<Integer>(dimensionChunks.keySet());
        Collections.sort(dims);
        if(dims.size() != dimComboBox.getItemCount())
            needsReset = true;
        else
        {
            for(int index = 0; index < dimComboBox.getItemCount();)
            {
                if(!dims.get(index).equals(dimComboBox.getItemAt(index)))
                {
                    needsReset = true;
                    break;
                }
                index++;
            }
        }

        if(needsReset)
        {
            dimComboBox.removeAllItems();
            dims = new LinkedList<Integer>(dimensionChunks.keySet());
            Collections.sort(dims);
            for(int dim : dims)
            {
                dimComboBox.addItem(dim);
            }

            if(dims.contains(selectedDim))
                dimComboBox.setSelectedItem(selectedDim);
        }
        repaint();
    }

    private void addComponents()
    {
        add(getDisplayArea());
        add(getXLabel());
        add(getXArea());
        add(getZLabel());
        add(getZArea());
        add(getDimLabel());
        add(getDimComboBox());
    }

    public DisplayArea getDisplayArea()
    {
        if(displayArea == null)
        {
            displayArea = new DisplayArea();
            displayArea.addMouseListener(displayArea);
            displayArea.addMouseMotionListener(displayArea);
        }
        return displayArea;
    }

    public JLabel getXLabel()
    {
        if(xLabel == null)
        {
            xLabel = new JLabel("xCenter");
        }
        return xLabel;
    }

    public JTextField getXArea()
    {
        if(xArea == null)
        {
            xArea = new JTextField();
            xArea.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    try {
                        setCenter(Integer.parseInt(xArea.getText()), zCenter);
                    } catch (NumberFormatException ignored) {
                    }
                }
            });
        }
        return xArea;
    }

    public JLabel getZLabel()
    {
        if(zLabel == null)
        {
            zLabel = new JLabel("zCenter");
        }
        return zLabel;
    }

    public JTextField getZArea()
    {
        if(zArea == null)
        {
            zArea = new JTextField();
            zArea.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    try {
                        setCenter(xCenter, Integer.parseInt(zArea.getText()));
                    } catch (NumberFormatException ignored) {
                    }
                }
            });
        }
        return zArea;
    }

    public JLabel getDimLabel()
    {
        if(dimLabel == null)
        {
            dimLabel = new JLabel("Dimension");
        }
        return dimLabel;
    }

    public JComboBox<Integer> getDimComboBox()
    {
        if(dimComboBox == null)
        {
            dimComboBox = new JComboBox<Integer>();
            dimComboBox.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    if(e.getActionCommand().equals("comboBoxChanged"))
                    {
                        if(dimComboBox.getSelectedItem() != null)
                            dimension = (Integer) dimComboBox.getSelectedItem();
                    }
                }
            });
        }
        return dimComboBox;
    }

    public class DisplayArea extends JPanel implements MouseListener, MouseMotionListener
    {
        int mouseClickedX;
        int centerClickedX;
        int mouseClickedY;
        int centerClickedZ;

        Point center;

        @Override
        public void paint(Graphics g1)
        {
            synchronized(dimensionChunks)
            {
                Graphics2D g = (Graphics2D) g1;
                Dimension dim = getSize();
                g.clearRect(0, 0, dim.width, dim.height);

                center = new Point(dim.width / 2, dim.height / 2);
                DimensionChunkInfo dimInfo = dimensionChunks.get(dimension);
                if(dimInfo == null)
                {
                    dimension = 0;
                    return;
                }

                g.setColor(new Color(1F, 0, 0));
                for(ChunkCoordIntPair coord : dimInfo.allchunks)
                {
                    Point pos = getChunkRenderPosition(coord.chunkXPos, coord.chunkZPos);
                    g.fillRect(pos.x, pos.y, 4, 4);
                }

                HashSet<ChunkCoordIntPair> forcedChunks = new HashSet<ChunkCoordIntPair>();
                int numTickets = 0;
                for(TicketInfo ticket : dimInfo.tickets.values())
                {
                    if(!ticket.chunkSet.isEmpty())
                        numTickets++;
                    for(ChunkCoordIntPair coord : ticket.chunkSet)
                        forcedChunks.add(coord);
                }

                g.setColor(new Color(0, 1F, 0));
                for(ChunkCoordIntPair coord : forcedChunks)
                {
                    Point pos = getChunkRenderPosition(coord.chunkXPos, coord.chunkZPos);
                    g.fillRect(pos.x + 1, pos.y + 1, 2, 2);
                }

                int numPlayers = 0;
                g.setColor(new Color(0, 0, 1F));
                for(PlayerInfo info : players.values())
                {
                    if(info.dimension == dimension)
                    {
                        Point pos = getChunkRenderPosition((int) info.position.x, 0, (int) info.position.z);
                        g.fillRect(pos.x + 1, pos.y + 1, 2, 2);
                        numPlayers++;
                    }
                }

                g.setColor(new Color(0, 0, 0));
                for(int x = (xCenter >> 4) - (center.x >> 2) - 2; x < (xCenter >> 4) + (center.x >> 2) + 2; x++)
                {
                    if(x % 16 == 0)
                    {
                        Point pos = getChunkRenderPosition(x, ((zCenter + 128) >> 8) << 4);
                        g.drawLine(pos.x, 0, pos.x, dim.height);

                        g.drawString(Integer.toString(x << 4), pos.x + 2, pos.y + 12);
                    }
                }
                for(int z = (zCenter >> 4) - (center.y >> 2) - 2; z < (zCenter >> 4) + (center.y >> 2) + 2; z++)
                {
                    if(z % 16 == 0)
                    {
                        Point pos = getChunkRenderPosition(((xCenter + 128) >> 8) << 4, z);
                        g.drawLine(0, pos.y, dim.width, pos.y);

                        g.drawString(Integer.toString(z << 4), pos.x + 2, pos.y - 2);
                    }
                }

                g.setColor(new Color(1F, 1F, 1F));
                g.fillRect(0, 0, 100, 60);

                g.setColor(new Color(0, 0, 0));
                g.drawString("Tickets: " + numTickets, 10, 20);
                g.drawString("Forced Chunks: " + forcedChunks.size(), 10, 30);
                g.drawString("Chunks: " + dimInfo.allchunks.size(), 10, 40);
                g.drawString("Players: " + numPlayers, 10, 50);
            }
        }

        public Point getChunkRenderPosition(int chunkX, int chunkZ)
        {
            int relBlockX = (chunkX << 4) + 8 - xCenter;
            int relBlockZ = (chunkZ << 4) + 8 - zCenter;
            return new Point(center.x + (relBlockX >> 2), center.y + (relBlockZ >> 2));
        }

        public Point getChunkRenderPosition(int blockX, int blockY, int blockZ)
        {
            return getChunkRenderPosition(blockX >> 4, blockZ >> 4);
        }

        @SuppressWarnings("unused")
        @Override
        public void mouseClicked(MouseEvent event)
        {
            if(event.getButton() != MouseEvent.BUTTON1 || event.getClickCount() < 2)
                return;

            Point mouse = event.getPoint();
            DimensionChunkInfo dimInfo = dimensionChunks.get(dimension);
            if(dimInfo == null)
            {
                dimension = 0;
                return;
            }

            LinkedList<TicketInfo> mouseOverTickets = getTicketsUnderMouse(dimInfo, mouse);
            if(!mouseOverTickets.isEmpty())
                new TicketInfoDialog(mouseOverTickets);
        }

        @Override
        public void mousePressed(MouseEvent event)
        {
            mouseClickedX = event.getX();
            centerClickedX = xCenter;
            mouseClickedY = event.getY();
            centerClickedZ = zCenter;
        }

        @Override
        public void mouseReleased(MouseEvent event)
        {
        }

        @Override
        public void mouseEntered(MouseEvent event)
        {
        }

        @Override
        public void mouseExited(MouseEvent event)
        {
        }

        @Override
        public void mouseDragged(MouseEvent event)
        {
            setCenter((mouseClickedX - event.getX()) * 4 + centerClickedX, (mouseClickedY - event.getY()) * 4 + centerClickedZ);
        }

        public LinkedList<TicketInfo> getTicketsUnderMouse(DimensionChunkInfo dimInfo, Point mouse)
        {
            LinkedList<TicketInfo> mouseOverTickets = new LinkedList<TicketInfo>();
            for(TicketInfo ticket : dimInfo.tickets.values())
            {
                for(ChunkCoordIntPair coord : ticket.chunkSet)
                {
                    Point pos = getChunkRenderPosition(coord.chunkXPos, coord.chunkZPos);
                    if(new Rectangle(pos.x, pos.y, 4, 4).contains(mouse))
                    {
                        mouseOverTickets.add(ticket);
                    }
                }
            }
            return mouseOverTickets;
        }

        @Override
        public void mouseMoved(MouseEvent event)
        {
            synchronized(dimensionChunks)
            {
                Point mouse = event.getPoint();
                DimensionChunkInfo dimInfo = dimensionChunks.get(dimension);
                if(dimInfo == null)
                {
                    dimension = 0;
                    return;
                }
                String tip = "";
                LinkedList<TicketInfo> mouseOverTickets = getTicketsUnderMouse(dimInfo, mouse);
                if(!mouseOverTickets.isEmpty())
                {
                    tip += mouseOverTickets.size() + (mouseOverTickets.size() == 1 ? " ticket" : " tickets");
                    for(TicketInfo info : mouseOverTickets)
                    {
                        tip += "\n" + info.modId;
                        if(info.player != null)
                            tip += ", " + info.player;
                    }
                }

                for(PlayerInfo info : players.values())
                {
                    if(info.dimension == dimension)
                    {
                        Point pos = getChunkRenderPosition((int) info.position.x, 0, (int) info.position.z);
                        if(new Rectangle(pos.x, pos.y, 4, 4).contains(mouse))
                            tip += "\n\n"+info.username + "\n(" + String.format("%.2f", info.position.x) + ", " + String.format("%.2f", info.position.y) + ", " + String.format("%.2f", info.position.z) + ")";
                    }
                }
                setToolTipText(tip.length() > 0 ? tip : null);
            }
        }

        @Override
        public void setToolTipText(String paramString)
        {
            if(paramString == null)
                super.setToolTipText(null);
            else
                super.setToolTipText("<html>" + paramString.replace("\n", "<br>") + "</html>");
        }
    }

    private static PlayerChunkViewer instance;

    public static PlayerChunkViewer instance()
    {
        return instance;
    }

    public void setCenter(int blockX, int blockZ)
    {
        xArea.setText(Integer.toString(blockX));
        xCenter = blockX;
        zArea.setText(Integer.toString(blockZ));
        zCenter = blockZ;
    }

    public void loadDimension(PacketCustom packet, WorldClient world)
    {
        synchronized(dimensionChunks)
        {
            DimensionChunkInfo dimInfo = new DimensionChunkInfo(packet.readInt());

            int numChunks = packet.readInt();
            for(int i = 0; i < numChunks; i++)
                dimInfo.allchunks.add(new ChunkCoordIntPair(packet.readInt(), packet.readInt()));

            int numTickets = packet.readInt();
            for(int i = 0; i < numTickets; i++)
            {
                TicketInfo ticket = new TicketInfo(packet, world);
                dimInfo.tickets.put(ticket.ID, ticket);
            }

            dimensionChunks.put(dimInfo.dimension, dimInfo);
        }
    }

    public void unloadDimension(int dim)
    {
        dimensionChunks.remove(dim);
    }

    public void handleChunkChange(int dimension, ChunkCoordIntPair coord, boolean add)
    {
        synchronized(dimensionChunks)
        {
            if(add)
                dimensionChunks.get(dimension).allchunks.add(coord);
            else
                dimensionChunks.get(dimension).allchunks.remove(coord);
        }
    }

    public void handleTicketChange(int dimension, int ticketID, ChunkCoordIntPair coord, boolean force)
    {
        synchronized(dimensionChunks)
        {
            DimensionChunkInfo dimInfo = dimensionChunks.get(dimension);
            TicketInfo ticket = dimInfo.tickets.get(ticketID);
            if(force)
                ticket.chunkSet.add(coord);
            else
                ticket.chunkSet.remove(coord);
        }
    }

    public void handleNewTicket(PacketCustom packet, WorldClient world)
    {
        synchronized(dimensionChunks)
        {
            int dim = packet.readInt();
            TicketInfo ticket = new TicketInfo(packet, world);
            dimensionChunks.get(dim).tickets.put(ticket.ID, ticket);
        }
    }

    public void handlePlayerUpdate(String username, int dimension, Vector3 position)
    {
        synchronized(dimensionChunks)
        {
            PlayerInfo info = players.get(username);
            if(info == null)
                players.put(username, info = new PlayerInfo(username));
            info.dimension = dimension;
            info.position = position;
        }
    }

    public void removePlayer(String username)
    {
        synchronized(dimensionChunks)
        {
            players.remove(username);
        }
    }
}