/**
 * Copyright (C) 2006 Aelitis, 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, or (at your option) any later version.
 * 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.
 * 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 63.529,40 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */
package com.aelitis.azureus.ui.swt.views.list;

import java.util.*;

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

import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrent;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
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.impl.TableCellImpl;
import org.gudy.azureus2.ui.swt.views.table.utils.CoreTableColumn;

import com.aelitis.azureus.ui.common.table.TableCellCore;
import com.aelitis.azureus.ui.common.table.TableColumnCore;
import com.aelitis.azureus.ui.common.table.TableView;
import com.aelitis.azureus.ui.swt.skin.SWTSkinProperties;

import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.ui.UIRuntimeException;
import org.gudy.azureus2.plugins.ui.tables.*;

import org.gudy.azureus2.pluginsimpl.local.disk.DiskManagerFileInfoImpl;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.peers.PeerManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.tracker.TrackerTorrentImpl;

/**
 * @author TuxPaper
 * @created Jun 12, 2006
 *
 */
public class ListRow
	implements TableRowSWT
{
	public static int ROW_HEIGHT = 38;

	private SWTSkinProperties skinProperties;

	private Object coreDataSource;

	private Object pluginDataSource;

	private Map mapTableCells;

	private boolean bDisposed;

	private boolean bSelected;

	private ListView view;

	private final Composite parent;

	private Color fg;

	private Color bg;

	private boolean bRowVisuallyChangedSinceRefresh;

	private ArrayList mouseListeners;

	private AEMonitor this_mon = new AEMonitor("ListRow");

	/**
	 * @param position 
	 * 
	 */
	public ListRow(final ListView view, Composite parent, Object datasource) {
		this.parent = parent;
		coreDataSource = datasource;
		this.view = view;

		pluginDataSource = null;
		bDisposed = false;
		bSelected = false;
		mapTableCells = new LinkedHashMap();

		skinProperties = view.getSkinProperties();

		TableColumnCore[] columns = view.getAllColumns();
		// XXX Need invisible "sort by" column
		int iStartPos = ListView.COLUMN_MARGIN_WIDTH;
		// this is -1 :(
		//int height = rowComposite.getSize().y;
		for (int i = 0; i < columns.length; i++) {
			TableColumnCore column = columns[i];

			boolean bVisible = column.getPosition() >= 0;
			Rectangle bounds = bVisible ? new Rectangle(iStartPos,
					ListView.ROW_MARGIN_HEIGHT, column.getWidth(), ROW_HEIGHT
							- (ListView.ROW_MARGIN_HEIGHT * 2)) : null;

			ListCell listCell;
			int iColumnPos = column.getPosition();
			int iSWTAlign = CoreTableColumn.getSWTAlign(column.getAlignment());
			if (column.getType() == TableColumn.TYPE_GRAPHIC) {
				listCell = new ListCellGraphic(this, iColumnPos, iSWTAlign, bounds);
			} else {
				listCell = new ListCell(this, iColumnPos, iSWTAlign, bounds);
			}

			if (bVisible) {
				iStartPos += bounds.width + (ListView.COLUMN_MARGIN_WIDTH * 2);
			}

			TableCellSWT cell = new TableCellImpl(this, column, i, listCell);
			listCell.setTableCell(cell);
			cell.setUpToDate(false);

			mapTableCells.put(column.getName(), cell);
			//cell.refresh();
		}
	}

	private void checkCellForSetting() {
		if (bDisposed) {
			throw new UIRuntimeException("ListRow is disposed.");
		}
	}

	private void setBackgroundColor(int iPosition) {
		checkCellForSetting();

		boolean bOdd = ((iPosition + 1) % 2) == 0;
		if (bSelected) {
			if (skinProperties == null) {
				bg = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
				fg = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
			} else {
				String sColorID = (bOdd) ? "color.row.odd.selected.bg"
						: "color.row.even.selected.bg";
				Color color = skinProperties == null ? null
						: skinProperties.getColor(sColorID);
				if (color != null) {
					bg = color;
				} else {
					bg = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
				}

				sColorID = (bOdd) ? "color.row.odd.selected.fg"
						: "color.row.even.selected.fg";
				Color cText = skinProperties == null ? null
						: skinProperties.getColor(sColorID);
				if (cText == null) {
					sColorID = (bOdd) ? "color.row.odd.fg" : "color.row.even.fg";
					cText = skinProperties == null ? null
							: skinProperties.getColor(sColorID);

					if (cText == null) {
						cText = parent.getDisplay().getSystemColor(
								SWT.COLOR_LIST_SELECTION_TEXT);
					}
				}

				fg = cText;
			}
			bRowVisuallyChangedSinceRefresh = true;

			invalidateGraphic();
			if (isVisible()) {
				redraw();
			}
		} else {
			boolean bChanged = false;
			if (skinProperties != null) {
				String sColorID = (bOdd) ? "color.row.odd.bg" : "color.row.even.bg";
				Color color = skinProperties.getColor(sColorID);
				if (color != null && !colorsEqual(color, bg)) {
					bChanged = true;
					bg = color;
				}
			} else {
				Color oldColor = bg;
				bg = bOdd ? bg = parent.getDisplay().getSystemColor(
						SWT.COLOR_LIST_BACKGROUND) : Colors.colorAltRow;
				if (!colorsEqual(oldColor, bg)) {
					bChanged = true;
				}
			}

			String sColorID = (bOdd) ? "color.row.odd.fg" : "color.row.even.fg";
			Color cText = skinProperties == null ? null
					: skinProperties.getColor(sColorID);

			if (cText == null) {
				cText = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
			}
			if (!colorsEqual(cText, fg)) {
				bChanged = true;
				fg = cText;
			}

			if (bChanged) {
				bRowVisuallyChangedSinceRefresh = true;
				invalidateGraphic();
				if (isVisible()) {
					redraw();
				}
			}
		}
		// 1160681379555: r54c4r.v?N;Invalidate Cell;true from ListRow::setBackgroundColor::316,ListRow::setIndex::468,ListView::notifyIndexChanges::385,ListView$3::run::344,Utils::execSWTThread::590,Utils::execSWTThread::618,ListView::addDataSources::313,ListView::processDataSourceQueue::242,ListView::updateUI::660,UIUpdaterImpl::update::139,UIUpdaterImpl::access$0::126,UIUpdaterImpl$1::runSupport::72,AERunnable::run::38,RunnableLock::run::35,Synchronizer::runAsyncMessages::123,Display::runAsyncMessages::3325,Display::readAndDispatch::2971,SWTThread::<init>::130,SWTThread::createInstance::64,Initializer::<init>::169,Initializer::main::147
	}

	private boolean colorsEqual(Color color1, Color color2) {
		if (color1 == color2) {
			return true;
		}
		if (color1 == null || color2 == null) {
			return false;
		}
		return color1.equals(color2);
	}

	private Color getAlternatingColor() {
		boolean bOdd = ((getIndex() + 1) % 2) == 0;
		if (skinProperties != null) {
			String sColorID = (bOdd) ? "color.row.odd.bg" : "color.row.even.bg";
			Color color = skinProperties.getColor(sColorID);
			if (color != null) {
				return color;
			}
		}
		return parent.getBackground();
	}

	// @see com.aelitis.azureus.ui.common.table.TableRowCore#delete()
	public void delete() {
		bDisposed = true;

		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT item = (TableCellSWT) iter.next();
			try {
				item.dispose();
			} catch (Exception e) {
				Debug.out("Disposing ListRow Column", e);
			}
		}
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#doPaint(org.eclipse.swt.graphics.GC, boolean, boolean)
	public void doPaint(GC gc, boolean bVisible) {
		doPaint(gc, bVisible, false);
	}

	public void doPaint(GC gc, boolean bVisible, boolean bListCellPaint) {
		// XXX Copied from TableRowImpl
		if (bDisposed || !bVisible) {
			return;
		}

		TableColumnCore[] visibleColumns = view.getVisibleColumns();

		long lTimeStart = System.currentTimeMillis();
		Rectangle oldClipping = gc.getClipping();
		try {
			gc.setForeground(getForeground());
			gc.setBackground(getBackground());
			//gc.setBackground(Display.getDefault().getSystemColor((int)(Math.random() * 16)));

			Rectangle clientArea = view.getClientArea();
			gc.fillRectangle(0, view.rowGetVisibleYOffset(this), clientArea.width,
					ROW_HEIGHT);
			if (isFocused()) {
				gc.setLineStyle(SWT.LINE_DOT);
				gc.drawRectangle(0, view.rowGetVisibleYOffset(this),
						clientArea.width - 1, ROW_HEIGHT - 1);
			}

			for (int i = 0; i < visibleColumns.length; i++) {
				TableColumnCore column = visibleColumns[i];
				TableCellSWT cell = (TableCellSWT) mapTableCells.get(column.getName());
				if (cell == null) {
					continue;
				}

				try {
					if (cell.needsPainting()) {
						Rectangle bounds = cell.getBounds();
						if (bounds != null) {
							Rectangle clipping = bounds.intersection(oldClipping);
							gc.setClipping(clipping);

							if (bListCellPaint) {
								ListCell listcell = (ListCell) cell.getBufferedTableItem();
								listcell.doPaint(gc);
							} else {
								cell.doPaint(gc);
							}
						}
					}
				} catch (Exception e) {
					//Debug.out(e);
					System.err.println("column " + column.getName() + ";" + cell);
				}
			}
		} finally {
			gc.setClipping(oldClipping);
		}
		long diff = System.currentTimeMillis() - lTimeStart;
		if (diff > 60) {
			view.log("doPaint took " + diff + "ms. " + this.toString());
		}
	}

	// XXX Copied from TableRowImpl!!
	public Object getDataSource(boolean bCoreObject) {
		if (bCoreObject) {
			return coreDataSource;
		}

		if (pluginDataSource != null) {
			return pluginDataSource;
		}

		if (coreDataSource instanceof DownloadManager) {
			DownloadManager dm = (DownloadManager) coreDataSource;
			if (dm != null) {
				try {
					pluginDataSource = DownloadManagerImpl.getDownloadStatic(dm);
				} catch (DownloadException e) { /* Ignore */
				}
			}
		}
		if (coreDataSource instanceof PEPeer) {
			PEPeer peer = (PEPeer) coreDataSource;
			if (peer != null) {
				pluginDataSource = PeerManagerImpl.getPeerForPEPeer(peer);
			}
		}

		if (coreDataSource instanceof PEPiece) {
			// XXX There is no Piece object for plugins yet
			PEPiece piece = (PEPiece) coreDataSource;
			if (piece != null) {
				pluginDataSource = null;
			}
		}

		if (coreDataSource instanceof DiskManagerFileInfo) {
			DiskManagerFileInfo fileInfo = (DiskManagerFileInfo) coreDataSource;
			if (fileInfo != null) {
				try {
					pluginDataSource = new DiskManagerFileInfoImpl(
							DownloadManagerImpl.getDownloadStatic(fileInfo.getDownloadManager()),
							fileInfo);
				} catch (DownloadException e) { /* Ignore */
				}
			}
		}

		if (coreDataSource instanceof TRHostTorrent) {
			TRHostTorrent item = (TRHostTorrent) coreDataSource;
			if (item != null) {
				pluginDataSource = new TrackerTorrentImpl(item);
			}
		}

		return pluginDataSource;
	}

	public Color getForeground() {
		if (fg == null) {
			setBackgroundColor(getIndex());
		}
		return fg;
	}

	public int getIndex() {
		return view.indexOf(this);
	}

	public TableCellCore getTableCellCore(String field) {
		if (bDisposed) {
			return null;
		}

		return (TableCellCore) mapTableCells.get(field);
	}

	// @see com.aelitis.azureus.ui.common.table.TableRowCore#invalidate()
	public void invalidate() {
		if (bDisposed) {
			return;
		}

		long lTimeStart = System.currentTimeMillis();
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell != null) {
				cell.invalidate(true);
			}
		}
		long diff = System.currentTimeMillis() - lTimeStart;
		if (diff >= 10) {
			System.out.println("invalidate: " + diff + "ms");
		}
	}

	public void invalidateGraphic() {
		if (bDisposed) {
			return;
		}

		long lTimeStart = System.currentTimeMillis();
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell.getTableColumn().getType() == TableColumn.TYPE_GRAPHIC) {
				cell.invalidate(true);
			}
		}
		long diff = System.currentTimeMillis() - lTimeStart;
		if (diff >= 10) {
			System.out.println("invalidate: " + diff + "ms");
		}
	}

	public boolean isRowDisposed() {
		return bDisposed;
	}

	public boolean isSelected() {
		return bSelected;
	}

	public boolean isVisible() {
		return view.isRowVisible(this);
	}

	public void locationChanged(int iStartColumn) {
		// TODO Auto-generated method stub

	}

	// XXX Copied from TableRowImp!
	public List refresh(boolean bDoGraphics) {
		if (bDisposed) {
			return new ArrayList();
		}

		boolean bVisible = isVisible();

		return refresh(bDoGraphics, bVisible);
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableRowCore#refresh(boolean, boolean)
	public List refresh(boolean bDoGraphics, boolean bVisible) {
		if (bDisposed) {
			return new ArrayList();
		}
		return view.rowRefresh(this, bDoGraphics, bVisible);
	}

	protected List _refresh(boolean bDoGraphics, boolean bVisible) {
		// If this were called from a plugin, we'd have to refresh the sorted column
		// even if we weren't visible

		if (!bVisible) {
			setUpToDate(false);
			return new ArrayList();
		}

		ArrayList list = new ArrayList();

		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			Rectangle bounds = cell.getBounds();
			TableColumn column = cell.getTableColumn();
			if (bounds != null && column.isVisible()) {
				boolean thisChanged = cell.refresh(bDoGraphics, bVisible);
				if (thisChanged) {
					list.add(cell);
				}
			}
		}
		if (bRowVisuallyChangedSinceRefresh) {
			list.add(0, this);
		}
		bRowVisuallyChangedSinceRefresh = false;
		return list;
	}

	public void setForeground(Color c) {
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT item = (TableCellSWT) iter.next();
			item.setForeground(c);
		}
	}

	public boolean setHeight(int iHeight) {
		// TODO Auto-generated method stub
		bRowVisuallyChangedSinceRefresh = true;
		return false;
	}

	public boolean setIconSize(Point pt) {
		// TODO Auto-generated method stub
		bRowVisuallyChangedSinceRefresh = true;
		return false;
	}

	public void setSelected(boolean bSelected) {
		if (this.bSelected == bSelected) {
			return;
		}

		this.bSelected = bSelected;
		setBackgroundColor(getIndex());
		view.rowSetSelected(this, bSelected);
	}

	/**
	 * @param b
	 */
	public void setFocused(boolean b) {
		ListRow oldRow = view.getRowFocused();
		if (b) {
			view.rowSetFocused(this);
		} else if (isFocused()) {
			view.rowSetFocused(null);
		}
		if (oldRow != null) {
			oldRow.redraw();
		}
		redraw();
	}

	public boolean isFocused() {
		return this.equals(view.getRowFocused());
	}

	/**
	 * Fixes up layout, and if something changed, it makes sure items near it 
	 * are fixed up
	 */
	public boolean setTableItem(int iRowPos) {
		if (!fixupPosition()) {
			return false;
		}

		ListRow row = view.getRow(iRowPos + 1);
		if (row != null) {
			row.fixupPosition();
		}

		bRowVisuallyChangedSinceRefresh = true;
		return true;
	}

	/**
	 * Fixes up row's layout.  Does not check if rows next to it are correct,
	 * even if some layout information changed
	 * 
	 * @return
	 */
	public boolean fixupPosition() {
		int iRowPos = getIndex();

		setBackgroundColor(iRowPos);

		if (parent == null || parent.isDisposed()) {
			return false;
		}

		return true;
	}

	public void setUpToDate(boolean upToDate) {
		if (bDisposed) {
			return;
		}

		long lTimeStart = System.currentTimeMillis();
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell != null) {
				boolean bOldUpToDate = cell.isUpToDate();
				if (bOldUpToDate != upToDate) {
					cell.setUpToDate(upToDate);

					// hack.. a call to ListCell.isShown will trigger Visibility Listener
					ListCell listcell = (ListCell) cell.getBufferedTableItem();
					listcell.isShown();
				}
			}
		}
		long diff = System.currentTimeMillis() - lTimeStart;
		if (diff >= 50) {
			System.out.println("sutd: " + upToDate + " for " + getIndex() + "; "
					+ diff + "ms");
		}
	}

	public Object getDataSource() {
		return getDataSource(false);
	}

	public TableCell getTableCell(String sColumnName) {
		if (bDisposed) {
			return null;
		}

		return (TableCell) mapTableCells.get(sColumnName);
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#getTableCellSWT(java.lang.String)
	public TableCellSWT getTableCellSWT(String sColumnName) {
		if (bDisposed) {
			return null;
		}

		return (TableCellSWT) mapTableCells.get(sColumnName);
	}

	public String getTableID() {
		return view.getTableID();
	}

	public boolean isValid() {
		if (bDisposed) {
			return true;
		}

		boolean valid = true;
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell != null) {
				valid &= cell.isValid();
			}
		}
		return valid;
	}

	public boolean getVisuallyChangedSinceLastRefresh() {
		if (bDisposed) {
			return true;
		}

		if (bRowVisuallyChangedSinceRefresh) {
			return true;
		}

		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell != null) {
				if (cell.getVisuallyChangedSinceRefresh()) {
					return true;
				}
			}
		}
		return false;
	}

	public Color getBackground() {
		if (bg == null) {
			setBackgroundColor(getIndex());
		}
		return bg;
	}

	/**
	 * @param x
	 * @param y
	 * @return
	 */
	public TableCellSWT getTableCellSWT(int x, int y) {
		Iterator iter = mapTableCells.values().iterator();
		while (iter.hasNext()) {
			TableCellSWT cell = (TableCellSWT) iter.next();
			if (cell.isShown()) {
				Rectangle bounds = cell.getBounds();
				if (bounds != null && bounds.contains(x, bounds.y)) {
					return cell;
				}
			}
		}
		return null;
	}

	public TableView getView() {
		return view;
	}

	public void redraw() {
		view.rowRefreshAsync(this, true, true);
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableRowCore#setAlternatingBGColor(boolean)
	public void setAlternatingBGColor(boolean bEvenIfNotVisible) {
		setBackgroundColor(getIndex());
	}

	public void doPaint(GC gc) {
		if (bDisposed) {
			return;
		}
		doPaint(gc, isVisible());
	}

	public String toString() {
		return "ListRow {" + getIndex() + (bDisposed ? ", Disposed" : "") + ","
				+ view.getTableID() + "}";
	}

	public int getVisibleYOffset() {
		return view.rowGetVisibleYOffset(this);
	}

	public void addMouseListener(TableRowMouseListener listener) {
		try {
			this_mon.enter();

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

			mouseListeners.add(listener);

		} finally {
			this_mon.exit();
		}
	}

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

			if (mouseListeners == null)
				return;

			mouseListeners.remove(listener);

		} finally {
			this_mon.exit();
		}
	}

  public void invokeMouseListeners(TableRowMouseEvent event) {
		ArrayList listeners = mouseListeners;
		if (listeners == null)
			return;
		
		for (int i = 0; i < listeners.size(); i++) {
			try {
				TableRowMouseListener l = (TableRowMouseListener) (listeners.get(i));

				l.rowMouseTrigger(event);

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