/*
 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
 */
 
 import java.awt.*;
 import java.awt.event.*;
 import java.util.Enumeration;
 import java.util.Vector;
 import ar.com.jkohen.irc.Channel;
 import ar.com.jkohen.awt.NewGraphics2D;
 import ar.com.jkohen.awt.event.ChatPanelListener;
 
 import ar.com.jkohen.irc.RFC1459;
 import ar.com.jkohen.util.Resources;
 import ar.com.jkohen.util.CollatedHashtable;
 
 public class ChatTabs extends Canvas implements MouseListener, MouseMotionListener
 {
 	public static final int NORMAL = 0;
 	public static final int CURRENT = 1;
 	public static final int ALARM = 2;
 	
 	public static final int STATES = 3;
 
 	public static final int LEFT = 0;
 	public static final int CENTER = 1;
 	public static final int COMPLETE = 2;
 	
 	public static final int POSITIONS = 3;
 	
 	public static final int PRIV = 0;
 	public static final int CHAN = 1;
 	public static final int SERV = 2;
 	
 	public static final int SOURCES = 3;
 
 	private ActionListener actionListener;
 
 	private Image image;
 	private Graphics img_gfx;
 	private boolean dirty_tabs;
 	private boolean dirty_images;
 
 	private CollatedHashtable name_to_tab;
 	private Vector public_tabs;
 	private Vector private_tabs;
 	private Vector service_tabs;
 
 	// Index of the tab displayed on the left margin.
 	private int first_displayed;
 	private Tab current_visible;
 
 	private Image images[][][];
 	
 	private EIRC eirc;
 	
 	public ChatTabs(EIRC eirc)
 	{
 		this.public_tabs = new Vector();
 		this.private_tabs = new Vector();
 		this.service_tabs = new Vector();
 		this.name_to_tab = new CollatedHashtable(RFC1459.getCollator());
 
 		this.images = new Image[ChatTabs.SOURCES][ChatTabs.STATES][ChatTabs.POSITIONS];
 		
 		this.eirc = eirc;
 		
 		addMouseListener(this);
 		addMouseMotionListener(this);
 	}
 
 	public void setImage(int source, int state, int pos, Image img)
 	{
 		this.images[source][state][pos] = img;
 
 		boolean changed_size = false;
 
 		int height = img.getHeight(this);
 //		while ((height = img.getHeight(this)) == -1);
 
 		// Use maximum height as a reference.
 		if (height > Tab.HEIGHT)
 		{
 			Tab.HEIGHT = height;
 			changed_size = true;
 		}
 
 		Image [] state_images = images[source][state];
 
 		if (state != CENTER && state_images[LEFT] != null)
 		{
 			int width_left = state_images[LEFT].getWidth(this);
 
 //			while ((width_left = state_images[LEFT].getWidth(this)) == -1);
 
 			int width_sides = width_left;
 
 			// Use maximum width as a reference.
 			if (Tab.WIDTH - width_sides > Tab.CENTER_WIDTH)
 			{
 				Tab.CENTER_WIDTH = Tab.WIDTH - width_sides;
 				changed_size = true;
 			}
 		}
 
 		if (changed_size)
 			this.dirty_tabs = true;
 
 		this.dirty_images = true;
 		repaint();
 	}
 
 	public void add(String name)
 	{
 		Tab tab = new Tab(name);
 
 		if (Channel.isChannel(name))
 		{
 			public_tabs.addElement(tab);
 		}
 		else
 		{
 			private_tabs.addElement(tab);
 		}
 		
 		if (eirc.isService(name))
 		{
 			tab.setService(true);
 		}
 
 		name_to_tab.put(name, tab);
 
 		this.dirty_tabs = true;
 		repaint();
 	}
 
 	public void remove(String name)
 	{
 		Tab tab = (Tab) name_to_tab.remove(name);
 
 		if (tab == null) return;
 
 		if (tab.equals(current_visible))
 		{
 			this.current_visible = null;
 		}
 
 		if (Channel.isChannel(name))
 		{
 			removeTab(public_tabs, tab);
 		}
 		else
 		{
 			removeTab(private_tabs, tab);
 		}
 
 		this.dirty_tabs = true;
 		repaint();
 	}
 
 	private void removeTab(Vector v, Tab t)
 	{
 		int index = v.indexOf(t);
 
 		v.removeElementAt(index);
 
 		if (t.getImageIndex() == CURRENT && 0 != v.size())
 		{
 			if (index > 0)
 				index--;
 
 			Tab reverted = (Tab) v.elementAt(index);
 			reverted.setImageIndex(CURRENT);
 		}
 	}
 
 	public void rename(String old_name, String name)
 	{
 		// FIXME: Must use an atomic operator.
 		Tab tab = (Tab) name_to_tab.get(old_name);
 		tab.setName(name);
 		name_to_tab.put(name, tab);
 		name_to_tab.remove(old_name);
 
 		this.dirty_tabs = true;
 		repaint();
 	}
 
 	public void setAlarm(String name, boolean alarm)
 	{
 		Tab tab = (Tab) name_to_tab.get(name);
 
 		int current_index = tab.getImageIndex();
 		int new_index = tab.equals(current_visible) ? ChatTabs.CURRENT : alarm ? ChatTabs.ALARM : ChatTabs.NORMAL;
 		
 		if (current_index != new_index)
 		{
 			tab.setImageIndex(new_index);
 			repaint();
 		}
 	}
 
 	public String getCurrent()
 	{
 		for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
 		{
 			Tab tab = (Tab) e.nextElement();
 			if (tab.getImageIndex() == CURRENT)
 				return tab.getName();
 		}
 
 		return null;
 	}
 
 	public void shiftLeft()
 	{
 		if (first_displayed > 0)
 		{
 			this.first_displayed--;
 			repaint();
 		}
 	}
 
 	public void shiftRight()
 	{
 		int visible_tabs = getSize().width / Tab.WIDTH;
 		int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());
 
 		if (first_displayed < tabs_in_longest_row - visible_tabs)
 		{
 			this.first_displayed++;
 			repaint();
 		}
 	}
 
 	public void makeVisible(String name)
 	{
 		Tab t = (Tab) name_to_tab.get(name);
 		if (t != null)
 		{
 			makeVisible(t);
 		}
 	}
 
 	protected void makeVisible(Tab new_visible)
 	{
 		if (new_visible == null)
 			return;
 			
 		int visible_tabs = getSize().width / Tab.WIDTH;
 		Vector v = Channel.isChannel(new_visible.getName()) ? public_tabs : private_tabs;
 		int tabs_in_row = v.size();
 		int index = v.indexOf(new_visible);
 
 		if (tabs_in_row <= visible_tabs)
 		{
 			this.first_displayed = 0;
 		}
 		else
 		{
 			if (tabs_in_row - index >= visible_tabs)
 				this.first_displayed = index;
 			else
 				this.first_displayed = tabs_in_row - visible_tabs;
 		}
 
 		if (current_visible != null)
 			current_visible.setImageIndex(ChatTabs.NORMAL);
 
 		new_visible.setImageIndex(ChatTabs.CURRENT);
 
 		current_visible = new_visible;
 		repaint();
 	}
 	
 	public void notVisible()
 	{
 		if (current_visible != null)
 			current_visible.setImageIndex(ChatTabs.NORMAL);
 		current_visible = null;
 		repaint();
 	}
 
 	public String getVisible()
 	{
 		if (current_visible != null)
 			return (current_visible.getName());
 		else
 			return (null);
 	}
 
 	public String getTabAt(Point p)
 	{
 		p.translate(first_displayed * Tab.WIDTH, 0);
 
 		Vector v = p.y < Tab.HEIGHT ? private_tabs : public_tabs;
 		int index = p.x / Tab.WIDTH;
 
 		if (index < v.size())
 			return ((Tab) v.elementAt(index)).getName();
 
 		return null;
 	}
 
 	protected void adjustTabsTitle(Graphics g)
 	{
 		FontMetrics fm = g.getFontMetrics();
 
 		for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
 		{
 			Tab tab = (Tab) e.nextElement();
 			String name = tab.getName();
 			String title = name;
 			int title_length = title.length();
 			// Trim tab's title if the name's too long.
 			while (fm.stringWidth(title) > Tab.CENTER_WIDTH)
 				title = name.substring(0, --title_length).concat("...");
 
 			tab.setTitle(title);
 		}
 	}
 
 	private void cacheImages()
 	{
 		for (int j = 0; j < SOURCES; j++)
 		{
 			for (int i = 0; i < STATES; i++)
 			{
 				Image state_images[] = images[j][i];
 
 				int width_left = state_images[LEFT].getWidth(this);
 
 				// Build and cache the complete image.
 				Image image = createImage(Tab.WIDTH, Tab.HEIGHT);
 				Graphics g = image.getGraphics();
 
 				g.drawImage(state_images[LEFT], 0, 0, this);
 				g.drawImage(state_images[CENTER], width_left, 0, Tab.CENTER_WIDTH, Tab.HEIGHT, this);
 
 				g.dispose();
 
 				state_images[COMPLETE] = image;
 			}
 		}
 	}
 
 	private void createImageBuffer(Dimension size)
 	{
 		if (size.height <= 0 || size.width <= 0)
 			return;		
 		
 		image = createImage(size.width, size.height);
 		if (null != img_gfx)
 			img_gfx.dispose();
 
 		img_gfx = image.getGraphics();
 
 		if (NewGraphics2D.hasRenderingHints)
 			NewGraphics2D.setRendering(img_gfx);
 	}
 	
 	public void setFont(Font f)
 	{
 		super.setFont(f);
 		repaint();
 	}
 	
 	public void setBackground(Color c)
 	{
 		super.setBackground(c);
 		this.dirty_images = true;
 		repaint();
 	}
 	
 	public void update(Graphics g)
 	{
 		paint(g);
 	}
 
 	public void paint(Graphics g)
 	{
 		Dimension size = getSize();
 
 		if (img_gfx == null || size.height != image.getHeight(this) || size.width != image.getWidth(this))
 			createImageBuffer(size);
 
 		if (dirty_tabs)
 		{
 			this.dirty_tabs = false;
 			adjustTabsTitle(img_gfx);
 		}
 
 		if (dirty_images)
 		{
 			this.dirty_images = false;
 			cacheImages();
 		}
 
 		img_gfx.setColor(getBackground());
 		img_gfx.fillRect(0, 0, size.width, size.height);
 		img_gfx.setColor(getForeground());
 
 		img_gfx.setFont(getFont());
 		FontMetrics fm = img_gfx.getFontMetrics();
 		int baseline = fm.getMaxDescent();
 		int text_height = fm.getMaxAscent() + fm.getLeading() + baseline;
 
 		Vector [] v = { private_tabs, public_tabs };
 
 		for (int i = 0; i < v.length; i++)
 		{
 			int x = -first_displayed * Tab.WIDTH;
 			int y = i * Tab.HEIGHT;
 			int source = i;
 			
 			for (Enumeration e = v[i].elements(); e.hasMoreElements(); )
 			{
 				Tab tab = (Tab) e.nextElement();
 				int state = tab.getImageIndex();
 
 				// Render the tab's background.
 	  			img_gfx.drawImage(images[(tab.isService() ? SERV : source)][state][ChatTabs.COMPLETE], x, y, this);
 
 				// Render the title centered on the CENTER image.
 	  			int width_left = images[(tab.isService() ? SERV : source)][state][ChatTabs.LEFT].getWidth(this); 
 
 				String title = tab.getTitle();
 				int text_width = fm.stringWidth(title);
 
 				img_gfx.drawString(title, x - (state != CURRENT ? 1 : 0) + width_left + (Tab.CENTER_WIDTH - text_width) / 2, y - (state != CURRENT ? 1 : 0) + Tab.HEIGHT - baseline - (Tab.HEIGHT - text_height) / 2);
 
 				x += Tab.WIDTH;
 			}
 		}
 
 		g.drawImage(image, 0, 0, this);	
 	}
 
 	public Dimension getMinimumSize()
 	{
 		return getPreferredSize();
 	}
 
 	public Dimension getPreferredSize()
 	{
 		int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());
 		return new Dimension(tabs_in_longest_row * Tab.WIDTH, 2 * Tab.HEIGHT);
 	}
 
 	public void setBounds(int x, int y, int w, int h)
 	{
 		super.setBounds(x, y, w, h);
 
 		if (null != image && (h != image.getHeight(this) || w != image.getWidth(this)))
 			createImageBuffer(new Dimension(w, h));
 
 		makeVisible(current_visible);
 	}
 
 	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 mousePressed(MouseEvent e)
 	{
 	}
 
 	public void mouseReleased(MouseEvent e)
 	{
 		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0 || e.getModifiers() == 0)
 		{
 			// WORKAROUND: #32 Netscape Navigator doesn't set it right for BUTTON1.
 			Point p = e.getPoint();
 
 	  		String tab = getTabAt(p);
 			if (tab != null)
 				processActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, tab));
 
 			// Hack to avoid getting mouse focus, and therefore take keyboard focus from other components.
 			e.consume();
 		}
 	}
 
 	public void mouseClicked(MouseEvent e)
 	{
 	}
 	
 	public void mouseDragged(MouseEvent e)
 	{
 	}
 	
 	public void mouseMoved(MouseEvent e)
 	{
 		Point p = e.getPoint();
 
   		String tab = getTabAt(p);
 		if (tab != null)
 			setCursor(new Cursor(Cursor.HAND_CURSOR));
 		else
 			setCursor(new Cursor(Cursor.DEFAULT_CURSOR));	
 	}
 	
 	public void mouseEntered(MouseEvent e)
 	{
 		Point p = e.getPoint();
 
   		String tab = getTabAt(p);
 		if (tab != null)
 			setCursor(new Cursor(Cursor.HAND_CURSOR));
 		else
 			setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
 	}
 	
 	public void mouseExited(MouseEvent e)
 	{
 		setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
 	}
 }
 
 class Tab
 {
 	static int HEIGHT = 0;
 	final static int WIDTH = 96;
 	static int CENTER_WIDTH = 0;
 
 	private String name;
 	private String title;
 	private int image_index;
 	private boolean service = false;
 
 	public Tab(String name)
 	{
 		this.name = this.title = name;
 		this.image_index = ChatTabs.NORMAL;
 	}
 
 	public String toString()
 	{
 		return getClass().getName().concat("[name=").concat(name).concat(",title=").concat(title).concat(",state=").concat(String.valueOf(image_index)).concat("]");
 	}
 
 	public String getName()
 	{
 		return name;
 	}
 
 	public void setName(String name)
 	{
 		this.name = name;
 	}
 
 	public String getTitle()
 	{
 		return title;
 	}
 
 	public void setTitle(String title)
 	{
 		this.title = title;
 	}
 
 	public int getImageIndex()
 	{
 		return image_index;
 	}
 
 	public void setImageIndex(int image_index)
 	{
 		this.image_index = image_index;
 	}
 	
 	public void setService(boolean service)
 	{
 		this.service = service;
 	}
 	
 	public boolean isService()
 	{
 		return(service);
 	}
 }