/**
 * Copyright (C) 2004-2007 Aelitis SAS, All rights Reserved
 * 
 * Date: July 14, 2004
 *
 * 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 com.aelitis.azureus.ui.common.table.impl;

import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.*;

import com.aelitis.azureus.ui.common.table.*;

import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadTypeComplete;
import org.gudy.azureus2.plugins.download.DownloadTypeIncomplete;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.sharing.ShareResource;
import org.gudy.azureus2.plugins.tracker.TrackerTorrent;
import org.gudy.azureus2.plugins.ui.UIRuntimeException;
import org.gudy.azureus2.plugins.ui.config.Parameter;
import org.gudy.azureus2.plugins.ui.tables.*;

import org.gudy.azureus2.pluginsimpl.local.ui.tables.TableContextMenuItemImpl;

/** 
 * Table Column definition and modification routines.
 * Implements both the plugin API and the core API.
 * <P>
 * A column is defined in relation to a table.  When one column is in  
 * multiple tables of different table ids, each table has it's own column
 * instance
 * 
 * @author TuxPaper
 * 
 * @see com.aelitis.azureus.ui.common.table.impl.TableColumnManager
 */
public class TableColumnImpl
	implements TableColumnCore
{
	private static final String CFG_SORTDIRECTION = "config.style.table.defaultSortOrder";

	/** Internal Name/ID of the column **/
	private String sName;

	/** key of the displayed title for the column.  If null, uses default calc */
	private String sTitleLanguageKey = null;

	private int iAlignment;

	private int	iDefaultAlignment = -1;
	
	private int iType;

	private int iPosition;

	private int iWidth;

	private int iDefaultWidth;

	private int iInterval;

	private long lLastSortValueChange;

	/** Table the column belongs to */
	private String sTableID;

	private boolean bColumnAdded;

	private boolean bCoreDataSource;

	private ArrayList<TableCellRefreshListener> cellRefreshListeners;

	private ArrayList<TableCellAddedListener> cellAddedListeners;

	private ArrayList<TableCellDisposeListener> cellDisposeListeners;

	private ArrayList<TableCellToolTipListener> cellToolTipListeners;

	private ArrayList<TableCellMouseListener> cellMouseListeners;

	private ArrayList<TableCellMouseMoveListener> cellMouseMoveListeners;

	private ArrayList<TableCellVisibilityListener> cellVisibilityListeners;
	
	private ArrayList<TableCellClipboardListener> cellClipboardListeners;
	
	private ArrayList<TableColumnExtraInfoListener> columnExtraInfoListeners;

	private Map<String, List<Object>> mapOtherCellListeners;
	
	private int iConsecutiveErrCount;

	private ArrayList<TableContextMenuItem> menuItemsHeader;

	private ArrayList<TableContextMenuItem> menuItemsColumn;
	
	private boolean bObfusticateData;

	protected AEMonitor this_mon = new AEMonitor("TableColumn");

	private boolean bSortValueLive;

	private long lStatsRefreshTotalTime;

	private long lStatsRefreshCount = 0;

	private long lStatsRefreshZeroCount = 0;

	private boolean bSortAscending;
	
	private boolean editable;

	private int iMinWidth = -1;

	private int iMaxWidth = -1;

	private boolean bVisible;
	
	private boolean bMaxWidthAuto = false;

	private boolean bWidthAuto;

	private int iPreferredWidth;

	private boolean bPreferredWidthAuto = true;

	private int iPreferredWidthMax = -1;
	
	private boolean auto_tooltip = false;
	
	private Map userData;

	private boolean removed;
	
	private List<Class<?>> forPluginDataSourceTypes = new ArrayList<Class<?>>();

	private String iconID;

	private boolean firstLoad;

	private boolean showOnlyImage;

	public TableColumnImpl(String tableID, String columnID) {
		init(tableID, columnID);
	}

	
	private void init(String tableID,
			String columnID) {
		sTableID = tableID;
		sName = columnID;
		iType = TYPE_TEXT_ONLY;
		iWidth = 50;
		iAlignment = ALIGN_LEAD;
		bColumnAdded = false;
		bCoreDataSource = false;
		iInterval = INTERVAL_INVALID_ONLY;
		iConsecutiveErrCount = 0;
		lLastSortValueChange = 0;
		bVisible = false;
		iMinWidth = 16;
		iPosition = POSITION_INVISIBLE;
		int iSortDirection = COConfigurationManager.getIntParameter(CFG_SORTDIRECTION);
		bSortAscending = iSortDirection == 1 ? false : true;
	}
	
	public void initialize(int iAlignment, int iPosition, int iWidth,
			int iInterval) {
		if (bColumnAdded) {
			throw (new UIRuntimeException("Can't set properties. Column '" + sName
					+ " already added"));
		}

		this.iAlignment = this.iDefaultAlignment =  iAlignment;
		setPosition(iPosition);
		this.iWidth = this.iDefaultWidth = iWidth;
		this.iMinWidth = 16;
		this.iInterval = iInterval;
	}

	public void initialize(int iAlignment, int iPosition, int iWidth) {
		if (bColumnAdded) {
			throw (new UIRuntimeException("Can't set properties. Column '" + sName
					+ " already added"));
		}

		this.iAlignment = this.iDefaultAlignment = iAlignment;
		setPosition(iPosition);
		this.iWidth = this.iDefaultWidth = iWidth;
		this.iMinWidth = 16;
	}

	public String getName() {
		return sName;
	}

	public String getTableID() {
		return sTableID;
	}

	public void setType(int type) {
		if (bColumnAdded) {
			throw (new UIRuntimeException("Can't set properties. Column '" + sName
					+ " already added"));
		}

		iType = type;
	}

	public int getType() {
		return iType;
	}

	public void setWidth(int width) {
		if (width == iWidth || width < 0) {
			return;
		}

		if (iMinWidth > 0 && width < iMinWidth) {
			return;
		}

		if (iMaxWidth > 0 && width > iMaxWidth) {
			if (width == iMaxWidth) {
				return;
			}
			width = iMaxWidth;
		}

		if (iMinWidth < 0) {
			iMinWidth = width;
		}

		//		if (iPreferredWidth <= 0) {
		//			iPreferredWidth = iWidth;
		//		}

		int diff = width - iWidth;
		iWidth = width;
		if (iDefaultWidth == 0) {
			iDefaultWidth = width;
		}

		if (bColumnAdded && bVisible) {
			triggerColumnSizeChange(diff);
		}
	}

	public void triggerColumnSizeChange(int diff) {
		TableStructureEventDispatcher tsed = TableStructureEventDispatcher.getInstance(sTableID);
		tsed.columnSizeChanged(this, diff);
		if (iType == TYPE_GRAPHIC) {
			invalidateCells();
		}
	}

	public int getWidth() {
		return iWidth;
	}

	public void setPosition(int position) {
		if (bColumnAdded) {
			throw (new UIRuntimeException("Can't set properties. Column '" + sName
					+ " already added"));
		}

		if (iPosition == POSITION_INVISIBLE && position != POSITION_INVISIBLE) {
			setVisible(true);
		}
		iPosition = position;
		if (position == POSITION_INVISIBLE) {
			setVisible(false);
		}
	}

	public int getPosition() {
		return iPosition;
	}

	public void setAlignment(int alignment) {
		/*
		if (bColumnAdded) {
			throw (new UIRuntimeException("Can't set properties. Column '" + sName
					+ " already added"));
		}
		*/
		
		iAlignment = alignment;
		
		if ( iDefaultAlignment == -1 ){
			iDefaultAlignment = alignment;
		}
		
		// Commented out because size hasn't changed!
		//if (bColumnAdded && bVisible) {
		//	triggerColumnSizeChange();
		//}
	}

	public int getAlignment() {
		return iAlignment;
	}

	public void addCellRefreshListener(TableCellRefreshListener listener) {
		try {
			this_mon.enter();

			if (cellRefreshListeners == null) {
				cellRefreshListeners = new ArrayList<TableCellRefreshListener>(1);
			}

			cellRefreshListeners.add(listener);
			//System.out.println(this + " :: addCellRefreshListener " + listener + ". " + cellRefreshListeners.size());

		} finally {

			this_mon.exit();
		}
	}

	public List<TableCellRefreshListener> getCellRefreshListeners() {
		try {
			this_mon.enter();

			if (cellRefreshListeners == null) {
				return (new ArrayList<TableCellRefreshListener>(0));
			}

			return (new ArrayList<TableCellRefreshListener>(cellRefreshListeners));

		} finally {

			this_mon.exit();
		}
	}

	public void removeCellRefreshListener(TableCellRefreshListener listener) {
		try {
			this_mon.enter();

			if (cellRefreshListeners == null) {
				return;
			}

			cellRefreshListeners.remove(listener);
		} finally {
			this_mon.exit();
		}
	}

	public boolean hasCellRefreshListener() {
		return cellRefreshListeners != null && cellRefreshListeners.size() > 0;
	}

	public void setRefreshInterval(int interval) {
		iInterval = interval;
	}

	public int getRefreshInterval() {
		return iInterval;
	}

	public void addCellAddedListener(TableCellAddedListener listener) {
		try {
			this_mon.enter();

			if (cellAddedListeners == null) {
				cellAddedListeners = new ArrayList<TableCellAddedListener>(1);
			}

			cellAddedListeners.add(listener);

		} finally {

			this_mon.exit();
		}
	}

	public void addCellOtherListener(String listenerID, Object listener) {
		try {
			this_mon.enter();
			
			if (mapOtherCellListeners == null) {
				mapOtherCellListeners = new HashMap<String, List<Object>>(1);
			}

			List<Object> list = mapOtherCellListeners.get(listenerID);
			if (list == null) {
				list = new ArrayList<Object>(1);
				mapOtherCellListeners.put(listenerID, list);
			}

			list.add(listener);

		} finally {

			this_mon.exit();
		}
	}
	
	public void removeCellOtherListener(String listenerID, Object l) {
		try {
			this_mon.enter();
			
  		if (mapOtherCellListeners == null) {
  			return;
  		}
  		
  		mapOtherCellListeners.remove(listenerID);

		} finally {

			this_mon.exit();
		}

	}
	
	public Object[] getCellOtherListeners(String listenerID) {
		if (mapOtherCellListeners == null) {
			return null;
		}

		List<Object> list = mapOtherCellListeners.get(listenerID);
		if (list == null) {
			return null;
		}
		return list.toArray();
	}
	
	// @see com.aelitis.azureus.ui.common.table.TableColumnCore#hasCellOtherListeners(java.lang.String)
	public boolean hasCellOtherListeners(String listenerID) {
		return mapOtherCellListeners != null
				&& mapOtherCellListeners.get(listenerID) != null;
	}

	public List getCellAddedListeners() {
		try {
			this_mon.enter();

			if (cellAddedListeners == null) {
				return Collections.emptyList();
			}

			return (new ArrayList(cellAddedListeners));

		} finally {

			this_mon.exit();
		}
	}

	public void removeCellAddedListener(TableCellAddedListener listener) {
		try {
			this_mon.enter();

			if (cellAddedListeners == null) {
				return;
			}

			cellAddedListeners.remove(listener);

		} finally {

			this_mon.exit();
		}
	}

	public void addCellDisposeListener(TableCellDisposeListener listener) {
		try {
			this_mon.enter();

			if (cellDisposeListeners == null) {
				cellDisposeListeners = new ArrayList<TableCellDisposeListener>(1);
			}

			cellDisposeListeners.add(listener);
		} finally {

			this_mon.exit();
		}
	}

	public void removeCellDisposeListener(TableCellDisposeListener listener) {
		try {
			this_mon.enter();

			if (cellDisposeListeners == null) {
				return;
			}

			cellDisposeListeners.remove(listener);
		} finally {

			this_mon.exit();
		}
	}

	public void addCellToolTipListener(TableCellToolTipListener listener) {
		try {
			this_mon.enter();

			if (cellToolTipListeners == null) {
				cellToolTipListeners = new ArrayList<TableCellToolTipListener>(1);
			}

			cellToolTipListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void removeCellToolTipListener(TableCellToolTipListener listener) {
		try {
			this_mon.enter();

			if (cellToolTipListeners == null) {
				return;
			}

			cellToolTipListeners.remove(listener);
		} finally {
			this_mon.exit();
		}
	}

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

			if (cellMouseListeners == null) {
				cellMouseListeners = new ArrayList<TableCellMouseListener>(1);
			}

			cellMouseListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

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

			if (cellMouseListeners == null) {
				return;
			}

			cellMouseListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}
	
	public boolean hasCellMouseMoveListener() {
		return cellMouseMoveListeners != null && cellMouseMoveListeners.size() > 0;
	}

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

			if (cellMouseMoveListeners == null) {
				cellMouseMoveListeners = new ArrayList<TableCellMouseMoveListener>(1);
			}

			cellMouseMoveListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

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

			if (cellMouseMoveListeners == null) {
				return;
			}

			cellMouseMoveListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void addCellClipboardListener(TableCellClipboardListener listener) {
		try {
			this_mon.enter();

			if (cellClipboardListeners == null) {
				cellClipboardListeners = new ArrayList<TableCellClipboardListener>(1);
			}

			cellClipboardListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void removeCellClipboardListener(TableCellClipboardListener listener) {
		try {
			this_mon.enter();

			if (cellClipboardListeners == null) {
				return;
			}

			cellClipboardListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

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

			if (cellVisibilityListeners == null) {
				cellVisibilityListeners = new ArrayList<TableCellVisibilityListener>(1);
			}

			cellVisibilityListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

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

			if (cellVisibilityListeners == null) {
				return;
			}

			cellVisibilityListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public List<TableColumnExtraInfoListener> getColumnExtraInfoListeners() {
		try {
			this_mon.enter();

			if (columnExtraInfoListeners == null) {
				return (new ArrayList<TableColumnExtraInfoListener>(0));
			}

			return (new ArrayList<TableColumnExtraInfoListener>(columnExtraInfoListeners));

		} finally {

			this_mon.exit();
		}
	}

	public void addColumnExtraInfoListener(TableColumnExtraInfoListener listener) {
		try {
			this_mon.enter();

			if (columnExtraInfoListeners == null) {
				columnExtraInfoListeners = new ArrayList<TableColumnExtraInfoListener>(1);
			}

			columnExtraInfoListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void removeColumnExtraInfoListener(TableColumnExtraInfoListener listener) {
		try {
			this_mon.enter();

			if (columnExtraInfoListeners == null) {
				return;
			}

			columnExtraInfoListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

	public void invalidateCells() {
		TableStructureEventDispatcher tsed = TableStructureEventDispatcher.getInstance(sTableID);
		tsed.columnInvalidate(this);
	}
	
	public void invalidateCell(Object data_source) {
		TableStructureEventDispatcher tsed = TableStructureEventDispatcher.getInstance(sTableID);
		tsed.cellInvalidate(this, data_source);
	}

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

		if (listenerObject instanceof TableCellRefreshListener) {
			addCellRefreshListener((TableCellRefreshListener) listenerObject);
		}

		if (listenerObject instanceof TableCellToolTipListener) {
			addCellToolTipListener((TableCellToolTipListener) listenerObject);
		}

		if (listenerObject instanceof TableCellAddedListener) {
			addCellAddedListener((TableCellAddedListener) listenerObject);
		}

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

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

		if (listenerObject instanceof TableCellVisibilityListener) {
			addCellVisibilityListener((TableCellVisibilityListener) listenerObject);
		}

		if (listenerObject instanceof TableColumnExtraInfoListener) {
			addColumnExtraInfoListener((TableColumnExtraInfoListener) listenerObject);
		}

		if (listenerObject instanceof TableCellClipboardListener) {
			addCellClipboardListener((TableCellClipboardListener) listenerObject);
		}
	}

	/* Start of not plugin public API functions */
	//////////////////////////////////////////////
	public void setColumnAdded() {
		if (bColumnAdded) {
			return;
		}
		preAdd();
		bColumnAdded = true;
	}

	public boolean getColumnAdded() {
		return bColumnAdded;
	}

	public void setUseCoreDataSource(boolean bCoreDataSource) {
		this.bCoreDataSource = bCoreDataSource;
	}

	public boolean getUseCoreDataSource() {
		return bCoreDataSource;
	}

	public void invokeCellRefreshListeners(TableCell cell, boolean fastRefresh) throws Throwable {
		//System.out.println(this + " :: invokeCellRefreshListeners" + cellRefreshListeners);
		if (cellRefreshListeners == null || cell == null || cell.isDisposed()) {
			return;
		}
		
		Throwable firstError = null;

		//System.out.println(this + " :: invokeCellRefreshListeners" + cellRefreshListeners.size());
		for (int i = 0; i < cellRefreshListeners.size(); i++) {
			
			TableCellRefreshListener l = cellRefreshListeners.get(i); 

			try {
				if(l instanceof TableCellLightRefreshListener)
					((TableCellLightRefreshListener)l).refresh(cell, fastRefresh);
				else
					l.refresh(cell);
			} catch (Throwable e) {
				
				if (firstError == null) {
					firstError = e;
				}
				Debug.printStackTrace(e);
			}
		}
		
		if (firstError != null) {
			throw firstError;
		}
	}

	public void invokeCellAddedListeners(TableCell cell) {
		if (cellAddedListeners == null) {
			return;
		}
		for (int i = 0; i < cellAddedListeners.size(); i++) {

			try {
				((TableCellAddedListener) (cellAddedListeners.get(i))).cellAdded(cell);

			} catch (Throwable e) {

				Debug.printStackTrace(e);
			}
		}
	}

	public void invokeCellDisposeListeners(TableCell cell) {
		if (cellDisposeListeners == null) {
			return;
		}
		for (int i = 0; i < cellDisposeListeners.size(); i++) {
			try {
				((TableCellDisposeListener) (cellDisposeListeners.get(i))).dispose(cell);

			} catch (Throwable e) {

				Debug.printStackTrace(e);
			}
		}
	}

	public String getClipboardText(TableCell cell) {
		if (cellClipboardListeners == null) {
			return null;
		}
		for (int i = 0; i < cellClipboardListeners.size(); i++) {
			try {
				String text = ((TableCellClipboardListener) (cellClipboardListeners.get(i))).getClipboardText(cell);
				
				if (text != null) {
					return text;
				}

			} catch (Throwable e) {

				Debug.printStackTrace(e);
			}
		}

		return null;
	}

	public void invokeCellToolTipListeners(TableCellCore cell, int type) {
		if (cellToolTipListeners == null) {
			return;
		}
		if (type == TableCellCore.TOOLTIPLISTENER_HOVER) {
			for (int i = 0; i < cellToolTipListeners.size(); i++) {
				try {
					((TableCellToolTipListener) (cellToolTipListeners.get(i))).cellHover(cell);
				} catch (Throwable e) {

					Debug.printStackTrace(e);
				}
			}
		} else {
			for (int i = 0; i < cellToolTipListeners.size(); i++) {

				try {
					((TableCellToolTipListener) (cellToolTipListeners.get(i))).cellHoverComplete(cell);
				} catch (Throwable e) {

					Debug.printStackTrace(e);
				}
			}
		}
	}

	public void invokeCellMouseListeners(TableCellMouseEvent event) {
		ArrayList<?> listeners = event.eventType == TableCellMouseEvent.EVENT_MOUSEMOVE
				? cellMouseMoveListeners : this.cellMouseListeners;
		if (listeners == null) {
			return;
		}

		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 invokeCellVisibilityListeners(TableCellCore cell, int visibility) {
		if (cellVisibilityListeners == null) {
			return;
		}

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

				l.cellVisibilityChanged(cell, visibility);

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

	public void setPositionNoShift(int position) {
		//if (iPosition < 0 && position >= 0) {
		//	setVisible(true);
		//}
		iPosition = position;
		//if (position < 0) {
		//	setVisible(false);
		//}
	}
	
	public Object getUserData(String key) {
		if(userData != null)
			return userData.get(key);
		return null;
	}
	
	public void setUserData(String key, Object value) {
		if(userData == null)
			userData = new LightHashMap(2);
		userData.put(key, value);		
	}
	
	public void removeUserData(String key) {
		if(userData == null)
			return;
		userData.remove(key);
		if(userData.size() < 1)
			userData = null;
	}
	
	public void
	remove()
	{
		removed = true;
		
		TableColumnManager.getInstance().removeColumns( new TableColumnCore[]{ this });
			
		TableStructureEventDispatcher tsed = TableStructureEventDispatcher.getInstance(sTableID);
					
		for (Class<?> cla : forPluginDataSourceTypes) {
			tsed.tableStructureChanged(true, cla);
		}
	}

	public boolean 
	isRemoved() 
	{
		return( removed );
	}
	
	public final void loadSettings(Map mapSettings) {
		// Format: Key = [TableID].column.[columnname]
		// Value[] = { visible, width, position, autotooltip, sortorder }
		String itemPrefix = "Column." + sName;
		String oldItemPrefix = "Table." + sTableID + "." + sName;
		Object object = mapSettings.get(itemPrefix);
		Object[] list;
		if (object instanceof List) {
			list = ((List) object).toArray();
		} else {
			list = new String[0];
		}

		int pos = 0;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			boolean vis = ((Number) list[pos]).intValue() == 1;
			setVisible(vis);
		}

		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			int position = ((Number) list[pos]).intValue();
			setPositionNoShift(position);
		} else {
			int position = COConfigurationManager.getIntParameter(oldItemPrefix + ".position",
					iPosition);
			if (iPosition == POSITION_INVISIBLE && position != POSITION_INVISIBLE) {
				setVisible(true);
			}
			setPositionNoShift(position);
			if (position == POSITION_INVISIBLE) {
				setVisible(false);
			}
		}

		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			int width = ((Number) list[pos]).intValue();
			setWidth(width);
		} else {
			setWidth(COConfigurationManager.getIntParameter(oldItemPrefix + ".width",
					iWidth));
		}
		
		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			boolean autoTooltip = ((Number) list[pos]).intValue() == 1;
			setAutoTooltip(autoTooltip);
		} else {
			setAutoTooltip(COConfigurationManager.getBooleanParameter(oldItemPrefix
					+ ".auto_tooltip", auto_tooltip));
		}

		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			int sortOrder = ((Number) list[pos]).intValue();
			if (sortOrder >= 0) {
				// dont call setSordOrder, since it will change lLastSortValueChange
				// which we shouldn't do if we aren't the sorting column
				bSortAscending = sortOrder == 1;
			}
		}
		
		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Map)) {
			userData = (Map)list[pos];
			if(userData.size() < 1)
				userData = null;
		}
		pos++;
		if (list.length >= (pos + 1) && (list[pos] instanceof Number)) {
			int align = ((Number) list[pos]).intValue();
			setAlignment( align );
		}
		firstLoad = list.length == 0;
		postConfigLoad();
	}
	
	public boolean isFirstLoad() {
		return firstLoad;
	}
	
	/* (non-Javadoc)
	 * @see org.gudy.azureus2.plugins.ui.tables.TableColumn#postLoad()
	 */
	public void postConfigLoad() {}

	public void preAdd() {}

	public void preConfigSave() {}

	public final void saveSettings(Map mapSettings) {
		preConfigSave();
		
		if (mapSettings == null) {
			mapSettings = TableColumnManager.getInstance().getTableConfigMap(sTableID);
			if (mapSettings == null) {
				return;
			}
		}
		String sItemPrefix = "Column." + sName;
		mapSettings.put(sItemPrefix, Arrays.asList(new Object[] {
			new Integer(bVisible ? 1 : 0),
			new Integer(iPosition),
			new Integer(iWidth),
			new Integer(auto_tooltip ? 1 : 0),
			new Integer(lLastSortValueChange == 0 ? -1 : (bSortAscending ? 1 : 0)),
			userData != null ? userData : Collections.EMPTY_MAP,
			new Integer(iAlignment)
		}));
		// cleanup old config
		sItemPrefix = "Table." + sTableID + "." + sName; 
		if (COConfigurationManager.hasParameter(sItemPrefix + ".width", true)) {
			COConfigurationManager.removeParameter(sItemPrefix + ".position");
			COConfigurationManager.removeParameter(sItemPrefix + ".width");
			COConfigurationManager.removeParameter(sItemPrefix + ".auto_tooltip");
		}
	}

	public String getTitleLanguageKey() {
		try {
			this_mon.enter();

			if (sTitleLanguageKey == null) {
				if (MessageText.keyExists(sTableID + ".column." + sName)) {
					sTitleLanguageKey = sTableID + ".column." + sName;
					return sTitleLanguageKey;
				}

				String sKeyPrefix;
				// Support "Old Style" language keys, which have a prefix of TableID + "View."
				// Also, "MySeeders" is actually stored in "MyTorrents"..
				sKeyPrefix = (sTableID.equals(TableManager.TABLE_MYTORRENTS_COMPLETE)
						? TableManager.TABLE_MYTORRENTS_INCOMPLETE : sTableID)
						+ "View.";
				if (MessageText.keyExists(sKeyPrefix + sName)) {
					sTitleLanguageKey = sKeyPrefix + sName;
					return sTitleLanguageKey;
				}
				
				// The "all peers" view should just share the same peer columns, so reuse them.
				if (sTableID.equals(TableManager.TABLE_ALL_PEERS)) {
					sKeyPrefix = TableManager.TABLE_TORRENT_PEERS + ".column.";
					if (MessageText.keyExists(sKeyPrefix + sName)) {
						sTitleLanguageKey = sKeyPrefix + sName;
						return sTitleLanguageKey;
					}
					
					// Or try "PeersView".
					sKeyPrefix = "PeersView.";
					if (MessageText.keyExists(sKeyPrefix + sName)) {
						sTitleLanguageKey = sKeyPrefix + sName;
						return sTitleLanguageKey;
					}
				}
				
				// Try a generic one of "TableColumn." + columnid
				sKeyPrefix = "TableColumn.header.";
				if (MessageText.keyExists(sKeyPrefix + sName)) {
					sTitleLanguageKey = sKeyPrefix + sName;
					return sTitleLanguageKey;
				}

				// another "Old Style"
				// 99% sure this can be removed now, but why risk it..
				sKeyPrefix = "MyTorrentsView." + sName;
				//System.out.println(sKeyPrefix + ";" + MessageText.getString(sKeyPrefix));
				if (MessageText.keyExists(sKeyPrefix)) {
					sTitleLanguageKey = sKeyPrefix;
					return sTitleLanguageKey;
				}
				
				sTitleLanguageKey = "!" + sName + "!";
			}
			return sTitleLanguageKey;
		} finally {

			this_mon.exit();
		}
	}

	public int getConsecutiveErrCount() {
		return iConsecutiveErrCount;
	}

	public void setConsecutiveErrCount(int iCount) {
		iConsecutiveErrCount = iCount;
	}

	public void removeContextMenuItem(TableContextMenuItem menuItem) {
		if (menuItemsColumn != null) {
			menuItemsColumn.remove(menuItem);
		}
		if (menuItemsHeader != null) {
			menuItemsColumn.remove(menuItem);
		}
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#addContextMenuItem(java.lang.String, int)
	public TableContextMenuItem addContextMenuItem(String key) {
		return addContextMenuItem(key, MENU_STYLE_COLUMN_DATA);
	}

	public TableContextMenuItem addContextMenuItem(String key, int menuStyle) {
		ArrayList<TableContextMenuItem> menuItems;		
		if (menuStyle == MENU_STYLE_COLUMN_DATA) {
			if (menuItemsColumn == null) {
				menuItemsColumn = new ArrayList<TableContextMenuItem>();
			}
			menuItems = menuItemsColumn;
		} else {
			if (menuItemsHeader == null) {
				menuItemsHeader = new ArrayList<TableContextMenuItem>();
			}
			menuItems = menuItemsHeader;
		}
		
		// Hack.. should be using our own implementation..
		TableContextMenuItemImpl item = new TableContextMenuItemImpl(null,"", key);
		menuItems.add(item);
		return item;
	}

	public TableContextMenuItem[] getContextMenuItems(int menuStyle) {
		ArrayList<TableContextMenuItem> menuItems;		
		if (menuStyle == MENU_STYLE_COLUMN_DATA) {
			menuItems = menuItemsColumn;
		} else {
			menuItems = menuItemsHeader;
		}

		if (menuItems == null) {
			return new TableContextMenuItem[0];
		}

		return menuItems.toArray(new TableContextMenuItem[0]);
	}

	public boolean isObfusticated() {
		return bObfusticateData;
	}

	public void setObfustication(boolean hideData) {
		bObfusticateData = hideData;
	}

	public long getLastSortValueChange() {
		if (bSortValueLive) {
			return SystemTime.getCurrentTime();
		}
		return lLastSortValueChange;
	}

	public void setLastSortValueChange(long lastSortValueChange) {
		//System.out.println(getName() + "] setlastSortValueChange via " + Debug.getCompressedStackTrace());
		lLastSortValueChange = lastSortValueChange;
	}

	public boolean isSortValueLive() {
		return bSortValueLive;
	}

	public void setSortValueLive(boolean live) {
		//		if (live && !bSortValueLive) {
		//			System.out.println("Setting " + sTableID + ": " + sName + " to live sort value");
		//		}
		bSortValueLive = live;
	}

	public void addRefreshTime(long ms) {
		if (ms == 0) {
			lStatsRefreshZeroCount++;
		} else {
			lStatsRefreshTotalTime += ms;
			lStatsRefreshCount++;
		}
	}

	public void generateDiagnostics(IndentWriter writer) {
		writer.println("Column " + sTableID + ":" + sName
				+ (bSortValueLive ? " (Live Sort)" : ""));
		try {
			writer.indent();

			if (lStatsRefreshCount > 0) {
				writer.println("Avg refresh time (" + lStatsRefreshCount
						+ " samples): " + (lStatsRefreshTotalTime / lStatsRefreshCount)
						+ " (" + lStatsRefreshZeroCount
						+ " zero ms refreshes not included)");
			}
			writer.println("Listeners: refresh="
					+ getListCountString(cellRefreshListeners) + "; dispose="
					+ getListCountString(cellDisposeListeners) + "; mouse="
					+ getListCountString(cellMouseListeners) + "; mm="
					+ getListCountString(cellMouseMoveListeners) + "; vis="
					+ getListCountString(cellVisibilityListeners) + "; added="
					+ getListCountString(cellAddedListeners) + "; tooltip="
					+ getListCountString(cellToolTipListeners));

			writer.println("lLastSortValueChange=" + lLastSortValueChange);
		} catch (Exception e) {
		} finally {
			writer.exdent();
		}
	}

	private String getListCountString(List<?> l) {
		if (l == null) {
			return "-0";
		}
		return "" + l.size();
	}

	public void setTableID(String tableID) {
		sTableID = tableID;
	}

	// @see java.util.Comparator#compare(T, T)
	public int compare(Object arg0, Object arg1) {
		TableCellCore cell0 = ((TableRowCore) arg0).getTableCellCore(sName);
		TableCellCore cell1 = ((TableRowCore) arg1).getTableCellCore(sName);

		Comparable c0 = (cell0 == null) ? "" : cell0.getSortValue();
		Comparable c1 = (cell1 == null) ? "" : cell1.getSortValue();
		
		// Put nulls and empty strings at bottom.
		boolean c0_is_null = (c0 == null || c0.equals(""));
		boolean c1_is_null = (c1 == null || c1.equals(""));
		if (c1_is_null)  {
			return (c0_is_null) ? 0 : -1;
		}
		else if (c0_is_null) {
			return 1;
		}

		try {
			boolean c0isString = c0 instanceof String;
			boolean c1isString = c1 instanceof String;
			if (c0isString && c1isString) {
				if (bSortAscending) {
					return ((String) c0).compareToIgnoreCase((String) c1);
				}

				return ((String) c1).compareToIgnoreCase((String) c0);
			}

			int val;
			if (c0isString && !c1isString) {
				val = -1;
			} else if (c1isString && !c0isString) {
				val = 1;
			} else {
				val = c1.compareTo(c0);
			}
			return bSortAscending ? -val : val;
		} catch (ClassCastException e) {
			int c0_index = (cell0 == null) ? -999 : cell0.getTableRowCore().getIndex();
			int c1_index = (cell1 == null) ? -999 : cell1.getTableRowCore().getIndex();
			System.err.println("Can't compare " + c0.getClass().getName() + "("
					+ c0.toString() + ") from row #" + c0_index
					+ " to " + c1.getClass().getName() + "(" + c1.toString()
					+ ") from row #" + c1_index
					+ " while sorting column " + sName);
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * @param bAscending The bAscending to set.
	 */
	public void setSortAscending(boolean bAscending) {
		if (this.bSortAscending == bAscending) {
			return;
		}
		setLastSortValueChange(SystemTime.getCurrentTime());

		this.bSortAscending = bAscending;
	}

	/**
	 * @return Returns the bAscending.
	 */
	public boolean isSortAscending() {
		return bSortAscending;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#getMinWidth()
	public int getMinWidth() {
		if (iMinWidth < 0) {
			return iWidth;
		}

		return iMinWidth;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setMinWidth(int)
	public void setMinWidth(int minwidth) {
		if (minwidth > iMaxWidth && iMaxWidth >= 0) {
			iMaxWidth = minwidth;
		}
		if (iPreferredWidth > 0 && iPreferredWidth < minwidth) {
			iPreferredWidth = minwidth;
		}
		iMinWidth = minwidth;
		if (iWidth < minwidth) {
			setWidth(minwidth);
		}
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#getMaxWidth()
	public int getMaxWidth() {
		return iMaxWidth;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setMaxWidth(int)
	public void setMaxWidth(int maxwidth) {
		if (maxwidth >= 0 && maxwidth < iMinWidth) {
			iMinWidth = maxwidth;
		}
		if (iPreferredWidth > maxwidth) {
			iPreferredWidth = maxwidth;
		}
		iMaxWidth = maxwidth;
		if (maxwidth >= 0 && iWidth > iMaxWidth) {
			setWidth(maxwidth);
		}
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setWidthLimits(int, int)
	public void setWidthLimits(int min, int max) {
		setMinWidth(min);
		setMaxWidth(max);
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#isVisible()
	public boolean isVisible() {
		return bVisible;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setVisible(boolean)
	public void setVisible(boolean visible) {
		if (bVisible == visible) {
			return;
		}
		
		//System.out.println("set " + sTableID + "/" + sName + " to " + visible
		//		+ " via " + Debug.getCompressedStackTrace());

		bVisible = visible;
		if (bVisible && iPosition == POSITION_INVISIBLE) {
			TableColumnCore[] allColumns = TableColumnManager.getInstance().getAllTableColumnCoreAsArray(
					null, sTableID);
			iPosition = 0;
			for (TableColumnCore tableColumnCore : allColumns) {
				if (tableColumnCore.getPosition() > iPosition) {
					iPosition = tableColumnCore.getPosition() + 1;
				}
			}
		}
		invalidateCells();
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#isMaxWidthAuto()
	public boolean isMaxWidthAuto() {
		return bMaxWidthAuto;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setMaxWidthAuto(boolean)
	public void setMaxWidthAuto(boolean automaxwidth) {
		bMaxWidthAuto = automaxwidth;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#isMinWidthAuto()
	public boolean isMinWidthAuto() {
		return bWidthAuto;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setMinWidthAuto(boolean)
	public void setMinWidthAuto(boolean autominwidth) {
		bWidthAuto = autominwidth;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#getPreferredWidth()
	public int getPreferredWidth() {
		return iPreferredWidth;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setPreferredWidthAuto(boolean)
	public void setPreferredWidthAuto(boolean auto) {
		bPreferredWidthAuto = auto;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#isPreferredWidthAuto()
	public boolean isPreferredWidthAuto() {
		return bPreferredWidthAuto;
	}

    //  dead
	public void setPreferredWidthMax(int maxprefwidth) {
		iPreferredWidthMax = maxprefwidth;
		if (iPreferredWidth > iPreferredWidthMax) {
			setPreferredWidth(maxprefwidth);
		}
	}

    //  dead
	public int getPreferredWidthMax() {
		return iPreferredWidthMax;
	}

	// @see org.gudy.azureus2.plugins.ui.tables.TableColumn#setPreferredWidth(int)
	public void setPreferredWidth(int width) {
		if (iPreferredWidthMax > 0 && width > iPreferredWidthMax) {
			width = iPreferredWidthMax;
		}

		if (width < iMinWidth) {
			iPreferredWidth = iMinWidth;
		} else if (iMaxWidth > 0 && width > iMaxWidth) {
			iPreferredWidth = iMaxWidth;
		} else {
			iPreferredWidth = width;
		}

		// Commented out because size hasn't changed!
		//if (bColumnAdded && bVisible) {
		//	triggerColumnSizeChange();
		//}
	}
	
	public void setAutoTooltip(boolean auto_tooltip) {
		this.auto_tooltip = auto_tooltip;
	}
	
	public boolean doesAutoTooltip() {
		return this.auto_tooltip;
	}
	
	public boolean isInplaceEdit() {
		return editable;
	}
	
	public void setInplaceEdit(boolean editable) {
		this.editable = editable;		
	}
	
	public boolean inplaceValueSet(TableCell cell, String value, boolean finalEdit) {
		return false;
	}

	public Class[] getForDataSourceTypes() {
		if (forPluginDataSourceTypes.isEmpty()) {
			// Guess forPluginDataSourceType based on tableID
			Class<?> forPluginDataSourceType = null;
			if (TableManager.TABLE_MYTORRENTS_ALL_BIG.equals(sTableID)
					|| TableManager.TABLE_MYTORRENTS_UNOPENED.equals(sTableID)
					|| TableManager.TABLE_MYTORRENTS_UNOPENED_BIG.equals(sTableID)) {
				forPluginDataSourceType = Download.class;
			} else if (TableManager.TABLE_MYTORRENTS_INCOMPLETE_BIG.equals(sTableID)
					|| TableManager.TABLE_MYTORRENTS_INCOMPLETE.equals(sTableID)) {
				forPluginDataSourceType = DownloadTypeIncomplete.class;
			} else if (TableManager.TABLE_MYTORRENTS_COMPLETE.equals(sTableID)
					|| TableManager.TABLE_MYTORRENTS_COMPLETE_BIG.equals(sTableID)) {
				forPluginDataSourceType = DownloadTypeComplete.class;
			} else if (TableManager.TABLE_TORRENT_PEERS.equals(sTableID)) {
				forPluginDataSourceType = Peer.class;
			} else if (TableManager.TABLE_TORRENT_FILES.equals(sTableID)) {
				forPluginDataSourceType = DiskManagerFileInfo.class;
			} else if (TableManager.TABLE_MYTRACKER.equals(sTableID)) {
				forPluginDataSourceType = TrackerTorrent.class;
			} else if (TableManager.TABLE_MYSHARES.equals(sTableID)) {
				forPluginDataSourceType = ShareResource.class;
			}
			if (forPluginDataSourceType != null) {
				forPluginDataSourceTypes.add(forPluginDataSourceType);
			}
		}
		return forPluginDataSourceTypes.toArray(new Class[0]);
	}

	public void reset() {
		if (iDefaultWidth != 0) {
			setWidth(iDefaultWidth);
		}
		if ( iDefaultAlignment != -1 ){
			setAlignment( iDefaultAlignment );
		}
	}

	public void addDataSourceType(Class<?> cla) {
		if (cla == null) {
			return;
		}
		if (cla == org.gudy.azureus2.core3.disk.DiskManagerFileInfo.class) {
			cla = DiskManagerFileInfo.class;
		}
		forPluginDataSourceTypes.add(cla);
	}
	
	public void addDataSourceTypes(Class[] datasourceTypes) {
		if (datasourceTypes == null) {
			return;
		}
		for (Class cla : datasourceTypes) {
			addDataSourceType(cla);
		}
	}

	public boolean handlesDataSourceType(Class<?> cla) {
		Class[] forPluginDataSourceTypes = getForDataSourceTypes();
		for (Class<?> forClass : forPluginDataSourceTypes) {
			if (forClass.isAssignableFrom(cla)) {
				return true;
			}
		}
		return false;
	}

	public Class getForDataSourceType() {
		Class[] forPluginDataSourceTypes = getForDataSourceTypes();
		return forPluginDataSourceTypes.length > 0 ? forPluginDataSourceTypes[0] : null;
	}
	
	public void setIconReference(String iconID, boolean showOnlyIcon) {
		this.iconID = iconID;
		this.showOnlyImage = showOnlyIcon;
	}
	
	public String getIconReference() {
		return iconID;
	}
	
	public void setMinimumRequiredUserMode(int mode) {

		TableColumnInfo info = TableColumnManager.getInstance().getColumnInfo( this );
		
		if ( info != null ){
			byte	prof;
			if ( mode == Parameter.MODE_BEGINNER ){
				prof = TableColumnInfo.PROFICIENCY_BEGINNER;
			}else if ( mode == Parameter.MODE_INTERMEDIATE ){
				prof = TableColumnInfo.PROFICIENCY_INTERMEDIATE;
			}else{
				prof = TableColumnInfo.PROFICIENCY_ADVANCED;
			}
			info.setProficiency( prof );
		}
	}


	public boolean showOnlyImage() {
		return showOnlyImage;
	}
}
