/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander 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 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.ui.quicklist.item;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.ui.icon.IconManager;
import com.mucommander.ui.main.WindowManager;
import com.mucommander.ui.main.table.CellLabel;
import com.mucommander.ui.quicklist.QuickListWithDataList;
import com.mucommander.ui.quicksearch.QuickSearch;
import com.mucommander.ui.theme.ColorChangedEvent;
import com.mucommander.ui.theme.FontChangedEvent;
import com.mucommander.ui.theme.ThemeCache;
import com.mucommander.ui.theme.ThemeData;
import com.mucommander.ui.theme.ThemeListener;
import com.mucommander.ui.theme.ThemeManager;

/**
 * This class represent a data list for FileTablePopupWithDataList.
 * 
 * @author Arik Hadas
 */

public class QuickListDataList<T> extends JList {	
	private static final Logger LOGGER = LoggerFactory.getLogger(QuickListDataList.class);
	
	private final static int VISIBLE_ROWS_COUNT = 10;

	private QuickSearch<T> quickSearch = new QuickListQuickSearch();
	
	private Component nextFocusableComponent;

	public QuickListDataList(Component nextFocusableComponent){
		this.nextFocusableComponent = nextFocusableComponent;
		
		setFocusTraversalKeysEnabled(false);

		addMouseListenerToList();

		DataListItemRenderer itemRenderer = getItemRenderer();
		ThemeManager.addCurrentThemeListener(itemRenderer);
		setCellRenderer(itemRenderer);
	}

	public QuickListDataList(T[] data) {
		this(new Component() {});
		setListData(data);
	}

	protected DataListItemRenderer getItemRenderer() {
		return new DataListItemRenderer();
	}

	public QuickSearch<T> getQuickSearch() { 
		return quickSearch;
	}
	
	public String getItemAsString(T item) {
		return ""+item;
	}

	/**
	 * This function is called before showing TablePopupWithDataList.
	 * It does the required steps before the popup is shown.	
	 */
	@Override
	public void setListData(Object[] data) {
		super.setListData(data);

		int numOfRowsInList = getModel().getSize();
		if (numOfRowsInList > 0) {
			setVisibleRowCount(Math.min(numOfRowsInList, VISIBLE_ROWS_COUNT));
			setSelectedIndex(0);
			ensureIndexIsVisible(0);
		}
	}

	protected T getListItem(int index) {
		if (index > getModel().getSize() || index < 0)
			return null;

		return (T) getModel().getElementAt(index);
	}

	protected void addMouseListenerToList() {
		addMouseListener(new MouseAdapter() {

			public void mouseClicked(MouseEvent e) {
				// If there was double click on item of the popup's list, 
				// select it, and update the text component.
				if (e.getClickCount() == 2) {
					int index = locationToIndex(e.getPoint());
					setSelectedIndex(index);
					((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
				}
			}
		});
	}

	public void setForegroundColors(Color foreground, Color selectedForeground) {
		DataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();
		cellRenderer.setItemForeground(foreground);
		cellRenderer.setSelectedItemForeground(selectedForeground);
	}

	public void setBackgroundColors(Color background, Color selectedBackground) {
		DataListItemRenderer cellRenderer = (DataListItemRenderer) getCellRenderer();
		cellRenderer.setItemBackgound(background);
		cellRenderer.setSelectedItemBackgound(selectedBackground);
	}

	/**
	 * 
	 */
	protected class DataListItemRenderer extends DefaultListCellRenderer implements ThemeListener {

		private Color selectedItemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);
		private Color selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);
		private Color itemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);
		private Color itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);

		private Font itemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);

		protected DataListItemRenderer() { }

		@Override
		public Component getListCellRendererComponent(JList list, Object value, int rowIndex, boolean isSelected, boolean cellHasFocus) {
			// Let superclass deal with most of it...
			super.getListCellRendererComponent(list, value, rowIndex, isSelected, cellHasFocus);

			T item = getListItem(rowIndex);

			// Sanity check.
			if(item==null) {
				LOGGER.debug("tableModel.getCachedFileAtRow("+ rowIndex +") RETURNED NULL !");
				return null;
			}

			QuickSearch<T> search = QuickListDataList.this.getQuickSearch();
			boolean matches = search.isActive() ? search.matches(getItemAsString(item)) : true;

			CellLabel label = new CellLabel();
			label.setFont(itemFont);

			label.setText(getItemAsString(item));
			//label.setToolTipText(""+item);

			// Set background color depending on whether the row is selected or not, and whether the table has focus or not
			if (isSelected) {
				label.setBackground(selectedItemBackgound);
				label.setForeground(selectedItemForeground);
			}
			else {
				label.setBackground(matches ? itemBackgound : ThemeCache.unmatchedBackground);
				label.setForeground(matches ? itemForeground : ThemeCache.unmatchedForeground);
			}
			
			return label;
		}

		public void setSelectedItemBackgound(Color selectedItemBackgound) {
			this.selectedItemBackgound = selectedItemBackgound;
		}

		public void setSelectedItemForeground(Color selectedItemForeground) {
			this.selectedItemForeground = selectedItemForeground;
		}

		public void setItemBackgound(Color itemBackgound) {
			this.itemBackgound = itemBackgound;
		}

		public void setItemForeground(Color itemForeground) {
			this.itemForeground = itemForeground;
		}

		//////////////////////////////////
		// ThemeListener implementation //
		//////////////////////////////////

		public void colorChanged(ColorChangedEvent event) {		
			if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR)
				itemBackgound = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_BACKGROUND_COLOR);

			else if (event.getColorId() == ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR)
				itemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_ITEM_FOREGROUND_COLOR);

			else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR)
				selectedItemBackgound= ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_BACKGROUND_COLOR);

			else if (event.getColorId() == ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR)
				selectedItemForeground = ThemeManager.getCurrentColor(ThemeData.QUICK_LIST_SELECTED_ITEM_FOREGROUND_COLOR);
		}

		public void fontChanged(FontChangedEvent event) {
			itemFont = ThemeManager.getCurrentFont(ThemeData.QUICK_LIST_ITEM_FONT);		
		}
	}

	/**
	 * 
	 */
	public class QuickListQuickSearch extends QuickSearch<T> {

		public QuickListQuickSearch() {
			super(QuickListDataList.this);
		}

		@Override
		protected void searchStarted() {
		}

		@Override
		protected void searchStopped() {
			WindowManager.getCurrentMainFrame().getStatusBar().updateSelectedFilesInfo();
			QuickListDataList.this.repaint();
		}

		@Override
		protected int getNumOfItems() {
			return QuickListDataList.this.getModel().getSize();
		}

		@Override
		protected String getItemString(int index) {
			return getItemAsString(getListItem(index));
		}

		@Override
		protected void searchStringBecameEmpty(String searchString) {
			WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString); // TODO: is needed?
		}

		@Override
		protected void matchFound(int row, String searchString) {
			if(row!=getSelectedIndex()) {
				setSelectedIndex(row);
				ensureIndexIsVisible(row);
			}
			
			// Display the new search string in the status bar
            // that indicates that the search has yielded a match
			WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.STATUS_BAR_ICON_SET, QUICK_SEARCH_OK_ICON), false);
		}

		@Override
		protected void matchNotFound(String searchString) {
			// No file matching the search string, display the new search string with an icon
            // that indicates that the search has failed
			WindowManager.getCurrentMainFrame().getStatusBar().setStatusInfo(searchString, IconManager.getIcon(IconManager.STATUS_BAR_ICON_SET, QUICK_SEARCH_KO_ICON), false);
		}

		@Override
		public synchronized void keyPressed(KeyEvent e) {
			int keyCode = e.getKeyCode();

			// If quick search is not active...
			if (!isActive()) {
				// Return (do not start quick search) if the key is not a valid quick search input
				if(!isValidQuickSearchInput(e)) {
					if (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_ENTER)
						tryToTransferFocusToTheNextComponent();
					
					if (keyCode == KeyEvent.VK_ENTER)
						((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
					
					return;
				}

				// Start the quick search and continue to process the current key event
				start();
			}

			// At this point, quick search is active
			boolean keyHasModifiers = (e.getModifiersEx()&(KeyEvent.SHIFT_DOWN_MASK|KeyEvent.ALT_DOWN_MASK|KeyEvent.CTRL_DOWN_MASK|KeyEvent.META_DOWN_MASK))!=0;

			// Backspace removes the last character of the search string
			if(keyCode==KeyEvent.VK_BACK_SPACE && !keyHasModifiers) {
				// Search string is empty already
				if(isSearchStringEmpty())
					return;

				removeLastCharacterFromSearchString();

				// Find the row that best matches the new search string and select it
				findMatch(0, true, true);
			}
			// Escape immediately cancels the quick search
			else if(keyCode==KeyEvent.VK_ESCAPE && !keyHasModifiers) {
				stop();
			}
			// Up/Down jumps to previous/next match
			// Shift+Up/Shift+Down marks currently selected file and jumps to previous/next match
			else if((keyCode==KeyEvent.VK_UP || keyCode==KeyEvent.VK_DOWN) && !keyHasModifiers) {
				// Find the first row before/after the current row that matches the search string
				boolean down = keyCode==KeyEvent.VK_DOWN;
				findMatch(getSelectedIndex() + (down ? 1 : -1), down, false);
			}
			// If no modifier other than Shift is pressed and the typed character is not a control character (space is ok)
			// and a valid Unicode character, add it to the current search string
			else if(isValidQuickSearchInput(e)) {
				appendCharacterToSearchString(e.getKeyChar());

				// Find the row that best matches the new search string and select it
				findMatch(0, true, true);
			}
			else {
				switch(e.getKeyCode()) {
				case KeyEvent.VK_UP:
				{
					int numOfItems = getModel().getSize();				
					if (numOfItems > 0 && getSelectedIndex() == 0) {
						setSelectedIndex(numOfItems - 1);
						ensureIndexIsVisible(numOfItems - 1);
						e.consume();
					}
				}
				break;
				case KeyEvent.VK_DOWN:
				{
					int numOfItems = getModel().getSize();
					if (numOfItems > 0 && getSelectedIndex() == numOfItems - 1) {				
						setSelectedIndex(0);
						ensureIndexIsVisible(0);
						e.consume();
					}						
				}
				break;
				case KeyEvent.VK_ENTER:
					tryToTransferFocusToTheNextComponent();
					((QuickListWithDataList)(getParent().getParent().getParent())).itemSelected(getSelectedValue());
					stop();
					break;
				case KeyEvent.VK_TAB:
					tryToTransferFocusToTheNextComponent();
					stop();
					break;
				}

				// Do not update last search string's change timestamp
				return;
			}

			// Update last search string's change timestamp
			setLastSearchStringChange(System.currentTimeMillis());

			e.consume();
		}
		
		private void tryToTransferFocusToTheNextComponent() {
			if (nextFocusableComponent != null)
				nextFocusableComponent.requestFocus();
		}
	}
}
