/**

TrakEM2 plugin for ImageJ(C).
Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.

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 (http://www.gnu.org/licenses/gpl.txt )

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. 

You may contact Albert Cardona at acardona at ini.phys.ethz.ch
Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
**/

package ini.trakem2.display;

import ini.trakem2.utils.Utils;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public final class DisplayablePanel extends JPanel implements MouseListener {

	private static final long serialVersionUID = 1L;

	static public final int HEIGHT = 52;

	static private ImageIcon LOCKED = new ImageIcon(DisplayablePanel.class.getResource("/img/locked.png"));
	static private ImageIcon UNLOCKED = new ImageIcon(DisplayablePanel.class.getResource("/img/unlocked.png"));
	static private ImageIcon VISIBLE = new ImageIcon(DisplayablePanel.class.getResource("/img/visible.png"));
	static private ImageIcon INVISIBLE = new ImageIcon(DisplayablePanel.class.getResource("/img/invisible.png"));
	static private ImageIcon LINKED = new ImageIcon(DisplayablePanel.class.getResource("/img/linked.png"));
	static private ImageIcon UNLINKED = new ImageIcon(DisplayablePanel.class.getResource("/img/unlinked.png"));

	static private final Font SMALL = new Font("Courier", Font.ITALIC, 11);
	static private final Color GRAYISH = new Color(50, 50, 50);

	private JCheckBox c, c_locked, c_linked;
	private JLabel title, title2, idlabel;
	private JPanel titles;
	private SnapshotPanel sp;

	private Display display;
	private Displayable d;

	final private ML listener = new ML();

	public DisplayablePanel(Display display, Displayable d) {
		this.display = display;
		this.d = d;

		this.c = new JCheckBox();
		this.c.setSelected(d.isVisible());
		this.c.addMouseListener(listener);
		this.c.setIcon(INVISIBLE);
		this.c.setSelectedIcon(VISIBLE);
		this.c.setBackground(Color.white);
		Dimension maxdim = new Dimension(26, 14);
		this.c.setPreferredSize(maxdim);
		this.c.setMaximumSize(maxdim);

		this.c_locked = new JCheckBox();
		this.c_locked.setIcon(UNLOCKED);
		this.c_locked.setSelectedIcon(LOCKED);
		this.c_locked.setSelected(d.isLocked2());
		this.c_locked.addMouseListener(listener);
		this.c_locked.setBackground(Color.white);
		Dimension maxdim10 = new Dimension(26, 10);
		this.c_locked.setPreferredSize(maxdim10);
		this.c_locked.setMaximumSize(maxdim10);

		this.c_linked = new JCheckBox();
		this.c_linked.setIcon(UNLINKED);
		this.c_linked.setSelectedIcon(LINKED);
		this.c_linked.setSelected(d.isLinked());
		this.c_linked.addMouseListener(listener);
		this.c_linked.setBackground(Color.white);
		this.c_linked.setPreferredSize(maxdim10);
		this.c_linked.setMaximumSize(maxdim10);

		this.sp = new SnapshotPanel(display, d);
		title = new JLabel();
		title.addMouseListener(this);
		title2 = new JLabel();
		title2.addMouseListener(this);
		idlabel = new JLabel("#" + d.getId());
		idlabel.setFont(SMALL);
		idlabel.setForeground(GRAYISH);
		titles = new JPanel();
		updateTitle();
		
		
		GridBagLayout gb = new GridBagLayout();
		GridBagConstraints co = new GridBagConstraints();
		this.setLayout(gb);


		// Column of checkboxes
		co.anchor = GridBagConstraints.NORTHWEST;
		co.fill = GridBagConstraints.NONE;
		co.gridx = 0;
		co.gridy = 0;
		gb.setConstraints(c, co);
		add(c);
		//
		co.gridy = 1;
		co.anchor = GridBagConstraints.WEST;
		co.fill = GridBagConstraints.VERTICAL;
		co.weighty = 1;
		gb.setConstraints(c_locked, co);
		add(c_locked);
		//
		co.gridy = 2;
		co.anchor = GridBagConstraints.SOUTHWEST;
		co.weighty = 0;
		gb.setConstraints(c_linked, co);
		add(c_linked);
		
		// Snapshot panel
		co.anchor = GridBagConstraints.NORTHWEST;
		co.fill = GridBagConstraints.NONE;
		co.gridx = 1;
		co.gridy = 0;
		co.gridheight = 3;
		gb.setConstraints(sp, co);
		add(sp);
		
		// Column of strings
		co.gridheight = 1;
		co.weightx = 1;
		co.fill = GridBagConstraints.HORIZONTAL;
		co.gridx = 2;
		co.gridy = 0;
		gb.setConstraints(title, co);
		add(title);
		//
		co.gridy = 1;
		co.anchor = GridBagConstraints.WEST;
		co.fill = GridBagConstraints.BOTH;
		co.weighty = 1;
		gb.setConstraints(title2, co);
		add(title2);
		//
		co.gridy = 2;
		co.anchor = GridBagConstraints.SOUTHWEST;
		co.fill = GridBagConstraints.HORIZONTAL;
		co.weighty = 0;
		gb.setConstraints(idlabel, co);
		add(idlabel);

		setMinimumSize(new Dimension(230, DisplayablePanel.HEIGHT));
		setPreferredSize(new Dimension(248, DisplayablePanel.HEIGHT));

		addMouseListener(this);
		setBackground(Color.white);
		setBorder(BorderFactory.createLineBorder(Color.black));
	}

	/** For instance-recycling purposes. */
	public void set(final Displayable d) {
		this.d = d;
		c.setSelected(d.isVisible());
		c_locked.setSelected(d.isLocked2());
		c_linked.setSelected(d.isLinked());
		updateTitle();
		sp.set(d);
	}

	public void paint(final Graphics g) {
		if (null == g) return;
		if (display.getSelection().contains(d)) {
			if (null != display.getActive() && display.getActive() == d) { // can be null when initializing ... because swing is designed with built-in async
				setBackground(Color.cyan);
			} else {
				setBackground(Color.pink);
			}
		} else {
			setBackground(Color.white);
		}
		super.paint(g);
	}

	@Override
	public void setBackground(Color c) {
		super.setBackground(c);
		if (null != titles) {
			titles.setBackground(c);
			title.setBackground(c);
			title2.setBackground(c);
		}
	}

	private String makeUpdatedTitle() {
		if (null == d) { Utils.log2("null d "); return ""; }
		else if (null == d.getTitle()) { Utils.log2("null title for " + d); return ""; }
		final Class<?> c = d.getClass();
		if (c == Patch.class) {
			return d.getTitle();
		} else if (c == DLabel.class) {
			return d.getTitle().replace('\n', ' ');
		} else {
			// gather name of the enclosing object in the project tree
			return d.getProject().getMeaningfulTitle2(d);
		}
	}

	static private int MAX_CHARS = 20;

	public void updateTitle() {
		idlabel.setText("#" + d.getId());
		String t = makeUpdatedTitle();
		if (t.length() <= MAX_CHARS) {
			title.setText(t);
			title2.setText("");
			return;
		}
		// else split at MAX_CHARS
		// First try to see if it can be cut nicely
		int lastbracket = t.lastIndexOf('[');
		int end = -1,
		    start = -1;
		if (lastbracket -1 <= MAX_CHARS && -1 != lastbracket) { // there's a space in front of the [
			end = lastbracket -1;
			start = lastbracket;
		} else {
			end = start = MAX_CHARS;
		}
		title.setText(t.substring(0, end));

		if (t.length() - start -1 > MAX_CHARS) {
			title2.setText(t.substring(start, start + 7) + "..." + t.substring(t.length() -10));
		} else {
			title2.setText(t.substring(start));
		}

		title.setToolTipText(t);
		title2.setToolTipText(t);
	}

	private class ML extends MouseAdapter {
		public void mousePressed(final MouseEvent me) {
			display.dispatcher.exec(new Runnable() {
				public void run() {
					JCheckBox source = (JCheckBox) me.getSource();
					if (source == c) {
						if (!source.isSelected()) {
							d.setVisible(true);
						} else {
							// Prevent hiding when transforming
							if (Display.isTransforming(d)) {
								Utils.showStatus("Transforming! Can't change visibility.", false);
								SwingUtilities.invokeLater(new Runnable() { public void run() {
									c.setSelected(true);
								}});
								return;
							}
							d.setVisible(false);
						}
					} else if (source == c_locked) {
						final String[] members = new String[]{"locked"};
						if (!source.isSelected()) {
							// Prevent locking while transforming
							if (Display.isTransforming(d)) {
								Utils.logAll("Transforming! Can't lock.");
								SwingUtilities.invokeLater(new Runnable() { public void run() {
									c_locked.setSelected(false);
								}});
								return;
							}
							d.getLayerSet().addDataEditStep(d, members);
							d.setLocked(true);
							d.getLayerSet().addDataEditStep(d, members);
						} else {
							d.getLayerSet().addDataEditStep(d, members);
							d.setLocked(false);
							d.getLayerSet().addDataEditStep(d, members);
						}
						// Update lock checkboxes of linked Displayables, except of this one
						Collection<Displayable> lg = d.getLinkedGroup(null);
						if (null != lg) {
							lg.remove(d); // not this one!
							Display.updateCheckboxes(lg, LOCK_STATE, d.isLocked2());
						}
					} else if (source == c_linked) {
						// Prevent linking/unlinking while transforming
						if (Display.isTransforming(d)) {
							Utils.logAll("Transforming! Can't modify linking state.");
							SwingUtilities.invokeLater(new Runnable() { public void run() {
								c_linked.setSelected(d.isLinked());
							}});
							return;
						}

						final Set<Displayable> hs;

						if (!source.isSelected()) {
							final Rectangle box = d.getBoundingBox();
							hs = new HashSet<Displayable>(d.getLayer().find(box, true)); // only those visible and overlapping
							hs.addAll(d.getLayerSet().findZDisplayables(d.getLayer(), box, true));
							if (hs.size() > 1) {
								d.getLayerSet().addDataEditStep(hs, new String[]{"data"}); // "data" contains links, because links are dependent on bounding box of data
								for (final Displayable other : hs) {
									if (other == d) continue;
									d.link(other);
								}

								d.getLayerSet().addDataEditStep(hs, new String[]{"data"}); // "data" contains links, because links are dependent on bounding box of data
							} else {
								// Nothing to link, restore icon
								SwingUtilities.invokeLater(new Runnable() { public void run() {
									c_linked.setSelected(false);
								}});
							}
						} else {
							hs = d.getLinkedGroup(null);
							d.getLayerSet().addDataEditStep(hs, new String[]{"data"});
							d.unlink();
							d.getLayerSet().addDataEditStep(hs, new String[]{"data"});
						}

						// Update link checkboxes of linked Displayables, except of this one
						if (null != hs) {
							hs.remove(d); // not this one!
							if (hs.size() > 0) {
								Display.updateCheckboxes(hs, LINK_STATE);
								Display.updateCheckboxes(hs, LOCK_STATE);
							}
						}

						// Recompute list of links in Selection
						Display.updateSelection(Display.getFront());
					}
				}
			});
		}
	}

	public void mousePressed(final MouseEvent me) {
		if (display.isTransforming()) return;
		display.select(d, me.isShiftDown());
		if (me.isPopupTrigger() || (ij.IJ.isMacOSX() && me.isControlDown()) || MouseEvent.BUTTON2 == me.getButton() || 0 != (me.getModifiers() & Event.META_MASK)) {
			display.getPopupMenu().show(DisplayablePanel.this, me.getX(), me.getY());
		}
	}

	public void mouseReleased(MouseEvent me) {}
	public void mouseEntered(MouseEvent me) {}
	public void mouseExited (MouseEvent me) {}
	public void mouseClicked(MouseEvent me) {}

	public String toString() {
		return "Displayable panel for " + d.toString();
	}

	static public final int LOCK_STATE = 1;
	static public final int VISIBILITY_STATE = 2;
	static public final int LINK_STATE = 4;

	protected void updateCheckbox(final int cb, final boolean state) {
		switch(cb) {
			case LOCK_STATE:
				c_locked.setSelected(state);
				break;
			case VISIBILITY_STATE:
				c.setSelected(state);
				d.getLayerSet().clearScreenshots();
				break;
			case LINK_STATE:
				c_linked.setSelected(state);
				break;
			default:
				Utils.log2("Ooops: don't know what to do with checkbox code " + cb);
				break;
		}
	}

	protected void updateCheckbox(final int cb) {
		switch(cb) {
			case LOCK_STATE:
				c_locked.setSelected(d.isLocked2());
				break;
			case VISIBILITY_STATE:
				c.setSelected(d.isVisible());
				d.getLayerSet().clearScreenshots();
				break;
			case LINK_STATE:
				c_linked.setSelected(d.isLinked());
				break;
			default:
				Utils.log2("Ooops: don't know what to do with checkbox code " + cb);
				break;
		}
	}
}