package adecWatt.view.jProp;

import javax.swing.JProgressBar;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.TreeSet;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;

import misc.Bundle;
import misc.Config;
import misc.ImagePreview;
import misc.Util;
import static misc.Util.GBC;
import static misc.Util.GBCNL;

import adecWatt.control.AdecWattManager;
import adecWatt.model.PreviewCache;
import adecWatt.model.ImageDB;
import adecWatt.view.JProp;
import adecWatt.view.JStringSet;

@SuppressWarnings ("serial") public class JImagePopup extends JPopupMenu implements ActionListener {

    // ========================================
    static public final String
	actionTag		= "Tag",
	actionAdd		= "Add",
	actionUpdate		= "Update",
	actionRemove		= "Remove",
	actionValidate		= "Validate",
	actionHorizontalSpin	= "HorizontalSpin",
	actionFilter		= "Filter",
	actionNoFilter		= "NoFilter";
    static public final Vector<String> emptyTag = new Vector<String> ();

    // ========================================
    static public final List<String>
	popupActionsNames	= Arrays.asList (AdecWattManager.actionRemoveImage,
						 AdecWattManager.actionChangeImage,
						 AdecWattManager.actionUnchange),
	iconActionsNames	= Arrays.asList (actionTag, actionAdd, actionUpdate, actionRemove, actionValidate);
    @SuppressWarnings ("unchecked")
    static public final Hashtable<String, Method> actionsMethod =
	Util.collectMethod (JImagePopup.class,
			    popupActionsNames, iconActionsNames);

    public void  actionPerformed (ActionEvent e) {
	Util.actionPerformed (actionsMethod, e, this);
    }

    // ========================================
    static public int thumbSide = 40;
    static public Dimension thumbDim = new Dimension (thumbSide, thumbSide);
    static public final int MAX_FILE_PREVIEW = 1024*1024;
    static public final int MAX_COL = 10;
    static public final int MAX_VISIBLE_LINE = 5;
    static public final int horizontalThickness = 2, verticalThickness = 4, selectedThickness = 1;
    static public Color selectionForeground = new Color (0x63, 0x82, 0xbf);
    static public Color selectionBackground = new Color (0xb8, 0xcf, 0xe5);
    static public Color standardBackground = new Color (0xee, 0xee, 0xee);
    static public final Border emptyBorder = BorderFactory.createEmptyBorder (horizontalThickness, verticalThickness,
									      horizontalThickness, verticalThickness);
    static public final Border selectedBorder =
	BorderFactory.createCompoundBorder (BorderFactory.createLineBorder (selectionForeground, selectedThickness),
					    BorderFactory.createEmptyBorder (horizontalThickness-selectedThickness, verticalThickness-selectedThickness,
									     horizontalThickness-selectedThickness, verticalThickness-selectedThickness));
    static public final Dimension DIM_AREA = new Dimension (10+horizontalThickness+MAX_COL*(thumbSide+horizontalThickness),
							    10+verticalThickness+MAX_VISIBLE_LINE*(thumbSide+verticalThickness));

    // ========================================
    ImageDB imageDB;
    boolean horizontalSpin;

    int nbImages;
    Vector<HighlightFile> nameList;

    JFilePreview preview = new JFilePreview (MAX_FILE_PREVIEW);
    JLabel previewId = new JLabel (" ", SwingConstants.CENTER);
    JList<String> previewTags = new JList<String> (emptyTag);
    JPanel selectionPanel;
    JViewport filesViewPort;
    JPanel filesPanel;
    JProp jProp;
    JLabel refLabel;
    JTextField filterTF = new JTextField ("", 16);
    JTextField noFilterTF = new JTextField ("", 16);
    Vector<ImageDB.NameLocal> namesLocals;
    Font localFont, normalFont;
    JProgressBar progressBar;
    
    public JImagePopup (JLabel jLabel, Point pos, final JProp jProp, ImageDB imageDB) {
	this.imageDB = imageDB;
	this.jProp = jProp;
	refLabel = jLabel;
	Util.addMenuItem (popupActionsNames, this, this);
	show (jLabel, pos.x, pos.y);
	DocumentListener documentListener = new DocumentListener () {
		public void changedUpdate (DocumentEvent e) {
		    doFilter ();
		}
		public void insertUpdate (DocumentEvent e) { changedUpdate (e); }
		public void removeUpdate (DocumentEvent e) { changedUpdate (e); }
	    };
	filterTF.getDocument ().addDocumentListener (documentListener);
	noFilterTF.getDocument ().addDocumentListener (documentListener);
	(new Thread () {
		public void run () {
		    updateNamesLocals ();
		    doFilter ();
		    if (nbSelection > 0)
			return;
		    try {
			setSelection (jProp.getLastValue ());
		    } catch (Exception e) {
		    }
		    updatePreview ();
		}
	    }).start ();
	previewTags.setForeground (Color.GRAY);
	previewTags.setBackground (getBackground ());
	previewId.setForeground (Color.GRAY);

    	normalFont = getFont ();
	localFont = normalFont.deriveFont (Font.ITALIC, normalFont.getSize ());
	selectionPanel = Util.getGridBagPanel ();
	horizontalSpin = jProp.getHorizontalSpin ();
	progressBar = new JProgressBar (0, 100);
	progressBar.setValue (0);

	JPanel cmdLine = new JPanel ();
	Util.addIconButton (iconActionsNames, this, cmdLine);
	cmdLine.add (Util.newCheckIcon (actionHorizontalSpin, new ActionListener () {
		public void  actionPerformed (ActionEvent e) {
		    horizontalSpin = ((JCheckBox)e.getSource ()).isSelected ();
		    updatePreview ();
		}
	    }, horizontalSpin));
	Util.addIconButton (actionFilter, null, cmdLine);
	cmdLine.add (filterTF);
	Util.addIconButton (actionNoFilter, null, cmdLine);
	cmdLine.add (noFilterTF);
	Util.unBoxButton (cmdLine);
	filesPanel = new JPanel (null);
	JPanel filesPanel2 = new JPanel ();
	filesPanel2.add (filesPanel);
	JScrollPane filesScrollPane = Util.getJScrollPane (filesPanel2);
	filesPanel2.addMouseListener (new MouseAdapter () {
		public void mousePressed (MouseEvent e) {
		    if (!SwingUtilities.isLeftMouseButton (e))
			return;
		    selectionPanel.requestFocusInWindow ();
		    clearSelection ();
		    updatePreview ();
		}
	    });
	filesScrollPane.setPreferredSize (DIM_AREA);
	filesViewPort = filesScrollPane.getViewport ();
	JScrollPane tagsScrollPane = Util.getJScrollPane (previewTags);
	tagsScrollPane.setPreferredSize (JFilePreview.previewDim);
	JPanel side = new JPanel (new BorderLayout ());
	side.add (previewId, BorderLayout.NORTH);
	side.add (preview, BorderLayout.CENTER);
	side.add (tagsScrollPane, BorderLayout.SOUTH);
	Util.addComponent (filesScrollPane, selectionPanel, GBC);
	Util.addComponent (side, selectionPanel, GBCNL);
	Util.addComponent (progressBar, selectionPanel, GBCNL);
	Util.addComponent (cmdLine, selectionPanel, GBCNL);

	selectionPanel.addKeyListener (new KeyAdapter () {
		public void keyPressed (KeyEvent e) {
		    switch (e.getKeyCode ()) {
		    case KeyEvent.VK_A:
			if (e.isControlDown ()) {
			    selectRange (0, nbImages-1);
			    setCursorIndex (lastSelection);
			}
			return;
		    case KeyEvent.VK_HOME:
			setCursorIndex (0);
			return;
		    case KeyEvent.VK_END:
			setCursorIndex (nbImages-1);
			return;
		    }
		    if (cursorIndex < 0 || cursorIndex > nbImages-1)
			return;
		    int nextCursorIndex = cursorIndex;
		    switch (e.getKeyCode ()) {
		    case KeyEvent.VK_SPACE:
			if (e.isControlDown ())
			    nameList.get (cursorIndex).toggleSelection ();
			else
			    setSelection (cursorIndex, true);
			return;
		    case KeyEvent.VK_UP:
		    case KeyEvent.VK_KP_UP:
			nextCursorIndex -= MAX_COL;
			break;
		    case KeyEvent.VK_DOWN:
		    case KeyEvent.VK_KP_DOWN:
			nextCursorIndex += MAX_COL;
			break;
		    case KeyEvent.VK_LEFT:
		    case KeyEvent.VK_KP_LEFT:
			nextCursorIndex--;
			break;
		    case KeyEvent.VK_RIGHT:
		    case KeyEvent.VK_KP_RIGHT:
			nextCursorIndex++;
			break;
		    default:
			return;
		    }
		    if (nextCursorIndex == cursorIndex ||
			nextCursorIndex < 0 ||
			nextCursorIndex > nbImages-1)
			return;
		    setCursorIndex (nextCursorIndex);
		    if (e.isShiftDown ())
			selectRange (beginSelection, nextCursorIndex);
		    else
			beginSelection = lastSelection = nextCursorIndex;
		}
	    });
    }

    public void chooseIconImage (JLabel attach) {
	updatePreview ();
	if (JOptionPane.showConfirmDialog (attach, selectionPanel,
					   Bundle.getTitle ("SelectImage"),
					   JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION || nbSelection < 1)
	    return;
	jProp.setSpin (horizontalSpin);
	jProp.setVal (Util.getBase (nameList.get (minSelection).nameLocal.fileName));
    }

    private String splitFilter (String pattern, TreeSet<String> set) {
	String part = null;
	for (String word : pattern.toLowerCase ().split ("\\s")) {
	    if (part != null) {
		set.add (part);
		part = null;
	    }
	    if (!word.isEmpty ()) {
		part = word;
	    }
	}
	return part;
    }

    public synchronized void doFilter () {
	TreeSet<String> setToKeep = new TreeSet<String> ();
	String partToKeep = splitFilter (filterTF.getText (), setToKeep);
	boolean noTag = filterTF.getText ().length () > 0 && partToKeep == null && setToKeep.size () < 1;
	TreeSet<String> setToForget = new TreeSet<String> ();
	String partToForget = splitFilter (noFilterTF.getText (), setToForget);
	Vector<ImageDB.NameLocal> result = new Vector<ImageDB.NameLocal> (namesLocals.size ());
	for (ImageDB.NameLocal namesLocal : namesLocals) {
	    Collection<String> tags = imageDB.getTags (namesLocal.fileName);
	    if (noTag && tags != null)
		continue;
	    if (partToKeep != null && !Util.containsPart (partToKeep, tags))
		continue;
	    if (setToKeep.size () > 0 && !Util.containsOne (setToKeep, tags))
		continue;
	    if (partToForget != null && Util.containsPart (partToForget, tags))
		continue;
	    if (setToForget.size () > 0 && Util.containsOne (setToForget, tags))
		continue;
	    result.add (namesLocal);
	}
	setFilesPanel (result);
    }

    public void setFilesPanel (Vector<ImageDB.NameLocal> namesLocals) {
	filesViewPort.invalidate ();
	nbSelection = 0;
	beginSelection = lastSelection = 0;
	cursorIndex = -1;
	nbImages = namesLocals.size ();
	nameList = new Vector<HighlightFile> (nbImages);
	for (ImageDB.NameLocal nameLocal: namesLocals)
	    nameList.add (new HighlightFile (nameLocal));
	filesPanel.removeAll ();
	GridLayout gridLayout = new GridLayout (0, MAX_COL);
	filesPanel.setLayout (gridLayout);
	for (int i = 0 ; i < nbImages; i++) {
	    HighlightFile highlightFile = nameList.get (i);
	    highlightFile.index = i;
	    filesPanel.add (highlightFile);
	}
	selectionPanel.setFocusable (true);
	filesViewPort.setViewPosition (new Point (0, 0));
	filesViewPort.repaint ();
	filesViewPort.validate ();
    }

    int cursorIndex = -1, minSelection, maxSelection, nbSelection;
    int beginSelection, lastSelection;
    public void clearSelection () {
	setCursorIndex (-1);
	if (nbSelection > 0)
	    for (int i = minSelection; i <= maxSelection; i++)
		nameList.get (i).setSelection (false);
	nbSelection = minSelection = maxSelection = beginSelection = lastSelection = 0;
    }

    public void setCursorIndex (int newVal) {
	if (cursorIndex == newVal)
	    return;
	if (cursorIndex >= 0)
	    nameList.get (cursorIndex).borderSelection (false);
	cursorIndex = newVal;
	if (cursorIndex < 0)
	    return;
	HighlightFile hf = nameList.get (cursorIndex);
	hf.borderSelection (true);
	hf.checkVisible ();
    }

    public synchronized void setSelection (String name) {
	if (name == null || name.isEmpty ())
	    return;
	name = Util.getBase (name);
	for (int i = 0; i < nbImages; i++)
	    if (name.equals (Util.getBase (nameList.get (i).nameLocal.fileName))) {
		setSelection (i, true);
		return;
	    }
    }
    public void setSelection (int index, boolean clearSelection) {
	if (clearSelection)
	    clearSelection ();
	setCursorIndex (index);
	if (index < 0 || index >= nbImages)
	    return;
	addSelection (index);
	beginSelection = lastSelection = index;
    }
    public void selectRange (int start, int stop) {
	int minRange = Integer.min (start, stop);
	int maxRange = Integer.max (start, stop);
	int minOldRange = Integer.min (beginSelection, lastSelection);
	int maxOldRange = Integer.max (beginSelection, lastSelection);
	beginSelection = start;
	lastSelection = stop;
	if (nbSelection > 0)
	    for (int i = minOldRange; i <= maxOldRange; i++) {
		HighlightFile name = nameList.get (i);
		if (name == null || !name.isSelected)
		    continue;
		name.setSelection (false);
		nbSelection--;
	    }
	for (int i = minRange; i <= maxRange; i++) {
	    HighlightFile name = nameList.get (i);
	    if (name == null || name.isSelected)
		continue;
	    nameList.get (i).setSelection (true);
	    nbSelection++;
	}
	if (nbSelection > 0) {
	    minSelection = Math.min (minSelection, minRange);
	    maxSelection = Math.max (maxSelection, maxRange);
	}
	updateMinMax ();
	updatePreview ();
    }
    public void addSelection (int index) {
	HighlightFile name = nameList.get (index);
	if (name == null)
	    return;
	name.setSelection (true);
	name.checkVisible ();
	if (nbSelection > 0) {
	    minSelection = Math.min (minSelection, index);
	    maxSelection = Math.max (maxSelection, index);
	} else
	    minSelection = maxSelection = index;
	nbSelection++;
	updatePreview ();
    }
    public void removeSelection (int index) {
	HighlightFile name = nameList.get (index);
	if (name == null)
	    return;
	name.setSelection (false);
	nbSelection--;
	updateMinMax ();
	updatePreview ();
    }
    public void updateMinMax () {
	if (nbSelection < 1)
	    return;
	for (int i = minSelection; i <= maxSelection; i++)
	    if (nameList.get (i).isSelected) {
		minSelection = i;
		break;
	    }
	for (int i = maxSelection; i >= minSelection; i--)
	    if (nameList.get (i).isSelected) {
		maxSelection = i;
		break;
	    }
	if (cursorIndex < minSelection)
	    nameList.get (minSelection).checkVisible ();
	if (cursorIndex > maxSelection)
	    nameList.get (maxSelection).checkVisible ();
    }
    public void updatePreview () {
	if (nbSelection < 1) {
	    preview.setFile (null, false);
	    previewId.setText ("");
	    previewTags.setListData (emptyTag);
	    Util.packWindow (previewTags);
	    return;
	}
	nameList.get (minSelection).preview ();
    }
    public void updateNamesLocals () {
	namesLocals = imageDB.listImage ();
	progressBar.setStringPainted (true);
	progressBar.setMaximum (namesLocals.size ());
	progressBar.setValue (0);
	int done = 0;
	PreviewCache previewCache = imageDB.previewCache;
	previewCache.prepareClean ();
	for (ImageDB.NameLocal nameLocal: namesLocals) {
	    nameLocal.icon = previewCache.getIcon (imageDB.getFile (nameLocal.fileName, nameLocal.isLocal));
	    done++;
	    progressBar.setValue (done);
	}
	previewCache.clean ();
	progressBar.setValue (0);
	progressBar.setStringPainted (false);
    }

    // ========================================
    class HighlightFile extends JLabel {
	public int index;
	public ImageDB.NameLocal nameLocal;
	public boolean isSelected;

	public HighlightFile (ImageDB.NameLocal nameLocal) {
	    super (nameLocal.icon, SwingConstants.CENTER);
	    setMinimumSize (thumbDim);
	    setPreferredSize (thumbDim);
	    setSize (thumbDim);
	    this.nameLocal = nameLocal;
	    setFont (nameLocal.isLocal ? localFont : normalFont);
	    setBorder (emptyBorder);
	    setOpaque (true);
	    setBackground (standardBackground);
	    addMouseListener (new MouseAdapter () {
		    public void mousePressed (MouseEvent e) {
			if (!SwingUtilities.isLeftMouseButton (e))
			    return;
			selectionPanel.requestFocusInWindow ();
			setCursorIndex (index);
			if (e.isControlDown ()) {
			    beginSelection = index;
			    lastSelection = index;
			    toggleSelection ();
			    return;
			}
			if (e.isShiftDown ()) {
			    selectRange (beginSelection, index);
			    return;
			}
			JImagePopup.this.setSelection (index, true);
		    }
		});
	}

	public void toggleSelection () {
	    if (isSelected)
		removeSelection (index);
	    else
		addSelection (index);
	}
	public void borderSelection (boolean isSelected) {
	    setBorder (isSelected ? selectedBorder : emptyBorder);
	}

	public void setSelection (boolean isSelected) {
	    setBackground (isSelected ? selectionBackground : standardBackground);
	    this.isSelected = isSelected;
	}
	public void preview () {
	    preview.setFile (imageDB.getFile (nameLocal.fileName, nameLocal.isLocal), horizontalSpin);
	    previewId.setText (Util.getBase (nameLocal.fileName));
	    previewTags.setListData (emptyTag);
	    Collection<String> tags = imageDB.getTags (nameLocal.fileName);
	    if (tags == null) {
		Util.packWindow (previewTags);
		return;
	    }
	    previewTags.setListData (new Vector<String> (tags));
	    Util.packWindow (previewTags);
	}

	public void checkVisible () {
	    Rectangle nameBounds = getBounds ();
	    Rectangle viewBounds = filesViewPort.getViewRect ();
	    if (!viewBounds.contains (nameBounds))
		filesViewPort.setViewPosition (new Point (0, nameBounds.y));
	}
    }

    // ========================================
    public void actionRemoveImage () {
	jProp.removeVal ();
    };
    public void actionChangeImage () {
	chooseIconImage (refLabel);
    }
    public void actionUnchange () {
	jProp.unchangeVal ();
    }
    
    // ========================================
    public void actionTag () {
	try {
	    if (nbSelection < 1)
		return;
	    String imageName = Util.getBase (nameList.get (minSelection).nameLocal.fileName);
	    JStringSet jStringSet = new JStringSet (imageDB.getTags (imageName));
	    jStringSet.init ();
	    if (JOptionPane.showConfirmDialog (refLabel,
					       jStringSet,
					       null, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
		return;
	    jStringSet.confirm ();
	    imageDB.putTags (imageName, jStringSet.getSet ());
	    doFilter ();
	    setSelection (imageName);
	} finally {
	    selectionPanel.requestFocusInWindow ();
	}
    }

    static public JFileChooser jFileChooser = new JFileChooser (Config.getString ("ImageDirName", ""));
    public boolean chooseFile () {
	jFileChooser.setFileFilter (new FileNameExtensionFilter (Bundle.getLabel ("ImageFilter"), PreviewCache.imageExtentions));
	jFileChooser.setAccessory (new ImagePreview (jFileChooser, MAX_FILE_PREVIEW));
	jFileChooser.setFileSelectionMode (JFileChooser.FILES_ONLY);
	if (jFileChooser.showOpenDialog (selectionPanel) != JFileChooser.APPROVE_OPTION)
	    return false;
	Config.setFile ("ImageDirName", jFileChooser.getSelectedFile ().getParentFile ());
	return true;
    }

    public void actionAdd () {
	jFileChooser.setMultiSelectionEnabled (true);
	if (!chooseFile ())
	    return;
	String last = null;
	for (File file : jFileChooser.getSelectedFiles ())
	    last = imageDB.addImage (file);
	updateNamesLocals ();
	doFilter ();
	// XXX sélection multiple
	setSelection (last);
    }
    public void actionUpdate () {
	if (nbSelection != 1)
	    return;
	jFileChooser.setMultiSelectionEnabled (false);
	if (!chooseFile ())
	    return;
	String newImageName = imageDB.updateImage (nameList.get (minSelection).nameLocal, jFileChooser.getSelectedFile ());
	updateNamesLocals ();
	doFilter ();
	setSelection (newImageName);
    }
    public void actionRemove () {
	if (nbSelection < 1)
	    return;
	boolean removed = false;
	TreeSet<String> selectedNames = new TreeSet<String> ();
	for (int i = minSelection; i <= maxSelection; i++) {
	    HighlightFile name = nameList.get (i);
	    if (!name.isSelected || !name.nameLocal.isLocal)
		continue;
	    imageDB.removeImage (name.nameLocal.fileName);
	    removed = true;
	    selectedNames.add (name.nameLocal.fileName);
	}
	if (!removed)
	    return;
	updateNamesLocals ();
	doFilter ();
	for (String name : selectedNames)
	    setSelection (name);
    }
    public void actionValidate () {
	if (nbSelection < 1)
	    return;
	TreeSet<String> imageNames = new TreeSet<String> ();
	TreeSet<String> imageTagNames = new TreeSet<String> ();
	for (int i = minSelection; i <= maxSelection; i++) {
	    HighlightFile name = nameList.get (i);
	    if (!name.isSelected)
		continue;
	    imageTagNames.add (name.nameLocal.fileName);
	    if (!name.nameLocal.isLocal)
		continue;
	    imageNames.add (name.nameLocal.fileName);
	}
	imageDB.promoteTag (imageTagNames); // XXX a changé de nom
	if (imageNames.size () == 0)
	    return;
	System.err.println ("coucou actionValidate:"+imageNames);
	imageDB.renameVisitor (imageNames);
	imageDB.promote (imageNames); // XXX a changé de nom
	updateNamesLocals ();
	doFilter ();
    }
    // ========================================
}
