package hiro.yoshioka.sdh;

import hiro.yoshioka.util.CSVUtil;
import hiro.yoshioka.util.StringUtil;
import hiro.yoshioka.util.Util;

import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OwnerDrawLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

public class CDHTableViewer extends TableViewer implements IDiffSupport {
	private static final int SAMPLING_COUNT_AT_VIRTUAL_PACK = 37;
	protected transient Log log = LogFactory.getLog(this.getClass());
	public static final int COPY = 4;
	public static final int COPY_WITH_HEAD = 44;

	public static final int PASTE = 5;

	public static final int PASTE_IN_A_CELL = 55;

	public static final int SELECT_ALL = 6;

	protected int lastSelectedRow = -1;
	protected int lastSelectedColumn = -1;

	protected boolean supportDiff;

	protected IRDHHyperLinkAction fICDHHyperLinkAction;

	protected boolean isVirtual;
	protected boolean supportChart;
	protected int maxMultiLine = 1;

	public boolean isVirtual() {
		return isVirtual;
	}

	public void clearSelectedCell() {
		lastSelectedColumn = -1;
		lastSelectedRow = -1;
	}

	protected void right_clicked() {

	}

	protected void clicked(MouseEvent e) {
		if (lastSelectedRow >= 0 && lastSelectedColumn >= 0) {
			if (fICDHHyperLinkAction != null) {
				if (fICDHHyperLinkAction.isHyperLinkColumn(lastSelectedColumn)) {
					fICDHHyperLinkAction.activateHyperLink(getCDH(),
							lastSelectedColumn, lastSelectedRow);
				}
			}
		}
	}

	public CDHTableViewer(Composite parent) {
		this(parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION
				| SWT.HIDE_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
	}

	protected void keyReleasedAtCell(KeyEvent e) {
		if (e.character == SWT.DEL) {
			int[] indices = getTable().getSelectionIndices();
			for (int i = 0; i < indices.length; i++) {
				getCDH().changeStatus(indices[i], HeaderType.DELETE);
			}
			if (indices.length > 0) {
				final TableItem[] items = getTable().getSelection();
				for (int j = 0; j < items.length; j++) {
					preservingSelection(new delayRefresh(items[j].getData()));
				}
			}
			e.doit = false;
		}
	}

	public CDHTableViewer(Composite parent, int style) {
		super(parent, style);
		isVirtual = (style & SWT.VIRTUAL) != 0;
		setContentProvider(new SDHTableContentProvider());
		getTable().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				clearSelectedCell();
				Point p = new Point(e.x, e.y);
				TableItem item = getTable().getItem(p);
				int selectedRow = -1;
				int selectedColumn = -1;
				if (item == null) {
					return;
				}
				TableItem[] items = getTable().getItems();
				int cols = getTable().getColumnCount();
				out: for (int i = 0; i < items.length; i++) {
					if (items[i] == item) {
						for (int coli = 0; coli < cols; coli++) {
							if (item.getBounds(coli).contains(p)) {
								selectedRow = i;
								selectedColumn = coli;
							}
						}
					}
				}
				if (item != null) {
					lastSelectedRow = selectedRow;
					lastSelectedColumn = selectedColumn;
				}
				if (e.button == 3) {
					right_clicked();
				} else {
					clicked(e);
				}
			}
		});

		getTable().setLinesVisible(true);
		getTable().setHeaderVisible(true);
		getTable().addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {
				keyReleasedAtCell(e);
			}
		});
	}

	protected boolean wasSelected() {
		return (lastSelectedColumn >= 0 && lastSelectedRow >= 0);
	}

	public int getLastSelectedColumn() {
		return lastSelectedColumn;
	}

	public int getLastSelectedRow() {
		return lastSelectedRow;
	}

	public boolean canDoOperation(int operation) {
		switch (operation) {
		case COPY:
			return wasSelected();
		case COPY_WITH_HEAD:
			return wasSelected();
		case PASTE_IN_A_CELL:
			return wasSelected();
		case PASTE:
			return wasSelected();
		case SELECT_ALL:
			return true;
		}
		return false;
	}

	protected Transfer getTransfer() {
		return CSVDataHolderTransfer.getInstance();
	}

	protected void paste() {
		CSVRecordDataHolder targetCDH = (CSVRecordDataHolder) getInput();

		CSVRecordDataHolder rdh = (CSVRecordDataHolder) Util.getClipboard()
				.getContents(getTransfer());
		if (rdh != null) {
			log.info("PASTE Action rdh is  not null");
			boolean result = targetCDH.pasteOverrideAt(lastSelectedRow, rdh);
			refresh();
			getTable().update();
		} else {
			log.info("PASTE Action rdh is  null");
			String str = (String) Util.getClipboard().getContents(
					TextTransfer.getInstance());
			if (str != null && lastSelectedRow >= 0 && lastSelectedColumn >= 0) {

				getTable().deselectAll();

				String[][] datum = CSVUtil.splitByTAB_quoteDouble(str);
				boolean result = targetCDH.pasteOverrideAt(lastSelectedRow,
						lastSelectedColumn, datum);
				refresh();
				getTable().update();
			}
		}
	}

	public final void doOperation(int operation) {

		try {
			switch (operation) {
			case COPY:
				setContents(getClipDataByString(),
						getClipDataOfCSVRecordDataHolder());
				break;
			case COPY_WITH_HEAD:
				setContents(getClipDataByStringWithHead(),
						getClipDataOfCSVRecordDataHolder());
				break;
			case PASTE_IN_A_CELL:
				pasetFromClipData();
				break;
			case PASTE:
				paste();
				break;
			case SELECT_ALL:
				getTable().selectAll();
			default:
				return;
			}
		} catch (Exception e) {
			log.fatal(StringUtil.EMPTY_STRING, e);
		}
	}

	public void applyEditorValue() {
		super.applyEditorValue();
	}

	protected final void setContents(String copyTo, Object o) {
		Util.getClipboard().setContents(new Object[] { copyTo, o },
				new Transfer[] { TextTransfer.getInstance(), getTransfer() });
	}

	public void setInputAdjustDatum(CSVRecordDataHolder rdh) {
		setInput(rdh, true);
	}

	public void setInputAdjustHeader(CSVRecordDataHolder rdh) {
		setInput(rdh, false);
	}

	public void setInputAdjustArea(CSVRecordDataHolder rdh) {
		setInput(rdh, false);
		Table table = getTable();
		Rectangle area = table.getBounds();
		Point preferredSize = table.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		int width = area.width - 2 * table.getBorderWidth();
		if (preferredSize.y > area.height + table.getHeaderHeight()) {
			Point vBarSize = table.getVerticalBar().getSize();
			width -= vBarSize.x;
		}
		TableColumn[] cols = table.getColumns();
		double totalColWitdh = 0.0;
		width -= cols[0].getWidth();
		for (int i = 1; i < cols.length; i++) {
			totalColWitdh += cols[i].getWidth();
		}
		double bai = 1.0 * width / totalColWitdh;
		for (int i = 1; i < cols.length; i++) {
			cols[i].setWidth((int) (cols[i].getWidth() * bai));
		}
		table.setSize(area.width, area.height);
		table.redraw();
	}

	public CSVRecordDataHolder getCDH() {
		return (CSVRecordDataHolder) getInput();
	}

	protected void setInput(final CSVRecordDataHolder rdh, boolean adjustDatum) {
		if (log.isDebugEnabled()) {
			if (rdh != null) {
				log.debug("adjustDatum[" + adjustDatum + "] count["
						+ rdh.getRowCount() + "]");
			}
		}
		getTable().setRedraw(false);
		getTable().removeAll();

		TableColumn[] cols = getTable().getColumns();
		if (cols != null) {
			for (int i = 0; i < cols.length; i++) {
				cols[i].dispose();
			}
		}
		for (int i = 0; i < rdh.getKey().length; i++) {
			TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
			if (rdh.isNotNullIdx(i)) {
				column.getColumn().setText("(*)" + rdh.getKey()[i]);
			} else {
				column.getColumn().setText(rdh.getKey()[i]);
			}
			column.getColumn().setMoveable(false);
			column.getColumn().setResizable(true);

			if (!(supportChart() || maxMultiLine > 1)) {
				column.setLabelProvider(new RDHTableLabelProvider(this, i));
			}

			if (!isVirtual) {
				ColumnViewerSorter cSorter = new ColumnViewerSorter(this,
						column, i) {

					protected int doCompare(Viewer viewer, Object e1, Object e2) {
						StringRecordData[] p1 = (StringRecordData[]) e1;
						StringRecordData[] p2 = (StringRecordData[]) e2;
						String s1 = p1[colIdx].getString();
						String s2 = p2[colIdx].getString();
						if (s1.matches("\\d+([.]\\d+)?")
								&& s2.matches("\\d+([.]\\d+)?")) {
							if (s1.equals(s2)) {
								return 0;
							}
							double d1 = Double.parseDouble(s1);
							double d2 = Double.parseDouble(s2);
							if (d1 > d2) {
								return 100;
							} else {
								return -100;
							}
						}
						return s1.compareToIgnoreCase(s2);
					}

				};
			}
			boolean setEditorable = true;
			if (fICDHHyperLinkAction != null) {
				setEditorable = !fICDHHyperLinkAction.isHyperLinkColumn(i);
			}
			if (setEditorable && i > 0) {
				column.setEditingSupport(new RDHEditingSupport(i, true));
			}
			if (!adjustDatum) {
				column.getColumn().pack();
			}
		}
		if (supportChart() || maxMultiLine > 1) {
			this.setLabelProvider(new RDHOwnerTableLabelProvider(this,
					supportChart(), maxMultiLine));
			OwnerDrawLabelProvider.setUpOwnerDraw(this);
		}

		super.setInput(rdh);

		if (adjustDatum) {
			if (isVirtual) {
				specialPack(getTable(), rdh);
			} else {
				TableColumn[] columns = getTable().getColumns();
				for (int i = 0; i < columns.length; i++) {
					columns[i].pack();
					if (columns[i].getWidth() > 500) {
						columns[i].setWidth(500);
					}
				}
				columns[0].pack();
			}
		}

		getTable().setRedraw(true);
	}

	public boolean supportChart() {
		return supportChart;
	}

	protected void specialPack(Table moto, CSVRecordDataHolder cdh) {
		try {
			Table t = new Table(moto.getShell(), SWT.NONE);
			t.setVisible(false);

			for (int i = 0; i < cdh.getKey().length; i++) {
				TableColumn c = new TableColumn(t, SWT.NONE);
			}
			int div = cdh.getRowCount() / SAMPLING_COUNT_AT_VIRTUAL_PACK;
			if (div < SAMPLING_COUNT_AT_VIRTUAL_PACK) {
				div = 1;
			}
			for (int i = 0; i < cdh.getRowCount(); i += div) {
				TableItem itm = new TableItem(t, SWT.NONE);
				itm.setText(cdh.getRow(i));
			}
			TableColumn[] columns = t.getColumns();
			for (int i = 0; i < columns.length; i++) {
				columns[i].pack();
			}
			columns[0].pack();
			for (int i = 0; i < columns.length; i++) {
				moto.getColumn(i).setWidth(columns[i].getWidth());
				if (log.isTraceEnabled()) {
					log.trace(moto.getColumn(i).getText() + "["
							+ columns[i].getWidth() + "]");
				}
			}
			t.dispose();
			t = null;
		} catch (Throwable e) {
			e.printStackTrace();
			TableColumn[] columns = moto.getColumns();
			for (int i = 0; i < columns.length; i++) {
				columns[i].setWidth(120);
			}
		}
	}

	class delayRefresh extends Thread {
		Object o;

		public delayRefresh(Object o) {
			this.o = o;
		}

		@Override
		public void run() {
			internalRefresh(o);
		}
	}

	public void pasetFromClipData() {
		try {
			if (log.isInfoEnabled()) {
				log.info(" paste[" + lastSelectedRow + "/" + lastSelectedColumn
						+ "]");
			}
			if (lastSelectedColumn <= 0 || lastSelectedRow < 0) {
				return;
			}
			String value = (String) Util.getClipboard().getContents(
					TextTransfer.getInstance());
			CSVRecordDataHolder cdh = getCDH();
			cdh.createBackUp(lastSelectedRow);

			if (log.isInfoEnabled()) {
				log.info("PASTE value [" + value + "]");
			}

			cdh.changeString(lastSelectedRow, lastSelectedColumn, value);
			if (cdh.reseted(lastSelectedRow)) {
				cdh.changeStatus(lastSelectedRow, HeaderType.NO_EDITION);
			} else {
				cdh.changeStatus(lastSelectedRow, HeaderType.UPDATE);
			}
			// preservingSelection(new delayRefresh(getTable().getItem(
			// lastSelectedRow).getData()));
			refresh();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public CSVRecordDataHolder getClipDataOfCSVRecordDataHolder() {
		CSVRecordDataHolder rdh = (CSVRecordDataHolder) getInput();

		try {
			rdh = rdh.createIncetanceByClipIndices(getTable()
					.getSelectionIndices());
			return rdh;
		} catch (RuntimeException e) {
			log.fatal(StringUtil.EMPTY_STRING, e);
		}
		return null;
	}

	public String getClipDataByStringWithHead() {
		StringBuilder buf = new StringBuilder();
		TableColumn[] columns = getTable().getColumns();
		for (int i = 1; i < columns.length; i++) {
			buf.append(columns[i].getText()).append("\t");
		}
		buf.setLength(buf.length() - 1);
		buf.append(StringUtil.LINE_SEPARATOR);
		buf.append(getClipDataByString());
		return buf.toString();
	}

	public String getClipDataByString() {
		StringBuilder buf = new StringBuilder();
		IStructuredSelection selection = (IStructuredSelection) getSelection();

		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			StringRecordData[] items = (StringRecordData[]) iter.next();
			for (int j = 1; j < items.length; j++) {
				if (items[j] != null) {
					String str = items[j].getString();

					buf.append(str);
				}
				buf.append("\t");
			}
			buf.setLength(buf.length() - 1);
			buf.append(StringUtil.LINE_SEPARATOR);
		}
		return buf.toString();
	}

	private class RDHEditingSupport extends EditingSupport {
		protected int colIdx;
		private TextCellEditor editor;

		public RDHEditingSupport(int colIdx, boolean doMulti) {
			super(CDHTableViewer.this);
			this.colIdx = colIdx;
			if (doMulti) {
				this.editor = new TextCellEditor(getTable(), SWT.MULTI
						| SWT.V_SCROLL);
			} else {
				this.editor = new TextCellEditor(getTable(), SWT.SINGLE);
			}
		}

		protected boolean canEdit(Object element) {
			return true;
		}

		protected CellEditor getCellEditor(Object element) {
			return editor;
		}

		protected void setValue(Object element, Object value) {
			doSetValue(element, value);
			update(element, null);
		}

		protected Object getValue(Object element) {
			return ((StringRecordData[]) element)[colIdx].getString();
		}

		protected void doSetValue(Object element, Object value) {
			try {
				CSVRecordDataHolder rdh = getCDH();

				int row = getTable().getSelectionIndex();

				if (row < 0) {
					return;
				}

				if (value.equals(rdh.getRow(row)[colIdx])) {
					return;
				}

				try {
					getCDH().createBackUp(row);
				} catch (Exception e) {
					if (log.isWarnEnabled()) {
						log.warn(StringUtil.EMPTY_STRING, e);
					}
				}
				getCDH().changeString(row, colIdx, (String) value);
				if (getCDH().reseted(row)) {
					getCDH().changeStatus(row, HeaderType.NO_EDITION);
				} else {
					getCDH().changeStatus(row, HeaderType.UPDATE);
				}
			} catch (RuntimeException e) {
				log.warn(StringUtil.EMPTY_STRING, e);
			}
		}
	}

	static abstract class ColumnViewerSorter extends ViewerComparator {
		public static final int ASC = 1;

		public static final int NONE = 0;

		public static final int DESC = -1;

		private int direction = 0;

		private TableViewerColumn column;

		private ColumnViewer viewer;

		protected int colIdx;

		public ColumnViewerSorter(ColumnViewer viewer,
				TableViewerColumn column, int colIdx) {
			this.column = column;
			this.viewer = viewer;
			this.colIdx = colIdx;
			this.column.getColumn().addSelectionListener(
					new SelectionAdapter() {

						public void widgetSelected(SelectionEvent e) {
							if (ColumnViewerSorter.this.viewer.getComparator() != null) {
								if (ColumnViewerSorter.this.viewer
										.getComparator() == ColumnViewerSorter.this) {
									int tdirection = ColumnViewerSorter.this.direction;

									if (tdirection == ASC) {
										setSorter(ColumnViewerSorter.this, DESC);
									} else if (tdirection == DESC) {
										setSorter(ColumnViewerSorter.this, NONE);
									}
								} else {
									setSorter(ColumnViewerSorter.this, ASC);
								}
							} else {
								setSorter(ColumnViewerSorter.this, ASC);
							}
						}
					});
		}

		public void setSorter(ColumnViewerSorter sorter, int direction) {
			if (direction == NONE) {
				column.getColumn().getParent().setSortColumn(null);
				column.getColumn().getParent().setSortDirection(SWT.NONE);
				viewer.setComparator(null);
			} else {
				column.getColumn().getParent()
						.setSortColumn(column.getColumn());
				sorter.direction = direction;

				if (direction == ASC) {
					column.getColumn().getParent().setSortDirection(SWT.DOWN);
				} else {
					column.getColumn().getParent().setSortDirection(SWT.UP);
				}

				if (viewer.getComparator() == sorter) {
					viewer.refresh();
				} else {
					viewer.setComparator(sorter);
				}

			}
		}

		public int compare(Viewer viewer, Object e1, Object e2) {
			return direction * doCompare(viewer, e1, e2);
		}

		protected abstract int doCompare(Viewer viewer, Object e1, Object e2);
	}

	public boolean supportDiff() {
		return supportDiff;
	}

	public void setSupportChart(boolean supportChart) {
		if (log.isInfoEnabled()) {
			log.info("supportChart=" + supportChart);
		}
		this.supportChart = supportChart;
	}

	public void setSupportDiff(boolean supportDiff) {
		this.supportDiff = supportDiff;
	}

	public void setMaxMultiLine(int maxLineNum) {
		if (log.isInfoEnabled()) {
			log.info("maxLineNum=" + maxLineNum);
		}
		this.maxMultiLine = maxLineNum;
	}

	public void setSupportHyperLink(IRDHHyperLinkAction hyperLinkAction) {
		fICDHHyperLinkAction = hyperLinkAction;
	}

	public void removeSupportHyperLink() {
		fICDHHyperLinkAction = null;
	}

	public boolean isHyperLinkColumn(int colIdx) {
		if (fICDHHyperLinkAction == null) {
			return false;
		}
		return fICDHHyperLinkAction.isHyperLinkColumn(colIdx);

	}

}
