/*
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.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.table.TableCellRenderer;

import common.PubEntity;
import common.PubGroupMember;
import common.PubRoomEntity;
import common.gameevent.StatGameEvent.StatType;

/**
 * Visual component for displaying either the room list or the group list.
 * 
 * @author David Green <green@couchpotato.net>
 *
 * @param <E> the type of pubentity that this list will contain
 */
public class EntityList<E extends PubEntity> extends JPanel {

    private static final long serialVersionUID = 3833181437003642169L;
  
    // The little skull icon that shows next to dead players
    private static final Icon DEATH_ICON = new ImageIcon(EntityList.class.getClassLoader().getResource("clientrc/dead.png"));
    // The little player icon next to players
    private static final Icon PLAYER_ICON = new ImageIcon(EntityList.class.getClassLoader().getResource("clientrc/player.gif"));
    // The little npc icon next to npcs
    private static final Icon NPC_ICON = new ImageIcon(EntityList.class.getClassLoader().getResource("clientrc/npc.gif"));
    // The little item icon next to items
    private static final Icon ITEM_ICON = new ImageIcon(EntityList.class.getClassLoader().getResource("clientrc/item.gif"));
    // The width of the arrow head (just the triangle part) in the locator arrow in the group list
    private static final double ARROW_HEAD_WIDTH = Math.toRadians(90);
    // The height of the arrow head (just the triangle part) in the locator arrow in the group list
    private static final double ARROW_HEAD_HEIGHT = 0.6; // should be percentage

    // Table model that actually holds the data
    private final EntityTableModel<E> model;
    
    /**
     * Creates a new entity list component.
     * @param name the title of the list (displayed at the top)
     */
    public EntityList(String name) {
        super(new BorderLayout());
        model = new EntityTableModel<E>(name);
        
        // used for displaying the health bar in the table
        final JProgressBar healthBar = makeHealthBar();
        // used for displaying the entity name and icons in the table
        final JLabel entityLabel = new JLabel(".");
        // used for displaying the entire entry in the table
        final JPanel entityPanel = makeEntityPanel(healthBar, entityLabel);
        // the table itself
        final JTable table = new JTable(model);
        
        // custom cell renderer that uses entityPanels for display
        table.getColumnModel().getColumn(0).setCellRenderer(
                new TableCellRenderer() {
                    public Component getTableCellRendererComponent(
                            JTable ltable, Object value, boolean isSelected,
                            boolean hasFocus, int row, int column) {
                        PubEntity entity = (PubEntity) value;
                        entityLabel.setText(entity.name);
                        entityLabel.setForeground(Color.black);
                        List<Icon> icons = new ArrayList<Icon>();
                        switch (entity.type) {
                            case PLAYER: icons.add(PLAYER_ICON); break;
                            case NPC: icons.add(NPC_ICON); break;
                            case ITEM: icons.add(ITEM_ICON); break;
                        }
                        if (entity.hp < 0) {
                            icons.add(DEATH_ICON);
                        }
                        if (entity instanceof PubGroupMember) {
                            PubGroupMember member = (PubGroupMember) entity;
                            if (member.showArrow) {
                                icons.add(makeArrowIcon(member.lastArrowDirection));
                            }
                            if (member.withYou) {
                                entityPanel.setBackground(Color.white);
                            }
                            else {
                                entityPanel.setBackground(Color.lightGray);
                            }
                            healthBar.setForeground(Color.red);
                        }
                        else {
                            PubRoomEntity roomEntity = (PubRoomEntity) entity;
                            entityPanel.setBackground(Color.white);
                            switch (roomEntity.type) {
                                case PLAYER: healthBar.setForeground(Color.red); break;
                                case NPC: healthBar.setForeground(Color.magenta); break;
                                case ITEM: healthBar.setForeground(Color.green); break;
                                default: System.err.println("Unknown room entity type: " + roomEntity.type);
                            }
                        }
                        if (icons.size() == 0) {
                            entityLabel.setIcon(null);
                        }
                        else {
                            entityLabel.setIcon(combineIcons(icons));
                        }
                        healthBar.setValue(entity.hp);
                        return entityPanel;
                    }

                });
        
        table.setRowHeight(entityPanel.getPreferredSize().height);
        table.setFocusable(false);
        JScrollPane scroller = new JScrollPane(table);
        scroller.setPreferredSize(new Dimension(1, 1));
        add(scroller, BorderLayout.CENTER);
    }
    
    /**
     * Returns an Icon that consists of the specified icons side-by-side.
     * @param icons the icons to combine, should be 16x16
     * @return an icon that consists of the specified icons side-by-side
     */
    private static Icon combineIcons(final Collection<Icon> icons) {
        return new Icon() {
        
            public int getIconHeight() {
                return 16;
            }
        
            public int getIconWidth() {
                // 21 = 16 pixel icon + 5 pixel spacer
                return icons.size() * 21 - 5;
            }
        
            public void paintIcon(Component c, Graphics g, int x, int y) {
                int num = 0;
                for (Icon icon : icons) {
                    icon.paintIcon(c, g, x + 21 * num++, y);
                }
            }
        
        };
    }

    /**
     * Makes a properly set up health bar component.
     * @return the health bar component
     */
    private static JProgressBar makeHealthBar() {
        JProgressBar healthBar = new JProgressBar(0, 10000);
        healthBar.setBorder(new CompoundBorder(new EmptyBorder(4, 0, 0, 0), new LineBorder(Color.black, 1)));
        healthBar.setPreferredSize(new Dimension(0, 12));
        healthBar.setBackground(Color.white);
        healthBar.setBorderPainted(true);
        healthBar.setOpaque(false);
        return healthBar;
    }
    
    /**
     * Makes a properly set up entity panel component.
     * @param healthBar the health bar component
     * @param entityLabel the entity label component
     * @return the entity panel component
     */
    private static JPanel makeEntityPanel(JProgressBar healthBar, JLabel entityLabel) {
        JPanel entityPanel = new JPanel(new BorderLayout());
        entityPanel.setBorder(new CompoundBorder(new LineBorder(Color.black, 1), new EmptyBorder(2, 2, 2, 2)));
        entityPanel.add(entityLabel, BorderLayout.NORTH);
        entityPanel.add(healthBar, BorderLayout.SOUTH);
        entityPanel.setOpaque(true);
        entityPanel.setBackground(Color.white);
        return entityPanel;
    }
    
    /**
     * Generates an arrow icon pointing in the specified direction.
     * @param radians the direction of the arrow, in radians.
     * @return the arrow icon
     */
    private static Icon makeArrowIcon(double radians) {
        double dsize = 16.0;
        double asize = dsize * ARROW_HEAD_HEIGHT;
        BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = (Graphics2D) img.getGraphics();
        g.setColor(new Color(255, 255, 255, 0));
        g.fillRect(0, 0, 16, 16);
        g.setColor(Color.black);
        g.translate(8, 8);
        
        Polygon arrowHead = new Polygon();
        int x = (int) (asize * Math.cos(radians - ARROW_HEAD_WIDTH) / 2.0);
        int y = (int) (asize * Math.sin(radians - ARROW_HEAD_WIDTH) / 2.0);
        arrowHead.addPoint(x, y);
        x = (int) (dsize * Math.cos(radians) / 2.0);
        y = (int) (dsize * Math.sin(radians) / 2.0);
        arrowHead.addPoint(x, y);
        x = (int) (asize * Math.cos(radians + ARROW_HEAD_WIDTH) / 2.0);
        y = (int) (asize * Math.sin(radians + ARROW_HEAD_WIDTH) / 2.0);
        arrowHead.addPoint(x, y);
        g.fill(arrowHead);
        
        dsize *= (1.0 + ARROW_HEAD_HEIGHT) / 2.0;
        x = (int) (dsize * Math.cos(radians) / 2.0);
        y = (int) (dsize * Math.sin(radians) / 2.0);
        g.setStroke(new BasicStroke(2.0f));
        g.drawLine(-x, -y, x, y);
        g.dispose();
        return new ImageIcon(img);
    }

    /**
     * Causes an update to the specified stat in the list.
     * Works by just delegating the update to the table model.
     * @param id the entity id to update
     * @param statType the type of stat to update
     * @param current the new value of the stat
     * @param max the new max value of the stat
     */
    public void statUpdate(int id, StatType statType, int current, int max) {
        model.statUpdate(id, statType, current, max);
    }
    
    /**
     * Clears the list, replacing it with the specified entities.
     * @param entities the new entities in the list
     */
    public void clear(E... entities) {
        model.clear(entities);
    }

    /**
     * Adds the specified entities to the list.
     * @param entities the entities to add
     */
    public void add(E... entities) {
        model.add(entities);
    }

    /**
     * Modifies the specified entities in the list.
     * @param entities the entities to modify
     */
    public void modify(E... entities) {
        model.modify(entities);
    }

    /**
     * Looks up the specified entity in the list, and returns it if it exists.
     * @param entity the entity to look up
     * @return the entity from the list, or null if not found
     */
    public E get(E entity) {
        return model.get(entity);
    }

    /**
     * Removes the specfied entities from the list.
     * @param entities the entities to remove
     */
    public void remove(E... entities) {
        model.remove(entities);
    }

    /**
     * Performs a health update for the specified entity. 
     * @param id the entity to update
     * @param hp the new health for that entity
     */
    public void health(int id, int hp) {
        // TODO: Why isn't statUpdate() used here?
        model.health(id, hp);
    }
    
}