/*
 * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of Substance Kirill Grouchnikov nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.pushingpixels.substance.internal.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicListUI;

import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.animation.AnimationFacet;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.ComponentStateFacet;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
import org.pushingpixels.substance.internal.utils.UpdateOptimizationAware;
import org.pushingpixels.substance.internal.utils.UpdateOptimizationInfo;
import org.pushingpixels.trident.Timeline.TimelineState;
import org.pushingpixels.trident.callback.TimelineCallback;
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;

/**
 * UI for lists in <b>Substance</b> look and feel.
 * 
 * @author Kirill Grouchnikov
 */
public class SubstanceListUI extends BasicListUI implements
		UpdateOptimizationAware {
	/**
	 * Holds the list of currently selected indices.
	 */
	protected Map<Integer, Object> selectedIndices;

	/**
	 * Holds the currently rolled-over index, or -1 is there is none such.
	 */
	protected int rolledOverIndex;

	/**
	 * Property listener that listens to the
	 * {@link SubstanceLookAndFeel#WATERMARK_VISIBLE} property.
	 */
	protected PropertyChangeListener substancePropertyChangeListener;

	/**
	 * Listener for transition animations on list selections.
	 */
	protected ListSelectionListener substanceListSelectionListener;

	/**
	 * Listener for transition animations on list rollovers.
	 */
	protected RolloverFadeListener substanceFadeRolloverListener;

	private ComponentListener substanceComponentListener;

	private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker;

	private ListDataListener substanceListDataListener;

	private class SubstanceListSelectionListener implements
			ListSelectionListener {
		@Override
        public void valueChanged(final ListSelectionEvent e) {
			// fix for issue 469/474 - update the inner structures
			// in a separate event
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					handleListSelectionChange(e);
                    if (list != null) {
					    list.repaint();
                    }
				}
			});
		}

		private void handleListSelectionChange(ListSelectionEvent e) {
			if (list == null) {
				// fix for issue 464 - misbehaving app listener can change
				// look-and-feel without giving this listener a chance to
				// react
				return;
			}
			// optimization on large lists and large selections
			if (LafWidgetUtilities.hasNoAnimations(list,
					AnimationFacet.SELECTION))
				return;

			// no selection animations on non-Substance renderers
			if (!(list.getCellRenderer() instanceof SubstanceDefaultListCellRenderer)) {
				syncModelContents();
				return;
			}

			Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();
			boolean fadeCanceled = false;

			for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
				if (i >= list.getModel().getSize())
					continue;
				if (list.isSelectedIndex(i)) {
					// check if was selected before
					if (!selectedIndices.containsKey(i)) {
						// start fading in
						// System.out.println("Fade in on index " + i);

						selectedIndices.put(i, list.getModel().getElementAt(i));

						if (!fadeCanceled) {
							StateTransitionTracker tracker = getTracker(i,
									(i == rolledOverIndex), false);
							tracker.getModel().setSelected(true);

							initiatedTrackers.add(tracker);
							if (initiatedTrackers.size() > 25) {
								stateTransitionMultiTracker.clear();
								initiatedTrackers.clear();
								fadeCanceled = true;
							}
						}
					}
				} else {
					// check if was selected before and still points to the
					// same element
					if (selectedIndices.containsKey(i)) {
						if (selectedIndices.get(i) == list.getModel()
								.getElementAt(i)) {
							// start fading out
							// System.out
							// .println("Fade out on index " + i);

							if (!fadeCanceled) {
								StateTransitionTracker tracker = getTracker(i,
										(i == rolledOverIndex), true);
								tracker.getModel().setSelected(false);

								initiatedTrackers.add(tracker);
								if (initiatedTrackers.size() > 25) {
									stateTransitionMultiTracker.clear();
									initiatedTrackers.clear();
									fadeCanceled = true;
								}
							}
						}
						selectedIndices.remove(i);
					}
				}
			}
		}
	}

	private final class SubstanceListDataListener implements ListDataListener {
		private void _syncModelContents() {
			// fix for issue 469/474 - update the inner structures
			// in a separate event
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					syncModelContents();
				}
			});
		}

		@Override
		public void intervalRemoved(ListDataEvent e) {
			_syncModelContents();
		}

		@Override
		public void intervalAdded(ListDataEvent e) {
			_syncModelContents();
		}

		@Override
		public void contentsChanged(ListDataEvent e) {
			_syncModelContents();
		}
	}

	/**
	 * Listener for fade animations on list rollovers.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class RolloverFadeListener implements MouseListener,
			MouseMotionListener {
		@Override
        public void mouseClicked(MouseEvent e) {
		}

		@Override
        public void mouseEntered(MouseEvent e) {
		}

		@Override
        public void mousePressed(MouseEvent e) {
		}

		@Override
        public void mouseReleased(MouseEvent e) {
		}

		@Override
        public void mouseExited(MouseEvent e) {
			// if (SubstanceCoreUtilities.toBleedWatermark(list))
			// return;

			fadeOutRolloverIndication();
			// System.out.println("Nulling RO index");
			resetRolloverIndex();
		}

		@Override
        public void mouseMoved(MouseEvent e) {
			if (!list.isEnabled())
				return;
			handleMove(e);
		}

		@Override
        public void mouseDragged(MouseEvent e) {
			if (!list.isEnabled())
				return;
			handleMove(e);
		}

		/**
		 * Handles various mouse move events and initiates the fade animation if
		 * necessary.
		 * 
		 * @param e
		 *            Mouse event.
		 */
		private void handleMove(MouseEvent e) {
			// no rollover effects on non-Substance renderers
			if (!(list.getCellRenderer() instanceof SubstanceDefaultListCellRenderer)) {
				fadeOutRolloverIndication();
				resetRolloverIndex();
				return;
			}

			int roIndex = list.locationToIndex(e.getPoint());
			if ((roIndex >= 0) && (roIndex < list.getModel().getSize())) {
				// test actual hit
				if (!list.getCellBounds(roIndex, roIndex)
						.contains(e.getPoint())) {
					roIndex = -1;
				}
			}
			if ((roIndex < 0) || (roIndex >= list.getModel().getSize())) {
				fadeOutRolloverIndication();
				// System.out.println("Nulling RO index");
				resetRolloverIndex();
			} else {
				// check if this is the same index
				if ((rolledOverIndex >= 0) && (rolledOverIndex == roIndex))
					return;

				fadeOutRolloverIndication();

				// rollover on a new row
				StateTransitionTracker tracker = getTracker(roIndex, false,
						list.isSelectedIndex(roIndex));
				tracker.getModel().setRollover(true);
				rolledOverIndex = roIndex;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
	 */
	public static ComponentUI createUI(JComponent comp) {
		SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
		return new SubstanceListUI();
	}

	/**
	 * Creates a UI delegate for list.
	 */
	public SubstanceListUI() {
		super();
		rolledOverIndex = -1;
		selectedIndices = new HashMap<Integer, Object>();

		this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicListUI#installDefaults()
	 */
	@Override
	protected void installDefaults() {
		super.installDefaults();
		if (SubstanceCoreUtilities.toDrawWatermark(list)) {
			list.setOpaque(false);
		}

		syncModelContents();
	}

	@Override
	protected void uninstallDefaults() {
		selectedIndices.clear();

		super.uninstallDefaults();
	}

	@Override
	public void uninstallUI(JComponent c) {
		this.stateTransitionMultiTracker.clear();

		super.uninstallUI(c);
	}

	/**
	 * Repaints a single cell during the fade animation cycle.
	 * 
	 * @author Kirill Grouchnikov
	 */
	protected class CellRepaintCallback extends UIThreadTimelineCallbackAdapter {
		/**
		 * Associated list.
		 */
		protected JList list;

		/**
		 * Associated (animated) cell index.
		 */
		protected int cellIndex;

		/**
		 * Creates a new animation repaint callback.
		 * 
		 * @param list
		 *            Associated list.
		 * @param cellIndex
		 *            Associated (animated) cell index.
		 */
		public CellRepaintCallback(JList list, int cellIndex) {
			this.list = list;
			this.cellIndex = cellIndex;
		}

		@Override
		public void onTimelineStateChanged(TimelineState oldState,
				TimelineState newState, float durationFraction,
				float timelinePosition) {
			repaintCell();
		}

		@Override
		public void onTimelinePulse(float durationFraction,
				float timelinePosition) {
			// System.out.println(cellIndex + " at " + timelinePosition);
			repaintCell();
		}

		/**
		 * Repaints the associated cell.
		 */
		private void repaintCell() {
			// SwingUtilities.invokeLater(new Runnable() {
			// public void run() {
			if (SubstanceListUI.this.list == null) {
				// may happen if the LAF was switched in the meantime
				return;
			}
			try {
				maybeUpdateLayoutState();
				int cellCount = list.getModel().getSize();
				if ((cellCount > 0) && (cellIndex < cellCount)) {
					// need to retrieve the cell rectangle since the
					// cells can be moved while animating
					Rectangle rect = SubstanceListUI.this.getCellBounds(list,
							cellIndex, cellIndex);
					// System.out.println("Repainting " + cellIndex
					// + " at " + rect);
					list.repaint(rect);
				}
			} catch (RuntimeException re) {
				return;
			}
		}
		// });
		// }
	}

	@Override
	protected void installListeners() {
		super.installListeners();

		// Add listener for the selection animation
		substanceListSelectionListener = new SubstanceListSelectionListener();
		list.getSelectionModel().addListSelectionListener(
				substanceListSelectionListener);

		substanceFadeRolloverListener = new RolloverFadeListener();
		list.addMouseMotionListener(substanceFadeRolloverListener);
		list.addMouseListener(substanceFadeRolloverListener);

		substancePropertyChangeListener = new PropertyChangeListener() {
			@Override
            public void propertyChange(final PropertyChangeEvent evt) {
				if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
						.getPropertyName())) {
					list.setOpaque(!SubstanceCoreUtilities
							.toDrawWatermark(list));
				}
				if ("model".equals(evt.getPropertyName())) {
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							ListModel oldModel = (ListModel) evt.getOldValue();
							if (oldModel != null) {
								oldModel.removeListDataListener(substanceListDataListener);
							}
							ListModel newModel = (ListModel) evt.getNewValue();
							substanceListDataListener = new SubstanceListDataListener();
							newModel.addListDataListener(substanceListDataListener);
							syncModelContents();
						}
					});
				}
				if ("selectionModel".equals(evt.getPropertyName())) {
					// fix for issue 475 - wire the listener on the new
					// selection model
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							ListSelectionModel oldModel = (ListSelectionModel) evt
									.getOldValue();
							if (oldModel != null) {
								oldModel.removeListSelectionListener(substanceListSelectionListener);
							}
							ListSelectionModel newModel = (ListSelectionModel) evt
									.getNewValue();
							substanceListSelectionListener = new SubstanceListSelectionListener();
							newModel.addListSelectionListener(substanceListSelectionListener);
							syncModelContents();
						}
					});
				}
			}
		};
		list.addPropertyChangeListener(substancePropertyChangeListener);

		this.substanceComponentListener = new ComponentAdapter() {
			@Override
			public void componentMoved(ComponentEvent e) {
				// clear the rollover indexes since these are no longer
				// in sync with the mouse location
				fadeOutRolloverIndication();
				resetRolloverIndex();
			}
		};
		this.list.addComponentListener(this.substanceComponentListener);

		this.substanceListDataListener = new SubstanceListDataListener();
		this.list.getModel()
				.addListDataListener(this.substanceListDataListener);
	}

	@Override
	protected void uninstallListeners() {
		this.list.getModel().removeListDataListener(
				this.substanceListDataListener);
		this.substanceListDataListener = null;

		list.getSelectionModel().removeListSelectionListener(
				substanceListSelectionListener);
		substanceListSelectionListener = null;

		list.removeMouseMotionListener(substanceFadeRolloverListener);
		list.removeMouseListener(substanceFadeRolloverListener);
		substanceFadeRolloverListener = null;

		list.removePropertyChangeListener(substancePropertyChangeListener);
		substancePropertyChangeListener = null;

		this.list.removeComponentListener(this.substanceComponentListener);
		this.substanceComponentListener = null;

		super.uninstallListeners();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicListUI#paintCell(java.awt.Graphics, int,
	 * java.awt.Rectangle, javax.swing.ListCellRenderer, javax.swing.ListModel,
	 * javax.swing.ListSelectionModel, int)
	 */
	@Override
	protected void paintCell(Graphics g, int row, Rectangle rowBounds,
			ListCellRenderer cellRenderer, ListModel dataModel,
			ListSelectionModel selModel, int leadIndex) {
		Object value = dataModel.getElementAt(row);
		boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
		boolean isSelected = selModel.isSelectedIndex(row);

		Component rendererComponent = cellRenderer
				.getListCellRendererComponent(list, value, row, isSelected,
						cellHasFocus);

		if (!(rendererComponent instanceof SubstanceDefaultListCellRenderer)) {
			// if it's not Substance renderer - ask the Basic delegate to paint
			// it.
			super.paintCell(g, row, rowBounds, cellRenderer, dataModel,
					selModel, leadIndex);
			return;
		}

		boolean isWatermarkBleed = updateInfo.toDrawWatermark;

		int cx = rowBounds.x;
		int cy = rowBounds.y;
		int cw = rowBounds.width;
		int ch = rowBounds.height;

		// if (isFileList) {
		// // Shrink renderer to preferred size. This is mostly used on Windows
		// // where selection is only shown around the file name, instead of
		// // across the whole list cell.
		// int w = Math
		// .min(cw, rendererComponent.getPreferredSize().width + 4);
		// if (!isLeftToRight) {
		// cx += (cw - w);
		// }
		// cw = w;
		// }

		Graphics2D g2d = (Graphics2D) g.create();
		g2d.setComposite(LafWidgetUtilities.getAlphaComposite(list, g));
		if (!isWatermarkBleed) {
			Color background = rendererComponent.getBackground();
			// optimization - only render background if it's different
			// from the list background
			if ((background != null)
					&& (!list.getBackground().equals(background) || this.updateInfo.isInDecorationArea)) {
				g2d.setColor(background);
				g2d.fillRect(cx, cy, cw, ch);
			}
		} else {
			BackgroundPaintingUtils.fillAndWatermark(g2d, this.list,
					rendererComponent.getBackground(), new Rectangle(cx, cy,
							cw, ch));
		}

		StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(
				row, rendererComponent);
		Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
				: modelStateInfo.getStateContributionMap());
		ComponentState currState = ((modelStateInfo == null) ? getCellState(
				row, rendererComponent) : modelStateInfo.getCurrModelState());

		// if the renderer is disabled, do not show any highlights
		boolean hasHighlights = false;
		if (rendererComponent.isEnabled()) {
			if (activeStates != null) {
				for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
						.entrySet()) {
					hasHighlights = (this.updateInfo
							.getHighlightAlpha(stateEntry.getKey())
							* stateEntry.getValue().getContribution() > 0.0f);
					if (hasHighlights)
						break;
				}
			} else {
				hasHighlights = (this.updateInfo.getHighlightAlpha(currState) > 0.0f);
			}
		}

		JList.DropLocation dropLocation = list.getDropLocation();
		if (dropLocation != null && !dropLocation.isInsert()
				&& dropLocation.getIndex() == row) {
			// mark drop location
			SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
					.getColorScheme(list,
							ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
							currState);
			SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
					.getColorScheme(list, ColorSchemeAssociationKind.BORDER,
							currState);
			Rectangle cellRect = new Rectangle(cx, cy, cw, ch);
			HighlightPainterUtils.paintHighlight(g2d, this.rendererPane,
					rendererComponent, cellRect, 0.8f, null, fillScheme,
					borderScheme);
		} else {
			if (hasHighlights) {
				Rectangle cellRect = new Rectangle(cx, cy, cw, ch);
				if (activeStates == null) {
					float alpha = this.updateInfo.getHighlightAlpha(currState);
					if (alpha > 0.0f) {
						SubstanceColorScheme fillScheme = this.updateInfo
								.getHighlightColorScheme(currState);
						SubstanceColorScheme borderScheme = this.updateInfo
								.getHighlightBorderColorScheme(currState);
						g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
								list, alpha, g));
						HighlightPainterUtils.paintHighlight(g2d,
								this.rendererPane, rendererComponent, cellRect,
								0.8f, null, fillScheme, borderScheme);
						g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
								list, g));
					}
				} else {
					for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
							.entrySet()) {
						ComponentState activeState = stateEntry.getKey();
						float alpha = this.updateInfo
								.getHighlightAlpha(activeState)
								* stateEntry.getValue().getContribution();
						if (alpha == 0.0f)
							continue;
						SubstanceColorScheme fillScheme = this.updateInfo
								.getHighlightColorScheme(activeState);
						SubstanceColorScheme borderScheme = this.updateInfo
								.getHighlightBorderColorScheme(activeState);
						g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
								list, alpha, g));
						HighlightPainterUtils.paintHighlight(g2d,
								this.rendererPane, rendererComponent, cellRect,
								0.8f, null, fillScheme, borderScheme);
						g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
								list, g));
					}
				}
			}
		}

		// System.out.println(row + ":" + rendererComponent.getBackground());
		rendererPane.paintComponent(g2d, rendererComponent, list, cx, cy, cw,
				ch, true);
		g2d.dispose();
	}

	public StateTransitionTracker getStateTransitionTracker(int row) {
		return this.stateTransitionMultiTracker.getTracker(row);
	}

	/**
	 * Returns the current state for the specified cell.
	 * 
	 * @param cellIndex
	 *            Cell index.
	 * @param rendererComponent
	 *            Renderer component for the specified cell index.
	 * @return The current state for the specified cell.
	 */
	public ComponentState getCellState(int cellIndex,
			Component rendererComponent) {
		boolean isEnabled = this.list.isEnabled();
		if (rendererComponent != null) {
			isEnabled = isEnabled && rendererComponent.isEnabled();
		}
		StateTransitionTracker tracker = this.stateTransitionMultiTracker
				.getTracker(cellIndex);
		if (tracker == null) {
			boolean isRollover = (rolledOverIndex >= 0)
					&& (rolledOverIndex == cellIndex);
			boolean isSelected = selectedIndices.containsKey(cellIndex);
			return ComponentState.getState(isEnabled, isRollover, isSelected);
		} else {
			ComponentState fromTracker = tracker.getModelStateInfo()
					.getCurrModelState();
			return ComponentState.getState(isEnabled,
					fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER),
					fromTracker.isFacetActive(ComponentStateFacet.SELECTION));
		}
	}

	public StateTransitionTracker.ModelStateInfo getModelStateInfo(int row,
			Component rendererComponent) {
		if (this.stateTransitionMultiTracker.size() == 0)
			return null;
		StateTransitionTracker tracker = this.stateTransitionMultiTracker
				.getTracker(row);
		if (tracker == null) {
			return null;
		} else {
			return tracker.getModelStateInfo();
		}
	}

	/**
	 * Resets the rollover index.
	 */
	public void resetRolloverIndex() {
		rolledOverIndex = -1;
	}

	/**
	 * Initiates the fade out effect.
	 */
	private void fadeOutRolloverIndication() {
		if (rolledOverIndex < 0)
			return;

		StateTransitionTracker tracker = getTracker(rolledOverIndex, true,
				list.isSelectedIndex(rolledOverIndex));
		tracker.getModel().setRollover(false);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
	 * javax.swing.JComponent)
	 */
	@Override
	public void update(Graphics g, JComponent c) {
		BackgroundPaintingUtils.updateIfOpaque(g, c);

		Graphics2D g2d = (Graphics2D) g.create();
		SubstanceStripingUtils.setup(c);
		this.updateInfo = new UpdateOptimizationInfo(c);
		this.paint(g2d, c);
		SubstanceStripingUtils.tearDown(c);
		g2d.dispose();
		this.updateInfo = null;
	}

	// /**
	// * Returns the current default color scheme. This method is for internal
	// use
	// * only.
	// *
	// * @return The current default color scheme.
	// */
	// public SubstanceColorScheme getDefaultColorScheme() {
	// if (this.updateInfo != null)
	// return this.updateInfo.defaultScheme;
	// return null;
	// }
	//
	// public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
	// {
	// if (this.updateInfo != null)
	// return updateInfo.getHighlightColorScheme(state);
	// return null;
	// }

	private UpdateOptimizationInfo updateInfo;

	// private class UpdateOptimizationInfo {
	// public boolean toDrawWatermark;
	//
	// private Map<ComponentState, SubstanceColorScheme> highlightSchemeMap;
	//
	// private Map<ComponentState, SubstanceColorScheme> borderSchemeMap;
	//
	// private Map<ComponentState, Float> highlightAlphaMap;
	//
	// public SubstanceColorScheme defaultScheme;
	//
	// public DecorationAreaType decorationAreaType;
	//
	// public boolean isInDecorationArea;
	//
	// public UpdateOptimizationInfo() {
	// this.toDrawWatermark = SubstanceCoreUtilities.toDrawWatermark(list);
	// this.defaultScheme = SubstanceColorSchemeUtilities.getColorScheme(
	// list, ComponentState.DEFAULT);
	// this.highlightAlphaMap = new EnumMap<ComponentState, Float>(
	// ComponentState.class);
	// this.highlightSchemeMap = new EnumMap<ComponentState,
	// SubstanceColorScheme>(
	// ComponentState.class);
	// this.borderSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
	// ComponentState.class);
	// this.decorationAreaType = SubstanceLookAndFeel
	// .getDecorationType(list);
	//
	// SubstanceSkin skin = SubstanceCoreUtilities.getSkin(list);
	// this.isInDecorationArea = (this.decorationAreaType != null)
	// && skin
	// .isRegisteredAsDecorationArea(this.decorationAreaType)
	// && SubstanceCoreUtilities.isOpaque(list);
	// }
	//
	// public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
	// {
	// if (!this.highlightSchemeMap.containsKey(state)) {
	// this.highlightSchemeMap.put(state,
	// SubstanceColorSchemeUtilities.getColorScheme(list,
	// ColorSchemeAssociationKind.HIGHLIGHT, state));
	// }
	// return this.highlightSchemeMap.get(state);
	// }
	//
	// public SubstanceColorScheme getHighlightBorderColorScheme(
	// ComponentState state) {
	// if (!this.borderSchemeMap.containsKey(state)) {
	// this.borderSchemeMap.put(state, SubstanceColorSchemeUtilities
	// .getColorScheme(list,
	// ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
	// state));
	// }
	// return this.borderSchemeMap.get(state);
	// }
	//
	// public float getHighlightAlpha(ComponentState state) {
	// if (!this.highlightAlphaMap.containsKey(state)) {
	// this.highlightAlphaMap.put(state, SubstanceColorSchemeUtilities
	// .getHighlightAlpha(list, state));
	// }
	// return this.highlightAlphaMap.get(state);
	// }
	// }

	private void syncModelContents() {
		if (list == null)
			return;
		stateTransitionMultiTracker.clear();
		selectedIndices.clear();
		for (int i = 0; i < list.getModel().getSize(); i++) {
			if (list.isSelectedIndex(i)) {
				selectedIndices.put(i, list.getModel().getElementAt(i));
			}
		}
		list.repaint();
	}

	private StateTransitionTracker getTracker(final int row,
			boolean initialRollover, boolean initialSelected) {
		StateTransitionTracker tracker = stateTransitionMultiTracker
				.getTracker(row);
		if (tracker == null) {
			ButtonModel model = new DefaultButtonModel();
			model.setSelected(initialSelected);
			model.setRollover(initialRollover);
			tracker = new StateTransitionTracker(list, model);
			tracker.registerModelListeners();
			tracker.setRepaintCallback(new RepaintCallback() {
				@Override
				public TimelineCallback getRepaintCallback() {
					return new CellRepaintCallback(list, row);
				}
			});
			tracker.setName("row " + row);
			stateTransitionMultiTracker.addTracker(row, tracker);
		}
		return tracker;
	}

	@Override
	public UpdateOptimizationInfo getUpdateOptimizationInfo() {
		return this.updateInfo;
	}
}
