/*
 * File    : TableCellImpl.java
 * Created : 24 nov. 2003
 * By      : Olivier
 * Originally PluginItem.java, and changed to be more generic.
 *
 * Copyright (C) 2004, 2005, 2006 Aelitis SAS, All rights Reserved
 *
 * 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.
 *
 * 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 ( see the LICENSE file ).
 *
 * 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
 *
 * AELITIS, SAS au capital de 46,603.30 euros,
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */
 
package org.gudy.azureus2.ui.swt.views.table.impl;

import java.text.Collator;
import java.util.*;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.components.*;
import org.gudy.azureus2.ui.swt.debug.ObfusticateCellText;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.plugins.UISWTGraphic;
import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTGraphicImpl;
import org.gudy.azureus2.ui.swt.views.table.TableCellSWT;
import org.gudy.azureus2.ui.swt.views.table.TableRowSWT;
import org.gudy.azureus2.ui.swt.views.table.TableViewSWT;
import org.gudy.azureus2.ui.swt.views.table.utils.TableColumnSWTUtils;

import com.aelitis.azureus.ui.common.table.TableColumnCore;
import com.aelitis.azureus.ui.common.table.TableColumnSortObject;
import com.aelitis.azureus.ui.common.table.TableRowCore;

import org.gudy.azureus2.plugins.ui.Graphic;
import org.gudy.azureus2.plugins.ui.UIRuntimeException;
import org.gudy.azureus2.plugins.ui.SWT.GraphicSWT;
import org.gudy.azureus2.plugins.ui.tables.*;


/** TableCellImpl represents one cell in the table.  
 * Table access is provided by BufferedTableItem.  
 * TableCellImpl is stored in and accessed by TableRowCore.
 * Drawing control gets passed to listeners.
 *
 * For plugins, this object is the implementation to TableCell.
 *
 * This object is needed to split core code from plugin code.
 */
public class TableCellImpl 
       implements TableCellSWT
{
	private static final LogIDs LOGID = LogIDs.GUI;
	
	private static final byte FLAG_VALID = 1;
	private static final byte FLAG_SORTVALUEISTEXT = 2;
	private static final byte FLAG_TOOLTIPISAUTO = 4;
  /**
   * For refreshing, this flag manages whether the row is actually up to date.
   * 
   * We don't update any visuals while the row isn't visible.  But, validility
   * does get set to true so that the cell isn't forced to refresh every
   * cycle when not visible.  (We can't just never call refresh when the row
   * is not visible, as refresh also sets the sort value)
   *  
   * When the row does become visible, we have to invalidate the row so
   * that the row will set its visuals again (this time, actually
   * updating a viewable object).
   */
	private static final byte FLAG_UPTODATE = 8;
	private static final byte FLAG_DISPOSED = 16;
	private static final byte FLAG_MUSTREFRESH = 32;
	private static final byte FLAG_VISUALLY_CHANGED_SINCE_REFRESH = 64;
	
	private byte flags;

  private TableRowCore tableRow;
  private Comparable sortValue;
  private BufferedTableItem bufferedTableItem;
  private ArrayList refreshListeners;
  private ArrayList disposeListeners;
  private ArrayList tooltipListeners;
	private ArrayList cellMouseListeners;
	private ArrayList cellMouseMoveListeners;
	private ArrayList cellVisibilityListeners;
  private TableColumnCore tableColumn;
  private byte refreshErrLoopCount;
  private byte tooltipErrLoopCount;
  private byte loopFactor;
  private Object oToolTip;
	private int iCursorID = -1;
	private Graphic graphic = null;
  
	public boolean bDebug = false;
  
  private static AEMonitor 	this_mon 	= new AEMonitor( "TableCell" );

  private static final String CFG_PAINT = "GUI_SWT_bAlternateTablePainting";
  private static boolean bAlternateTablePainting;

  static {
  	COConfigurationManager.addAndFireParameterListener(CFG_PAINT,
				new ParameterListener() {
					public void parameterChanged(String parameterName) {
						bAlternateTablePainting = COConfigurationManager
								.getBooleanParameter(CFG_PAINT);
					}
				});
  }

  public TableCellImpl(TableRowCore _tableRow, TableColumnCore _tableColumn,
      int position, BufferedTableItem item) {
    this.tableColumn = _tableColumn;
    this.tableRow = _tableRow;
    flags = FLAG_SORTVALUEISTEXT | FLAG_UPTODATE;
    refreshErrLoopCount = 0;
    tooltipErrLoopCount = 0;
    loopFactor = 0;

    if (item != null) {
    	bufferedTableItem = item;
    } else {
    	createBufferedTableItem(position);
    }

    tableColumn.invokeCellAddedListeners(TableCellImpl.this);
    //bDebug = (position == 1) && tableColumn.getTableID().equalsIgnoreCase("Peers");
  }

  /**
   * Initialize
   *  
   * @param _tableRow
   * @param _tableColumn
   * @param position
   */
  public TableCellImpl(TableRowSWT _tableRow, TableColumnCore _tableColumn,
                       int position) {
  	this(_tableRow, _tableColumn, position, null);
  }
  
  private void createBufferedTableItem(int position) {
    BufferedTableRow bufRow = (BufferedTableRow)tableRow;
    if (tableColumn.getType() == TableColumnCore.TYPE_GRAPHIC) {
    	if (bAlternateTablePainting) {
	      bufferedTableItem = new BufferedGraphicTableItem2(bufRow, position) {
	        public void refresh() {
	          TableCellImpl.this.refresh();
	        }
	        public void invalidate() {
	        	clearFlag(FLAG_VALID);
	        }
	      };
	    } else {
	      bufferedTableItem = new BufferedGraphicTableItem1(bufRow, position) {
	        public void refresh() {
	          TableCellImpl.this.refresh();
	        }
	        public void invalidate() {
	        	clearFlag(FLAG_VALID);
	        }
	      };
	    }
    	setOrientationViaColumn();
    } else {
      bufferedTableItem = new BufferedTableItemImpl(bufRow, position) {
        public void refresh() {
          TableCellImpl.this.refresh();
        }
        public void invalidate() {
        	clearFlag(FLAG_VALID);
        }
      };
    }
  }

  private void pluginError(Throwable e) {
    String sTitleLanguageKey = tableColumn.getTitleLanguageKey();

    String sPosition = (bufferedTableItem == null) 
      ? "null" 
      : "" + bufferedTableItem.getPosition() + 
        " (" + MessageText.getString(sTitleLanguageKey) + ")";
    Logger.log(new LogEvent(LOGID, "Table Cell Plugin for Column #" + sPosition
				+ " generated an exception ", e));
  }
  
  private void checkCellForSetting() {
  	if (isDisposed())
  		throw new UIRuntimeException("Table Cell is disposed.");
  }
  
  /* Public API */
  ////////////////
  
  public Object getDataSource() {
		// if we've been disposed then row/col are null
	  
	TableRowCore	row = tableRow;
	TableColumnCore	col	= tableColumn;
	
	if ( row == null || col == null){
		return( null );
	}
	
    return row.getDataSource(col.getUseCoreDataSource());
  }
  
  public TableColumn getTableColumn() {
    return tableColumn;
  }

  public TableRow getTableRow() {
    return tableRow;
  }

  public String getTableID() {
    return tableRow.getTableID();
  }
  
  public boolean isValid() {
  	// Called often.. inline faster
  	return (flags & FLAG_VALID) != 0;
    //return hasFlag(FLAG_VALID);
  }
  
  public Color getForegroundSWT() {
  	checkCellForSetting();

    return bufferedTableItem.getForeground();
  }
  
  public Color getBackgroundSWT() {
		checkCellForSetting();

		return bufferedTableItem.getBackground();
	}

  public int[] getBackground() {
		if (bufferedTableItem == null) {
			return null;
		}
		Color color = bufferedTableItem.getBackground();

		if (color == null) {
			return null;
		}

		return new int[] {
			color.getRed(),
			color.getGreen(),
			color.getBlue()
		};
	}

  // @see org.gudy.azureus2.plugins.ui.tables.TableCell#getForeground()
  public int[] getForeground() {
  	if (bufferedTableItem == null) {
  		return new int[] { 0, 0, 0 };
  	}
		Color color = bufferedTableItem.getForeground();

		if (color == null) {
			return new int[3];
		}

		return new int[] { color.getRed(), color.getGreen(), color.getBlue()
		};
	}
  
  public boolean setForeground(Color color) {
  	checkCellForSetting();

  	// Don't need to set when not visible
  	if (isInvisibleAndCanRefresh())
  		return false;

    boolean set = bufferedTableItem.setForeground(color);
    if (set) {
    	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
    }
    return set;
  }

	public boolean setForeground(int red, int green, int blue) {
		checkCellForSetting();

		// Don't need to set when not visible
		if (isInvisibleAndCanRefresh())
			return false;

		boolean set;
		if (red < 0 || green < 0 || blue < 0) {
			set = bufferedTableItem.setForeground(null);
		} else {
			set = bufferedTableItem.setForeground(red, green, blue);
		}
		if (set) {
			setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
		}
		return set;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableCell#setForeground(int[])
	public boolean setForeground(int[] rgb) {
		if (rgb == null || rgb.length < 3) {
			return setForeground((Color) null);
		}
		return setForeground(rgb[0], rgb[1], rgb[2]);
	}

  public boolean setForegroundToErrorColor() {
	  return setForeground(Colors.colorError);
  }

  public boolean setText(String text) {
  	checkCellForSetting();
  	if (text == null)
  		text = "";
  	boolean bChanged = false;

  	if (hasFlag(FLAG_SORTVALUEISTEXT) && !text.equals(sortValue)) {
  		bChanged = true;
  		sortValue = text;
    	tableColumn.setLastSortValueChange(SystemTime.getCurrentTime());
    	if (bDebug)
    		debug("Setting SortValue to text;");
  	}
  	

// Slower than setText(..)!
//  	if (isInvisibleAndCanRefresh()) {
//  		if (bDebug) {
//  			debug("setText ignored: invisible");
//  		}
//  		return false;
//  	}

    if (bufferedTableItem.setText(text) && !hasFlag(FLAG_SORTVALUEISTEXT))
    	bChanged = true;

		if (bDebug) {
			debug("setText (" + bChanged + ") : " + text);
		}

		if (bChanged) {
			setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
		}

		boolean do_auto = this.tableColumn.doesAutoTooltip();
		
		// If we were using auto tooltips (and we aren't any more), then
		// clear up previously set tooltips.
		if (!do_auto) {
			if (hasFlag(FLAG_TOOLTIPISAUTO)) {
				this.oToolTip = null;
				clearFlag(FLAG_TOOLTIPISAUTO);
			}
		}
		
		else {
			this.oToolTip = text;
			setFlag(FLAG_TOOLTIPISAUTO);
		}
		
  	return bChanged;
  }
  
  private boolean isInvisibleAndCanRefresh() {
  	return !isShown()
				&& (refreshListeners != null || tableColumn.hasCellRefreshListener());
  }
  
  public String getText() {
  	if (hasFlag(FLAG_SORTVALUEISTEXT) && sortValue instanceof String)
  		return (String)sortValue;
  	if (bufferedTableItem == null) {
  		return null;
  	}
    return bufferedTableItem.getText();
  }

  public boolean isShown() {
  	if (bufferedTableItem == null) {
  		return false;
  	}
    return bufferedTableItem.isShown();
  }
  
  public boolean setSortValue(Comparable valueToSort) {
		if (!tableColumn.isSortValueLive()) {
			// objects that can't change aren't live
			if (!(valueToSort instanceof Number) && !(valueToSort instanceof String)
					&& !(valueToSort instanceof TableColumnSortObject)) {
				tableColumn.setSortValueLive(true);
			}
		}
		return _setSortValue(valueToSort);
	}

  private boolean _setSortValue(Comparable valueToSort) {
  	checkCellForSetting();

    if (sortValue == valueToSort)
      return false;

    if (hasFlag(FLAG_SORTVALUEISTEXT)) {
    	clearFlag(FLAG_SORTVALUEISTEXT);
    	if (sortValue instanceof String)
	    	// Make sure text is actually in the cell (it may not have been if
	      // cell wasn't created at the time of setting)
	      setText((String)sortValue);
    }
    
    if ((valueToSort instanceof String) && (sortValue instanceof String)
				&& sortValue.equals(valueToSort)) {
			return false;
		}

    if ((valueToSort instanceof Number) && (sortValue instanceof Number)
				&& sortValue.equals(valueToSort)) {
			return false;
		}

  	if (bDebug)
  		debug("Setting SortValue to "
					+ ((valueToSort == null) ? "null" : valueToSort.getClass().getName()));
  	
  	tableColumn.setLastSortValueChange(SystemTime.getCurrentTime());
    sortValue = valueToSort;

    return true;
  }
  
  public boolean setSortValue(long valueToSort) {
  	checkCellForSetting();

		if ((sortValue instanceof Long)
				&& ((Long) sortValue).longValue() == valueToSort)
			return false;

		return _setSortValue(new Long(valueToSort));
  }
  
  public boolean setSortValue( float valueToSort ) {
  	checkCellForSetting();

		if (sortValue instanceof Float
				&& ((Float) sortValue).floatValue() == valueToSort)
			return false;

		return _setSortValue(new Float(valueToSort));
  }

  public Comparable getSortValue() {
  	if (bDebug)
			debug("GetSortValue;"
					+ (sortValue == null ? "null" : sortValue.getClass().getName() + ";"
							+ sortValue.toString()));

    if (sortValue == null) {
      if (bufferedTableItem != null)
        return bufferedTableItem.getText();
      return "";
    }
    return sortValue;
  }
  
  public void setToolTip(Object tooltip) {
    oToolTip = tooltip;
    clearFlag(FLAG_TOOLTIPISAUTO);
  }

  public Object getToolTip() {
    return oToolTip;
  }

	public boolean isDisposed() {
		return hasFlag(FLAG_DISPOSED);
	}
	
	// @see org.gudy.azureus2.plugins.ui.tables.TableCell#getMaxLines()
	public int getMaxLines() {
		if (bufferedTableItem == null) {
			// use 1 in case some plugin borks on div by zero
			return 1;
		}
		return bufferedTableItem.getMaxLines();
	}
  
  /* Start TYPE_GRAPHIC Functions */

	public Point getSize() {
    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return null;
    return ((BufferedGraphicTableItem)bufferedTableItem).getSize();
  }

  public int getWidth() {
  	Point pt = null;
  	
    if (bufferedTableItem instanceof BufferedGraphicTableItem) {
    	pt = ((BufferedGraphicTableItem)bufferedTableItem).getSize();
    } else {
    	Rectangle bounds = bufferedTableItem.getBounds();
    	if (bounds != null) {
    		pt = new Point(bounds.width, bounds.height);
    	}
    }
    if (pt == null)
      return -1;
    return pt.x;
  }

  public int getHeight() {
  	Point pt = null;
  	
    if (bufferedTableItem instanceof BufferedGraphicTableItem) {
    	pt = ((BufferedGraphicTableItem)bufferedTableItem).getSize();
    } else {
    	Rectangle bounds = bufferedTableItem.getBounds();
    	if (bounds != null) {
    		pt = new Point(bounds.width, bounds.height);
    	}
    }
    if (pt == null)
      return -1;
    return pt.y;
  }

  public boolean setGraphic(Image img) {
  	checkCellForSetting();

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return false;

    graphic = null;
    boolean b = ((BufferedGraphicTableItem)bufferedTableItem).setGraphic(img);
    if (b) {
    	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
    }
    return b;
  }

  public boolean setGraphic(Graphic img) {
  	if (img != null){
  		checkCellForSetting();
  	}

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return false;

    graphic = img;

    if (img == null) {
      boolean b = ((BufferedGraphicTableItem)bufferedTableItem).setGraphic(null);
      if (b) {
      	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
      }
    }

    if (img instanceof GraphicSWT){
    	Image imgSWT = ((GraphicSWT)img).getImage();
    	boolean b = ((BufferedGraphicTableItem)bufferedTableItem).setGraphic(imgSWT);
      if (b) {
      	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
      }
    }
    
    if (img instanceof UISWTGraphic){
    	Image imgSWT = ((UISWTGraphic)img).getImage();
    	boolean b = ((BufferedGraphicTableItem)bufferedTableItem).setGraphic(imgSWT);
      if (b) {
      	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
      }
    }
    
    return( false );
  }

  public Graphic getGraphic() {
  	if (graphic != null) {
  		return graphic;
  	}

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return null;
    Image img = ((BufferedGraphicTableItem)bufferedTableItem).getGraphic();
    return new UISWTGraphicImpl(img);
  }

  public Image getGraphicSWT() {
    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return null;
    return ((BufferedGraphicTableItem)bufferedTableItem).getGraphic();
  }

  public void setFillCell(boolean bFillCell) {
  	checkCellForSetting();

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return;
    
    if (bFillCell)
    	((BufferedGraphicTableItem)bufferedTableItem).setOrientation(SWT.FILL);
    else
    	setOrientationViaColumn();
    setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
  }

	public void setMarginHeight(int height) {
  	checkCellForSetting();

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return;
    ((BufferedGraphicTableItem)bufferedTableItem).setMargin(-1, height);
    setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
  }

  public void setMarginWidth(int width) {
  	checkCellForSetting();

    if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
      return;
    ((BufferedGraphicTableItem)bufferedTableItem).setMargin(width, -1);
    setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
  }

  /* End TYPE_GRAPHIC Functions */

  public void addRefreshListener(TableCellRefreshListener listener) {
  	try{
  		this_mon.enter();
  	
  		if (refreshListeners == null)
  			refreshListeners = new ArrayList(1);

  		if (bDebug) {
  			debug("addRefreshListener; count=" + refreshListeners.size());
  		}
  		refreshListeners.add(listener);
  		
  	}finally{
  		this_mon.exit();
  	}
  }

  public void removeRefreshListener(TableCellRefreshListener listener) {
  	try{
  		this_mon.enter();
  
	    if (refreshListeners == null)
	      return;
	
	    refreshListeners.remove(listener);
  	}finally{
  		
  		this_mon.exit();
  	}
  }

  public void addDisposeListener(TableCellDisposeListener listener) {
  	try{
  		this_mon.enter();
  
	    if (disposeListeners == null) {
	      disposeListeners = new ArrayList(1);
	    }
	    disposeListeners.add(listener);
  	}finally{
  		
  		this_mon.exit();
  	}
  }

  public void removeDisposeListener(TableCellDisposeListener listener) {
  	try{
  		this_mon.enter();
  
  		if (disposeListeners == null)
  			return;

  		disposeListeners.remove(listener);
  		
  	}finally{
  		
  		this_mon.exit();
  	}
  }
  
  public void addToolTipListener(TableCellToolTipListener listener) {
  	try{
  		this_mon.enter();
  
  		if (tooltipListeners == null) {
  			tooltipListeners = new ArrayList(1);
  		}
  		tooltipListeners.add(listener);
  		
  	}finally{
  		this_mon.exit();
  	}
  }

  public void removeToolTipListener(TableCellToolTipListener listener) {
  	try{
  		this_mon.enter();
  	
  		if (tooltipListeners == null)
  			return;

  		tooltipListeners.remove(listener);
  	}finally{
  		
  		this_mon.exit();
  	}
  }
  
  
	public void addMouseListener(TableCellMouseListener listener) {
		try {
			this_mon.enter();

			if (cellMouseListeners == null)
				cellMouseListeners = new ArrayList(1);

			cellMouseListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void removeMouseListener(TableCellMouseListener listener) {
		try {
			this_mon.enter();

			if (cellMouseListeners == null)
				return;

			cellMouseListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void addMouseMoveListener(TableCellMouseMoveListener listener) {
		try {
			this_mon.enter();

			if (cellMouseMoveListeners == null)
				cellMouseMoveListeners = new ArrayList(1);

			cellMouseMoveListeners.add(listener);
			
		} finally {
			this_mon.exit();
		}
	}

	public void removeMouseMoveListener(TableCellMouseMoveListener listener) {
		try {
			this_mon.enter();

			if (cellMouseMoveListeners == null)
				return;

			cellMouseMoveListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void addVisibilityListener(TableCellVisibilityListener listener) {
		try {
			this_mon.enter();

			if (cellVisibilityListeners == null)
				cellVisibilityListeners = new ArrayList(1);

			cellVisibilityListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void removeVisibilityListener(TableCellVisibilityListener listener) {
		try {
			this_mon.enter();

			if (cellVisibilityListeners == null)
				return;

			cellVisibilityListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void addListeners(Object listenerObject) {
		if (listenerObject instanceof TableCellDisposeListener) {
			addDisposeListener((TableCellDisposeListener)listenerObject);
		}

		if (listenerObject instanceof TableCellRefreshListener)
			addRefreshListener((TableCellRefreshListener)listenerObject);

		if (listenerObject instanceof TableCellToolTipListener)
			addToolTipListener((TableCellToolTipListener)listenerObject);

		if (listenerObject instanceof TableCellMouseMoveListener) {
			addMouseMoveListener((TableCellMouseMoveListener) listenerObject);
		}

		if (listenerObject instanceof TableCellMouseListener) {
			addMouseListener((TableCellMouseListener) listenerObject);
		}

		if (listenerObject instanceof TableCellVisibilityListener)
			addVisibilityListener((TableCellVisibilityListener)listenerObject);
	}

	/**
	 * If a plugin in trying to invalidate a cell, then clear the sort value
	 * too.
	 */
	public void invalidate() {
  	checkCellForSetting();

  	invalidate(true);
	}

	/* Start of Core-Only function */
  //////////////////////////////////
	
  public void invalidate(final boolean bMustRefresh) {
  	if (!hasFlag(FLAG_VALID)) {
  		if (bMustRefresh) {
  			if (hasFlag(FLAG_MUSTREFRESH)) {
  				return;
  			}
  		} else {
  			return;
  		}
  	}
  	clearFlag(FLAG_VALID);
  	
  	setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);

  	if (bDebug)
  		debug("Invalidate Cell;" + bMustRefresh);

  	if (bMustRefresh) {
  		setFlag(FLAG_MUSTREFRESH);
  		if (bufferedTableItem != null) {
  			bufferedTableItem.invalidate();
  		}
  	}
  }
  
  public boolean refresh() {
    return refresh(true);
  }
  
  public boolean refresh(boolean bDoGraphics) {
		boolean isCellShown = isShown();
		boolean isRowShown = tableRow.getView().isRowVisible(tableRow);
		return refresh(bDoGraphics, isRowShown, isCellShown);
  }

  public boolean refresh(boolean bDoGraphics, boolean bRowVisible) {
  	return refresh(bDoGraphics, bRowVisible, isShown());
  }
  
  private boolean bInRefresh = false;
  public boolean refresh(boolean bDoGraphics, boolean bRowVisible,  boolean bCellVisible)
  {
	  bCellVisible &= bRowVisible;
	  bCellVisible &= tableRow.getView().isColumnVisible(tableColumn);
	  
	  boolean ret = getVisuallyChangedSinceRefresh();

	  int iErrCount = 0;
	  try {
		  if (refreshErrLoopCount > 2) {
			  return ret;
		  }

		  iErrCount = tableColumn.getConsecutiveErrCount();
		  if (iErrCount > 10) {
			  refreshErrLoopCount = 3;
			  return ret;
		  }

		  if (bInRefresh) {
			  // Skip a Refresh call when being called from within refresh.
			  // This could happen on virtual tables where SetData calls us again, or
			  // if we ever introduce plugins to refresh.
			  if (bDebug)
				  debug("Calling Refresh from Refresh :) Skipping.");
			  return ret;
		  }
		  bInRefresh = true;

		  // See bIsUpToDate variable comments
		  if (bCellVisible && !isUpToDate()) {
			  if (bDebug)
				  debug("Setting Invalid because visible & not up to date");
			  clearFlag(FLAG_VALID);
			  setFlag(FLAG_UPTODATE);
		  } else if (!bCellVisible && isUpToDate()) {
			  if (bDebug)
				  debug("Setting not up to date because cell not visible " + Debug.getCompressedStackTrace());
		  	clearFlag(FLAG_UPTODATE);
		  }
		  
		  if (bDebug) {
			  debug("Cell Valid?" + hasFlag(FLAG_VALID) + "; Visible?" + tableRow.isVisible() + "/" + bufferedTableItem.isShown());
		  }
		  int iInterval = tableColumn.getRefreshInterval();
		  if (iInterval == TableColumnCore.INTERVAL_INVALID_ONLY 
		  	&& !hasFlag(FLAG_MUSTREFRESH | FLAG_VALID) && hasFlag(FLAG_SORTVALUEISTEXT) && sortValue != null
			  && tableColumn.getType() == TableColumnCore.TYPE_TEXT_ONLY) {
			  if (bCellVisible) {
				  if (bDebug)
					  debug("fast refresh: setText");
				  ret = setText((String)sortValue);
				  setFlag(FLAG_VALID);
			  }
		  } else if ((iInterval == TableColumnCore.INTERVAL_LIVE ||
			  (iInterval == TableColumnCore.INTERVAL_GRAPHIC && bDoGraphics) ||
			  (iInterval > 0 && (loopFactor % iInterval) == 0) ||
			  !hasFlag(FLAG_VALID) || hasFlag(FLAG_MUSTREFRESH))) 
		  {
			  boolean bWasValid = isValid();

			  if (bDebug)
				  debug("invoke refresh; wasValid? " + bWasValid);

			  long lTimeStart = SystemTime.getMonotonousTime();
			  tableColumn.invokeCellRefreshListeners(this, !bCellVisible);
			  if (refreshListeners != null) {
				  for (int i = 0; i < refreshListeners.size(); i++) {
					  TableCellRefreshListener l = (TableCellRefreshListener)refreshListeners.get(i);
					  if(l instanceof TableCellLightRefreshListener)
						  ((TableCellLightRefreshListener)l).refresh(this,!bCellVisible);
					  else
						  l.refresh(this);
				  }
			  }
			  long lTimeEnd = SystemTime.getMonotonousTime();
			  tableColumn.addRefreshTime(lTimeEnd - lTimeStart);

			  // Change to valid only if we weren't valid before the listener calls
			  // This is in case the listeners set valid to false when it was true
			  if (!bWasValid) 
			  	setFlag(FLAG_VALID);

			  if (hasFlag(FLAG_MUSTREFRESH)) {
			  	clearFlag(FLAG_MUSTREFRESH);
			  }
		  }
		  loopFactor++;
		  refreshErrLoopCount = 0;
		  if (iErrCount > 0)
			  tableColumn.setConsecutiveErrCount(0);

		  ret = getVisuallyChangedSinceRefresh();
		  if (bDebug)
			  debug("refresh done; visual change? " + ret + ";" + Debug.getCompressedStackTrace());
	  } catch (Throwable e) {
		  refreshErrLoopCount++;
		  if (tableColumn != null) {
			  tableColumn.setConsecutiveErrCount(++iErrCount);
		  }
		  pluginError(e);
		  if (refreshErrLoopCount > 2)
			  Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR,
				  "TableCell will not be refreshed anymore this session."));
	  } finally {
	  	clearFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
		  bInRefresh = false;
	  }
	  return ret;
  }
  
  public boolean getVisuallyChangedSinceRefresh() {
  	return hasFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
  }


  public void dispose() {
  	setFlag(FLAG_DISPOSED);

    tableColumn.invokeCellDisposeListeners(this);

    if (disposeListeners != null) {
      try {
        for (Iterator iter = disposeListeners.iterator(); iter.hasNext();) {
          TableCellDisposeListener listener = (TableCellDisposeListener)iter.next();
          listener.dispose(this);
        }
        disposeListeners = null;
      } catch (Throwable e) {
        pluginError(e);
      }
    }

    if (bufferedTableItem != null)
      bufferedTableItem.dispose();
    
    refreshListeners = null;
    bufferedTableItem = null;
    tableColumn = null;
    tableRow = null;
    sortValue = null;
  }
  
  public boolean setIcon(Image img) {
  	if (isInvisibleAndCanRefresh())
  		return false;

    bufferedTableItem.setIcon(img);
    graphic = null;
    setFlag(FLAG_VISUALLY_CHANGED_SINCE_REFRESH);
    return true;
  }
  
  public Image getIcon() {
  	if (bufferedTableItem == null) {
  		return null;
  	}
  	return bufferedTableItem.getIcon();
  }

  public boolean needsPainting() {
  	if (bufferedTableItem == null) {
  		return false;
  	}
    return bufferedTableItem.needsPainting();
  }
  
  public void doPaint(GC gc) {
  	if ((!hasFlag(FLAG_UPTODATE) || !hasFlag(FLAG_VALID))
				&& (refreshListeners != null || tableColumn.hasCellRefreshListener())) {
  		if (bDebug) {
  			debug("doPaint: invoke refresh");
  		}
  		refresh();
  	}

		if (bDebug) {
			debug("doPaint up2date:" + hasFlag(FLAG_UPTODATE) + ";v:" + hasFlag(FLAG_VALID) + ";rl=" + refreshListeners);
		}
    bufferedTableItem.doPaint(gc);
  }

  public void locationChanged() {
  	if (bufferedTableItem != null) {
  		bufferedTableItem.locationChanged();
  	}
  }

  public TableRowCore getTableRowCore() {
    return tableRow;
  }

	// @see org.gudy.azureus2.ui.swt.views.table.TableCellSWT#getTableRowSWT()
	public TableRowSWT getTableRowSWT() {
		if (tableRow instanceof TableRowSWT) {
			return (TableRowSWT)tableRow;
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return "TableCell {"
				+ tableColumn.getName()
				+ ","
				+ (bufferedTableItem == null ? "null" : ""
						+ bufferedTableItem.getPosition()) + "," + getText() + ","
				+ getSortValue() + "}";
	}

	/* Comparable Implementation */
  
  /** Compare our sortValue to the specified object.  Assumes the object 
   * is TableCellImp (safe assumption)
   */
  public int compareTo(Object o) {
    try {
      Comparable ourSortValue = getSortValue();
      Comparable otherSortValue = ((TableCellImpl)o).getSortValue();
      if (ourSortValue instanceof String && otherSortValue instanceof String) {
        // Collator.getInstance cache's Collator object, so this is relatively
        // fast.  However, storing it as static somewhere might give a small
        // performance boost.  If such an approach is take, ensure that the static
        // variable is updated the user chooses an different language.
        Collator collator = Collator.getInstance(Locale.getDefault());
        return collator.compare(ourSortValue, otherSortValue);
      }
      try {
        return ourSortValue.compareTo(otherSortValue);
      } catch (ClassCastException e) {
        // It's possible that a row was created, but not refreshed yet.
        // In that case, one sortValue will be String, and the other will be
        // a comparable object that the plugin defined.  Those two sortValues 
        // may not be compatable (for good reason!), so just skip it.
      }
    } catch (Exception e) {
      System.out.println("Could not compare cells");
      Debug.printStackTrace( e );
    }
    return 0;
  }

  public void invokeToolTipListeners(int type) {
  	if (tableColumn == null)
  		return;

    tableColumn.invokeCellToolTipListeners(this, type);

    if (tooltipListeners == null || tooltipErrLoopCount > 2)
      return;

    int iErrCount = tableColumn.getConsecutiveErrCount();
    if (iErrCount > 10)
      return;

    try {
	    if (type == TOOLTIPLISTENER_HOVER) {
	      for (int i = 0; i < tooltipListeners.size(); i++)
	        ((TableCellToolTipListener)(tooltipListeners.get(i))).cellHover(this);
	    } else {
	      for (int i = 0; i < tooltipListeners.size(); i++)
	        ((TableCellToolTipListener)(tooltipListeners.get(i))).cellHoverComplete(this);
	    }
	    tooltipErrLoopCount = 0;
    } catch (Throwable e) {
      tooltipErrLoopCount++;
      tableColumn.setConsecutiveErrCount(++iErrCount);
      pluginError(e);
      if (tooltipErrLoopCount > 2)
      	Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR,
						"TableCell's tooltip will not be refreshed anymore this session."));
    }
  }

  public void invokeMouseListeners(TableCellMouseEvent event) {
		ArrayList listeners = event.eventType == TableCellMouseEvent.EVENT_MOUSEMOVE
				? cellMouseMoveListeners : cellMouseListeners;
		if (listeners == null)
			return;
		
		if (event.cell != null && event.row == null) {
			event.row = event.cell.getTableRow();
		}
		
		for (int i = 0; i < listeners.size(); i++) {
			try {
				TableCellMouseListener l = (TableCellMouseListener) (listeners.get(i));

				l.cellMouseTrigger(event);

			} catch (Throwable e) {
				Debug.printStackTrace(e);
			}
		}
	}

	public void invokeVisibilityListeners(int visibility,
			boolean invokeColumnListeners) {
		if (invokeColumnListeners) {
			tableColumn.invokeCellVisibilityListeners(this, visibility);
		}

		if (cellVisibilityListeners == null)
			return;

		for (int i = 0; i < cellVisibilityListeners.size(); i++) {
			try {
				TableCellVisibilityListener l = (TableCellVisibilityListener) (cellVisibilityListeners.get(i));

				l.cellVisibilityChanged(this, visibility);

			} catch (Throwable e) {
				Debug.printStackTrace(e);
			}
		}
	}


  public static final Comparator TEXT_COMPARATOR = new TextComparator();
  private static class TextComparator implements Comparator {
		public int compare(Object arg0, Object arg1) {
			return arg0.toString().compareToIgnoreCase(arg1.toString());
		}
  }
  
	public void setUpToDate(boolean upToDate) {
	  if (bDebug)
		  debug("set up to date to " + upToDate);
		if (upToDate) {
			setFlag(FLAG_UPTODATE);
		} else {
			clearFlag(FLAG_UPTODATE);
		}
	}
	
	public boolean isUpToDate() {
		return hasFlag(FLAG_UPTODATE);
	}
	
	public void debug(final String s) {
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				System.out.println(SystemTime.getCurrentTime() + ": r"
						+ tableRow.getIndex() + "c" + tableColumn.getPosition() + "r.v?"
						+ ((tableRow.isVisible() ? "Y" : "N")) + ";" + s);
			}
		}, true);
	}

	public Rectangle getBounds() {
		if (isDisposed()) {
			return new Rectangle(0, 0, 0, 0);
		}
		Rectangle bounds = bufferedTableItem.getBounds();
		if (bounds == null) {
			return new Rectangle(0, 0, 0, 0);
		}
    return bounds;
	}

	private void setOrientationViaColumn() {
		if (!(bufferedTableItem instanceof BufferedGraphicTableItem))
			return;
		
		int align = tableColumn.getAlignment();
		BufferedGraphicTableItem ti = (BufferedGraphicTableItem) bufferedTableItem;
		ti.setOrientation(TableColumnSWTUtils.convertColumnAlignmentToSWT(align));
	}

	public String getObfusticatedText() {
		if (tableColumn.isObfusticated()) {
			if (tableColumn instanceof ObfusticateCellText) {
				return ((ObfusticateCellText)tableColumn).getObfusticatedText(this);
			}
			
			return "";
		}
		return null;
	}

	public Graphic getBackgroundGraphic() {
		if (bufferedTableItem == null) {
			return null;
		}
  	return new UISWTGraphicImpl(bufferedTableItem.getBackgroundImage());
	}

	public Image getBackgroundImage() {
		if (bufferedTableItem == null) {
			return null;
		}
  	return bufferedTableItem.getBackgroundImage();
	}
	
	public BufferedTableItem getBufferedTableItem() {
		return bufferedTableItem;
	}

	public int getCursorID() {
		return iCursorID;
	}
	
	public void setCursorID(int cursorID) {
		iCursorID = cursorID;
		if (isMouseOver()) {
			Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
					bufferedTableItem.setCursor(iCursorID);
				}
			});
		}
	}
	
	public boolean isMouseOver() {
		if (tableRow == null) {
			return false;
		}
		TableViewSWT view = (TableViewSWT) tableRow.getView();
		return view == null ? false : view.getTableCellWithCursor() == this;
	}
	
	public int[] getMouseOffset() {
		Point ofs = ((TableViewSWT) tableRow.getView()).getTableCellMouseOffset(this);
		return ofs == null ? null : new int[] { ofs.x, ofs.y };
	}
	
	private boolean hasFlag(int flag) {
		return (flags & flag) != 0;
	}
	
	private void setFlag(int flag) {
		flags |= flag;
	}
	
	private void clearFlag(int flag) {
		flags &= ~flag;
	}
}
