/*
 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.RFC1459;
 import ar.com.jkohen.irc.User;
 import ar.com.jkohen.awt.event.NewMouseWheelEvent;
 
 public class NickList extends Panel 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 NickListArea screen_list;
 	protected Scrollbar sb;
 	private NickInfoPopup nick_pop;
 	private Point mouse_coords;
 
 	protected Vector 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;
 	
 	private static boolean hasWindowFocus = true;
 
 	public NickList()
 	{
 		collator = RFC1459.getCollator(sort_method);
 		items = new Vector();
 		
 		setLayout(new BorderLayout(0, 0));
 		sb = new Scrollbar(Scrollbar.VERTICAL);
 		//sb.setBackground(SystemColor.text);

 		add("East", sb);
 		screen_list = new NickListArea(sb);
 		add("Center", screen_list);
 		nick_pop = new NickInfoPopup();
 		add("West", nick_pop);
 
 		screen_list.addMouseListener(this);
 		screen_list.addMouseMotionListener(this);
 		
 		enableWheelEvt(true);
 		
 		updateBar();
 	}
 	
 	public String[] getNicks()
 	{
 		String list[] = new String [items.size()];
 
 		int i = 0;
 		for (Enumeration e = items.elements(); e.hasMoreElements(); )
 			list[i++] = ((NickItem)e.nextElement()).getNick();
 
 		return list;
 	}
 
 	/**
 	 * Gets the nick name for the item that contains p.
 	 *
 	 * @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 p.
 	 *
 	 * @param p the point to look for in the NickList.
 	 */
 
 	protected NickItem getNickItemAt(Point p)
 	{
 		for (int i = screen_list.offset; i < items.size(); i++ )
 		{
 			NickItem ni = (NickItem)items.elementAt(i);
 
 	  		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.removeAllElements();
 		for (int i = 0; i < users.length; i++)
 			add(users[i]);
 		repaint();
 	}
 
 	/**
 	 * Adds nick to the list with user mode mode.
 	 */
 	 
 	public void add(User u)
 	{
 		if (u == null)
 			throw new IllegalArgumentException("Invalid User");
 			
 		String nick = u.getTag();
 		int mode = u.getModes();
 
 		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));
 			
 		
 		/* Sort at insert */
 		nick = ni.toString();
 		for (int i = 0; i < items.size(); i++)
 		{
 			Object o = items.elementAt(i);
 			int comp = collator.compare(nick, o.toString());
 			if (comp < 0)
 			{
 				items.insertElementAt(ni, i);
 				return;
 			}
 			else if (comp == 0)
 			{
 				items.setElementAt(ni, i);
 				return;
 			}
 		}
 		
 		items.addElement(ni);
 
 	}
 	
 	public void remove(String u)
 	{
 		if (u == null)
 			throw new IllegalArgumentException("Invalid User");
 
 		for (int i = 0; i < items.size(); i++)
 		{
 			NickItem ni = (NickItem)items.elementAt(i);
 			int comp = collator.compare(u, ni.getNick());
 			if (comp == 0)
 			{
 				items.removeElementAt(i);
 				return;
 			}
 		}
 	}
 	
 	public void update(String u1, User u2)
 	{
 		remove(u1);
 		add(u2);
 	}
 
 	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 synchronized void setSortMethod(int method)
 	{
 		this.sort_method = method;
 		collator = RFC1459.getCollator(sort_method);
 	}
 
 	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.repaint();
 		updateBar();
 	}
 
 	public void setFont(Font f)
 	{
 		screen_list.setFont(f);
 		nick_pop.setFont(f);
 		repaint();
 	}
 	
 	public void setBackground(Color c)
 	{
 		screen_list.setBackground(c);
 		repaint();
 	}
 	
 	public Color getTextBackground()
 	{
 		return(textbg);
 	}
 
 	public void setTextBackground(Color 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);
 	}
 	
 	public static void setWindowFocus(boolean b)
 	{
 		NickList.hasWindowFocus = b;
 	}
 	
 	public Point getScrollPosition()
 	{
 		return(mouse_coords);
 	}	
 	
    	public void updateBar()
  	{
 		if (sb == null)
  			return;
 
 		if (screen_list != null)
 		{ 		
  			int size = items.size();
  			int visible = screen_list.getVisible();
 
 			if (size >= visible)
 			{
 				sb.setValues(sb.getValue(), visible, 0, size);
 				sb.setBlockIncrement(size / 10);
 				return;
 			}	
 		}
 
 		sb.setValues(0, 0, 0, 0);
 		sb.setBlockIncrement(0);
  	}
 
 
 	/**
 	 * 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)
 	{
 		if (!hasWindowFocus || !isShowing())
 			return;
 			
 		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;
 			screen_list.repaint();
 		}
 	}
 	
 	public void mouseMoved(MouseEvent ev)
 	{
 		if (!hasWindowFocus || !isShowing())
 			return;
 			
 		Object source = ev.getSource();
 		if (source.equals(screen_list))
 		{
 			mouse_coords = ev.getPoint();
 			screen_list.repaint();
 		}
 	}
 	
 	public void mouseDragged(MouseEvent ev)
 	{
 	}
 
 	public void mouseWheelMoved(NewMouseWheelEvent ev)
 	{
 		if (ev != null)
 		{
 			mouse_coords = ev.getPoint();
 			screen_list.mouseWheelMoved(ev);
 			repaint();
 		}
 	}
 
 	public void processEvent(AWTEvent ev)
 	{
 		if (ev.getClass() == NewMouseWheelEvent.EventClass)
 			mouseWheelMoved(new NewMouseWheelEvent((MouseEvent)ev));
 		
 		super.processEvent(ev);
 	}
 	
 	public void enableWheelEvt(boolean b)
 	{
 		if (b && NewMouseWheelEvent.EventMask > 0)
 			enableEvents(NewMouseWheelEvent.EventMask);
 		else
 			disableEvents(NewMouseWheelEvent.EventMask);
 	}
 
 	protected class NickListArea extends Canvas implements AdjustmentListener
 	{
 		private Image img;
 		private Graphics gph;
 		private Scrollbar bar;
 
 		private int offset, this_w, this_h, img_w, img_h, line_h, adjust, old_num, visible;
 		private int border_w = 2, border_h = 2;
 	
 		public NickListArea()
 		{
 			this(null);
 		}
 
 		public NickListArea(Scrollbar sb)
 		{
 			bar = sb;
 			bar.addAdjustmentListener(this);
 		}
 		
 		public void setBackground(Color c)
 		{
 			super.setBackground(c);
 			img = null;
 			this.repaint();
 		}
 
 		private void createBuffer(int w, int h)
 		{
 			this_w = w;
 			this_h = h;
 
 			img_w = this_w;
 			img_h = this_h;
 			img = createImage(img_w, img_h);
 			if (img != null)
 				gph = img.getGraphics();
 				
 			if (NewGraphics2D.hasRenderingHints)
 				NewGraphics2D.setRendering(gph);
 		}
 
 		public void update(Graphics g)
 		{
 			paint(g);
 		}
 	
 		public void paint(Graphics g)
 		{
 			int size = getLinesNum();
 			int w = getSize().width;
 			int h = getSize().height;
 
 			visible = 0;
 			
 			/* Component resized */
 			if ((img == null || w != this_w || h != this_h) && w > 0 && h > 0)
 			{
 				createBuffer(w, h);
 				
 				if (line_h > 0 && line_h < img_h)
 					visible = img_h / line_h;
 					
 				updateBar();
 			}
 			
 			gph.clearRect(0, 0, img_w, img_h);
 				
 			if (size > 0)
 			{
 				NickItem ni = (NickItem)items.elementAt(0);
 				Dimension d = ni.getSize(g);
 				line_h = d.height;
 				adjust = 0;
 				
 				if (line_h > 0 && line_h < img_h)
 					visible = img_h / line_h;
 				
 				if (((size * line_h) > img_h) && ((size - offset) * line_h) < img_h)
 				{
 					offset = size - (img_h / line_h);
 					if ((img_h % line_h) < (line_h / 2))
 						adjust = img_h % line_h;
 				}
 				
 //				if ((size * line_h) < img_h)
 //					offset = 0;
 				
 				paintText(gph);
 				paintRollover(gph);
 			}
 
 			paintBorder(gph);
 			if (img != null)
 				g.drawImage(img, 0, 0, this);
 		}
 
 		private void paintText(Graphics g) throws ArrayIndexOutOfBoundsException
 		{
 			Dimension d = null;
 			int y = border_h - adjust;
 			int i = offset;
 			while(i < items.size() && y < img_h)
 			{
 				NickItem ni = (NickItem)items.elementAt(i++);
 				if (ni != null)
 				{
 					d = ni.getSize(g);
 					ni.setBounds(0, y, img_h, d.height);
 					ni.paint(g);
 					y += d.height;
 				}
 			}			
 			
 			if (d != null)
 				line_h = d.height;
 		}
 	
 		private void paintBorder(Graphics g)
 		{
 			g.setColor(Color.gray);
 			int tempx1[] = {0, 0, this_w - 1};
 			int tempy1[] = {this_h - 1, 0, 0};
 			g.drawPolyline(tempx1, tempy1, 3);
 			
 			g.setColor(Color.black);
 			int tempx2[] = {1, 1, this_w - 2};
 			int tempy2[] = {this_h - 2, 1, 1};
 			g.drawPolyline(tempx2, tempy2, 3);
 			
 			g.setColor(Color.white);
 			int tempx3[] = {0, this_w - 1, this_w - 1};
 			int tempy3[] = {this_h - 1, this_h - 1, 0};
 			g.drawPolyline(tempx3, tempy3, 3);
 			
 			g.setColor(Color.lightGray);
 			int tempx4[] = {1, this_w - 2, this_w - 2};
 			int tempy4[] = {this_h - 2, this_h - 2, 1};
 			g.drawPolyline(tempx4, tempy4, 3);
 		}
 		
 		private void paintRollover(Graphics g)
 		{
 			nick_pop.setVisible(false);
 		
 			if (mouse_coords != null)
 			{
 				nick_pop.setLocation(mouse_coords);
 				NickItem ni = getNickItemAt(mouse_coords);
 				if (ni != null && ni.getNick() != null)
 				{
 					nick_pop.setNick(ni.getNick());
 					nick_pop.setVisible(true);
 					nick_pop.paint(g);
 				}
 			}
 		}
 	
 		public int getLinesNum()
 		{
 			if (items != null)
 				return(items.size());
 			else
 				return(0);
 		}
 		
 		public int getVisible()
 		{
 			return(visible);
 		}
 
 		public void adjustmentValueChanged(AdjustmentEvent e)
 		{
 			offset = e.getValue();
 			this.repaint();
 		}
 	 
 	 	public void mouseWheelMoved(NewMouseWheelEvent e)
 	 	{
 	 		int num = e.getUnitsToScroll();
 	 		if (num != 0)
 	 		{
 				offset = offset + num;
 			
 				if (offset < 0)
 					offset = 0;
 			
 				if (sb != null)
 				{
 					int max = sb.getMaximum() - sb.getVisibleAmount();
 					if (offset > max)
 						offset = max;
 						
 					sb.setValue(offset);
 				}
 				
 //		 		System.out.println("offset " + offset + " size " + getLinesNum() + " min " + bar.getMinimum() + " max " + bar.getMaximum() + " visible " + bar.getVisibleAmount());

 				this.repaint();
 			}
 		}
 	}
 	
 }