/*
 Eteria IRC Client, an RFC 1459 compliant client program written in Java.
 Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
 package ar.com.jkohen.awt;
 
 import java.awt.*;
 import java.awt.event.*;
 import java.text.Collator;
 import java.text.CollationKey;
 import java.util.Enumeration;
 import java.util.Vector;
 import ar.com.jkohen.irc.Modes;
 import ar.com.jkohen.irc.RFC1459;
 import ar.com.jkohen.irc.User;
 import ar.com.jkohen.util.CollatedHashtable;
 
 public class NickList extends ScrollPane implements MouseListener, MouseMotionListener, ItemSelectable
 {
 	public static final int SYMBOL_RENDERER = 0;
 	public static final int BULLET_RENDERER = 1;
 
 	public static final int SORT_ALPHA = 0;
 	public static final int SORT_ALPHA_FAVOR_MODE = 1;
 
 	private static final int PREF_MIN_WIDTH = 190;
 
 	private int item_renderer = BULLET_RENDERER;
 	private int sort_method = SORT_ALPHA;
 	
 	private MouseListener mouseListener;
 	private ActionListener actionListener;
 	private ItemListener itemListener;
 
 	protected MyList screen_list;
 	private NickInfoPopup nick_pop;
 	private Point mouse_coords;
 
 	private CollatedHashtable items;
 
 	private Collator collator;
 
 	private Color textbg = SystemColor.text;
 	private Color textfg = SystemColor.textText;
 	private Color selbg = SystemColor.textHighlight;
 	private Color selfg = SystemColor.textHighlightText;
 
 	private String last_selected_nick;
 
 	// SORT_*_FAVOR_MODE masks. These are ordered by priority.
 	private static final int [] SORT_MASKS = { User.OWNER_MASK, User.ADMIN_MASK, User.OP_MASK, User.HALFOP_MASK, User.VOICE_MASK, User.NORMAL_MASK };
 	private static final String SORT_CHARS = "123456";
 
 	public NickList()
 	{
 		super(ScrollPane.SCROLLBARS_ALWAYS); // _AS_NEEDED is so buggy...
 
 		this.collator = RFC1459.getCollator();
 		this.items = new CollatedHashtable(collator);
 
 		Panel p = new Panel();
 		CardLayout layout = new CardLayout();
 		p.setLayout(layout);
 
 		this.screen_list = new MyList();
 		
 		this.nick_pop = new NickInfoPopup();
 
 	  	p.add(screen_list, "list");
 	  	p.add(nick_pop, "popup");
 		
 	   	add(p);
 
 		screen_list.addMouseListener(this);
 		screen_list.addMouseMotionListener(this);
 	}
 	
 	public String [] getNicks()
 	{
 		String [] list = new String [items.size()];
 
 		int i = 0;
 		for (Enumeration e = items.keys(); e.hasMoreElements(); )
 			list[i++] = (String)e.nextElement();
 
 		return list;
 	}
 
 	/**
 	 * Gets the nick name for the item that contains <code>p</code>.
 	 *
 	 * @param p the point to look for in the nicks list.
 	 */
 	 
 	public String getNickAt(Point p)
 	{
 		NickItem ni = getNickItemAt(p);
 		if (ni != null)
 			return ni.getNick();
 		else
 			return(null);
 	}
 
 	/**
 	 * Gets the NickItem that contains <code>p</code>.
 	 *
 	 * @param p the point to look for in the NickList.
 	 */
 	 
 	protected NickItem getNickItemAt(Point p)
 	{
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem)e.nextElement();
 
 	  		Rectangle r = new Rectangle(ni.getBounds().x, ni.getBounds().y, screen_list.getBounds().width, ni.getBounds().height);
 	  		if (r.contains(p))
   				return ni;
 		}
 
 		return null;
 	}
 
 	public void loadList(User [] users)
 	{
 		items.clear();
 		for (int i = 0; i < users.length; i++)
 			add(users[i].getTag(), users[i].getModes());
 
 //		validate();
 		repaint();
 	}
 
 	/**
 	 * Adds <code>nick</code> to the list with user mode <code>mode</code>.
 	 */
 	 
 	public void add(String nick, int mode)
 	{
 		NickItem ni = new NickItem(nick);
 
 		switch (item_renderer)
 		{
 		case BULLET_RENDERER:
 			ni.setBullet(true);
 			break;
 		case SYMBOL_RENDERER:
 			ni.setBullet(false);
 			break;
 		default:
 			throw new IllegalArgumentException("Invalid NickItem renderer selected");
 		}
 
 		ni.setForeground(getForeground());
 		ni.setBackground(getBackground());
 		ni.setSelectedForeground(selfg);
 		ni.setSelectedBackground(selbg);
 		ni.setModes(mode);
 		ni.setTextBackground(textbg);
 		
 		if ((mode & User.OWNER_MASK) != 0)
 			ni.setTextForeground(User.COLOR_OWNER);
 		else if ((mode & User.ADMIN_MASK) != 0)
 			ni.setTextForeground(User.COLOR_ADMIN);
 		else if ((mode & User.OP_MASK) != 0) 
 			ni.setTextForeground(User.COLOR_OP);
 		else if ((mode & User.HALFOP_MASK) != 0) 
 			ni.setTextForeground(User.COLOR_HOP);
 		else if ((mode & User.VOICE_MASK) != 0)
 			ni.setTextForeground(User.COLOR_VOICE);
 		else
 			ni.setTextForeground(NickInfo.nickToColor(nick));
 		
 		items.put(nick, ni);
 	}
 
 	protected void setItemSelected(NickItem ni, boolean state)
 	{
 		ni.setSelected(state);
 
 		int item_state = state ? ItemEvent.SELECTED : ItemEvent.DESELECTED;
 		processItemEvent(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this, item_state));
 
 		screen_list.repaint();
 	}
 
 	protected void unselectAllItems()
 	{
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			setItemSelected(ni, false);
 		}
 	}
 
 	public int getSortMethod()
 	{
 		return sort_method;
 	}
 
 	public void setSortMethod(int method)
 	{
 		this.sort_method = method;
 		repaint();
 	}
 
 	public String getSelectedItem()
 	{
 		return last_selected_nick;
 	}
 
 	public Object[] getSelectedObjects()
 	{
 		Vector temp_list = new Vector(items.size());
 
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			if (ni.isSelected())
 				temp_list.addElement(ni.getNick());
 		}
 
 		Object[] objs = new String[temp_list.size()];
 		temp_list.copyInto(objs);
 		
 		return objs;
 	}
 
 	public int getItemRenderer()
 	{
 		return item_renderer;
 	}
 
 	public void setItemRenderer(int item_renderer)
 	{
 		this.item_renderer = item_renderer;
 	}
 	
 	public void repaint()
 	{
 		screen_list.setDirty();
 		screen_list.repaint();
 		validate();
 	}
 
 	public void setFont(Font f)
 	{
 		screen_list.setFont(f);
 		nick_pop.setFont(f);
 		repaint();
 	}
 	
 	public Color getTextBackground()
 	{
 		return(textbg);
 	}
 
 	public void setTextBackground(Color c)
 	{
 		super.setBackground(c);
 		screen_list.setBackground(c);
 		this.textbg = c;
 
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			ni.setTextBackground(c);
 		}
 		repaint();
 	}
 
 	public Color getTextForeground()
 	{
 		return textfg;
 	}
 
 	public void setTextForeground(Color c)
 	{
 		this.textfg = c;
 
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			ni.setTextForeground(c);
 		}
 	}
 
 	public Color getSelectedBackground()
 	{
 		return selbg;
 	}
 
 	public void setSelectedBackground(Color c)
 	{
 		this.selbg = c;
 
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			ni.setSelectedBackground(c);
 		}
 	}
 
 	public Color getSelectedForeground()
 	{
 		return selfg;
 	}
 
 	public void setSelectedForeground(Color c)
 	{
 		this.selfg = c;
 
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 		{
 			NickItem ni = (NickItem) e.nextElement();
 			ni.setSelectedForeground(c);
 		}
 	}
 
 	public Dimension getPreferredSize()
 	{
 		Dimension d = super.getPreferredSize();
 		d.width = Math.min(d.width, PREF_MIN_WIDTH);
 		return(d);
 	}
 
 	/**
 	 * Translate the event as if it were thrown by this object.
 	 */
 	 
 	protected MouseEvent translateListMouseEvent(MouseEvent ev)
 	{
 		return new MouseEvent(this, ev.getID(), ev.getWhen(), ev.getModifiers(), ev.getX(), ev.getY(), ev.getClickCount(), ev.isPopupTrigger());
 	}
 
 	public void addMouseListener(MouseListener l)
 	{
 		if (null != l)
 			mouseListener = AWTEventMulticaster.add(mouseListener, l);
 	}
 
 	public void removeMouseListener(MouseListener l)
 	{
 		if (null != l)
 			mouseListener = AWTEventMulticaster.remove(mouseListener, l);
 	}
 
 	public void addActionListener(ActionListener l)
 	{
 		if (null != l)
 			actionListener = AWTEventMulticaster.add(actionListener, l);
 	}
 
 	public void removeActionListener(ActionListener l)
 	{
 		if (null != l)
 			actionListener = AWTEventMulticaster.remove(actionListener, l);
 	}
 
 	protected void processActionEvent(ActionEvent e)
 	{
 		if (actionListener != null)
 			actionListener.actionPerformed(e);
 	}
 
 	public void addItemListener(ItemListener l)
 	{
 		if (null != l)
 			itemListener = AWTEventMulticaster.add(itemListener, l);
 	}
 
 	public void removeItemListener(ItemListener l)
 	{
 		if (null != l)
 			itemListener = AWTEventMulticaster.remove(itemListener, l);
 	}
 
 	protected void processItemEvent(ItemEvent e)
 	{
 		if (itemListener != null)
 			itemListener.itemStateChanged(e);
 	}
 
 	public void mouseClicked(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 
 		if (source.equals(screen_list))
 		{
 			if (mouseListener != null)
 				mouseListener.mouseClicked(translateListMouseEvent(ev));
 
 			NickItem ni = getNickItemAt(ev.getPoint());
 			if (null == ni)
 				return;
 
 			int ev_modifiers = ev.getModifiers();
 			if ((ev_modifiers & MouseEvent.BUTTON1_MASK) != 0 || ev_modifiers == 0)
 			{
 				// WORKAROUND: #32 Netscape Navigator doesn't set it right for BUTTON1.
 				String nick = ni.getNick();
 
 				switch (ev.getClickCount())
 				{
 				case 1:
 					boolean previously_selected = ni.isSelected();
 					if (!previously_selected)
 						last_selected_nick = nick;
 					
 					if (0 == (ev_modifiers & MouseEvent.CTRL_MASK))
 						unselectAllItems();
 					
 					setItemSelected(ni, !previously_selected);
 					break;
 				case 2:
 					processActionEvent(new ActionEvent(NickList.this, ActionEvent.ACTION_PERFORMED, nick, ev.getModifiers()));
 					break;
 				}
 			}
 		}
 	}
 
 	public void mousePressed(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 
 		if (source.equals(screen_list))
 			if (mouseListener != null)
 				mouseListener.mousePressed(translateListMouseEvent(ev));
 	}
 
 	public void mouseReleased(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 
 		if (source.equals(screen_list))
 			if (mouseListener != null)
 				mouseListener.mouseReleased(translateListMouseEvent(ev));
 	}
 
 	public void mouseEntered(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 
 		if (source.equals(this))
 			if (mouseListener != null)
 				mouseListener.mouseEntered(ev);
 	}
 
 	public void mouseExited(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 
 		if (source.equals(this))
 		{
 			if (mouseListener != null)
 				mouseListener.mouseExited(ev);
 		}
 		else if (source.equals(screen_list))
 		{
 			mouse_coords = null;
 			repaint();
 		}
 	}
 	
 	public void mouseMoved(MouseEvent ev)
 	{
 		Object source = ev.getSource();
 		if (source.equals(screen_list))
 		{
 			mouse_coords = ev.getPoint();
 			repaint();
 		}
 	}
 	
 	public void mouseDragged(MouseEvent ev)
 	{
 	}
 
 
 
 
 	protected class MyList extends Canvas implements ComponentListener
 	{
 		private Image image;
 		private Graphics img_gfx;
 		private NickItem [] items; // This obscures items in NickList.
 		private Dimension items_size;
 		private boolean dirty_items;
 
 		MyList()
 		{
 			this.dirty_items = true;
 			NickList.this.addComponentListener(this);
 		}
 		
 		public void setDirty()
 		{
 			this.dirty_items = true;
 		}
 
 		private void createImageBuffer(Dimension size)
 		{
 			image = createImage(size.width, size.height);
    			if (img_gfx != null)
    				img_gfx.dispose();
 
 			img_gfx = image.getGraphics();
 		}
 
 		public void update(Graphics g)
 		{
 			paint(g);
 		}
 
 		public void paint(Graphics g)
 		{
 
 			if (dirty_items)
 			{
 				setSize(getPreferredSize());
 	  			super.repaint();
 	  			return;
 			}
 
 			Dimension size = getSize();
 
 			if (null == img_gfx	|| size.width != image.getWidth(this) || size.height != image.getHeight(this))
 				createImageBuffer(size);
 
 			img_gfx.setColor(getBackground());
 			img_gfx.fillRect(0, 0, size.width, size.height);
 			img_gfx.setColor(getForeground());
 
 			for (int i = 0; i < items.length; i++)
 			{
 				Rectangle r = items[i].getBounds();
 
 				// Draw item.
 				Graphics ng = img_gfx.create(r.x, r.y, size.width, r.height);
 	   			items[i].paint(ng);
 //				items[i].setSelected(false);
 				if (ng != null)
 					ng.dispose();
 			}
 			
 			paintRollover(img_gfx);
 
 			g.drawImage(image, 0, 0, this);
 		}
 
 		private void paintRollover(Graphics g)
 		{
 			nick_pop.setVisible(false);
 		
 			if (mouse_coords != null)
 			{
 				NickItem ni = getNickItemAt(mouse_coords);
 				String n = null;
 				if (ni != null)
 				{
 //					ni.setSelected(true);
 				
 					n = getNickAt(mouse_coords);
 					if (n != null)
 					{
 						nick_pop.setNick(n);
 						nick_pop.setVisible(true);
 					}
 				}
 			
 				nick_pop.setLocation(mouse_coords);
 			}
 
 			nick_pop.paint(g);
 		}
 	
 		public Dimension getMinimumSize()
 		{
 			return getPreferredSize();
 		}
 
 		public Dimension getPreferredSize()
 		{
 			if (dirty_items)
 			{
 				grabItems();
 				sortItems();
 
 				Graphics g = null != img_gfx ? img_gfx : getGraphics();
 
 				int y = 0;
 				int max_width = 0;
 
 				for (int i = 0; i < items.length; i++)
 				{
 					Dimension d = items[i].getSize(g);
 					items[i].setBounds(0, y, d.width, d.height);
 
 					max_width = Math.max(max_width, d.width);
 	  				y += d.height;
 				}
 
 				this.items_size = new Dimension(max_width, y);
 			}
 
 			return adjustToPane(items_size.width, items_size.height);
 		}
 
 		private Dimension adjustToPane(int w, int h)
 		{
 			Dimension vp = NickList.this.getViewportSize();
 
 			if (w < vp.width)
 				w = vp.width;
 		
 			if (h < vp.height)
 				h = vp.height;
 
 			return new Dimension(w, h);
 		}
 
 		private void grabItems()
 		{
 			// FIXME: Implement synchronization.
 			// Can't store directly to ITEMS array because NickList.this.items size could change and break the array store.
 
 			Vector temp_list = new Vector(NickList.this.items.size());
 
 			for (Enumeration e = NickList.this.items.elements(); e.hasMoreElements(); )
 			{
 				NickItem ni = (NickItem) e.nextElement();
 				temp_list.addElement(ni);
 			}
 
 			this.items = new NickItem [temp_list.size()];
 			temp_list.copyInto(items);
 
 			dirty_items = false;
 		}
 
 		// Sorting algorithm from James Gosling
 		
 		private void sortItems()
 		{
 			sort(items, 0, items.length - 1);
 		}
 		
 		private void sort(NickItem n[], int i, int j)
 		{
 			int k = i;
 			int l = j;
 			if(k >= l)
 				return;
 			else
 				if(k == l - 1)
 				{
 					NickItem a = n[k];
 					NickItem b = n[l];
 					if (compare(a, b) < 0)
 					{
 						n[k] = b;
 						n[l] = a;
 					}
 				}
 				
 			NickItem a = n[(k + l) / 2];
 			n[(k + l) / 2] = n[l];
 			n[l] = a;
 			
 			while(k < l) 
 			{
 				while(k < l && compare(a, n[k]) >= 0)
 					k++;
 				while(k < l && compare(a, n[l]) <= 0)
 					l--;
 				if(k < l)
 				{
 					NickItem ni = n[k];
 					n[k] = n[l];
 					n[l] = ni;
 				}
 			}
 			n[j] = n[l];
 			n[l] = a;
 					
 			sort(n, i, k - 1);
 			sort(n, l + 1, j);
 		}
 		
 		private int compare(NickItem i, NickItem j)
 		{
 			String n = i.getNick();
 			String m = j.getNick();
 			
 			int o = i.getModes();
 			int p = j.getModes();
 			
 			int comp = 0;
 			
 			switch (sort_method)
 			{
 			default:
 			case SORT_ALPHA:
 				comp = collator.compare(i.getNick(), j.getNick());
 				break;
 			case SORT_ALPHA_FAVOR_MODE:
 				// Will prefix the nick with a num char according to its highest privilege.			
 				int k = 0;
 				while ((k < SORT_MASKS.length) && ((o & SORT_MASKS[k]) == 0))
 					k++;
 				if (k >= SORT_MASKS.length)
 					k = SORT_MASKS.length - 1;
 				n = SORT_CHARS.charAt(k) + n;
 
 				k = 0;
 				while ((k < SORT_MASKS.length) && ((p & SORT_MASKS[k]) == 0))
 					k++;
 				if (k >= SORT_MASKS.length)
 					k = SORT_MASKS.length - 1;
 				m = SORT_CHARS.charAt(k) + m;
 
 				comp = collator.compare(n, m);
 			   	break;
 			}
 			
 			return(comp);
 		}
 
 		public void componentResized(ComponentEvent e)
 		{
 			setSize(getPreferredSize());
 		}
 
 		public void componentMoved(ComponentEvent e)
 		{
 		}
 
 		public void componentShown(ComponentEvent e)
 		{
 		}
 
 		public void componentHidden(ComponentEvent e)
 		{
 		}	
 	}
 	
 }