/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.tools.views;

import java.io.*;
import java.lang.reflect.*;

import org.eclipse.core.runtime.*;
import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.tools.internal.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.*;
import org.eclipse.ui.part.*;

public class SpyView extends ViewPart {
	private StyledText output;
	private Action spyAction, fullyQualifiedAction;
	private Listener keyFilter;
	private Runnable timer;
	private Control lastControl;
	private Field field;

	static final int TIMEOUT = 100;

	private static final ImageDescriptor SPY_IMAGE_DESCRIPTOR = ImageDescriptor.createFromURL(
			FileLocator.find(Platform.getBundle(SpiesConstants.PLUGIN_ID), new Path("icons/spy.png"), null));
	/**
	 * This is a callback that will allow us
	 * to create the viewer and initialize it.
	 */
	@Override
	public void createPartControl(Composite parent) {
		output = new StyledText(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY);

		keyFilter = e -> {
			// If this accelerator changes, change the tooltip text
			if (e.keyCode == '.' && e.stateMask == (SWT.ALT | SWT.SHIFT | SWT.CONTROL)) {
				if (spyAction.isChecked()) {
					spyAction.setChecked(false);
				} else {
					spyAction.setChecked(true);
					spyAction.run();
				}
				e.type = SWT.None;
			}
		};
		parent.getDisplay().addFilter(SWT.KeyDown, keyFilter);

		timer = new Runnable() {
			@Override
			public void run() {
				if (output == null || output.isDisposed() || !spyAction.isChecked()) return;
				Display display = output.getDisplay();
				Control control = display.getCursorControl();
				if (control != lastControl) {
					StringBuilder text = new StringBuilder();
					if (control != null) {
						text.append(getName(control)+"@"+getOSHandle(control)+"\n");
						text.append("\tStyle: "+getStyle(control)+"\n");
						text.append("\tLayout Data: "+getName(control.getLayoutData())+"\n");
						text.append("\tBounds: "+control.getBounds()+"\n");
						if (control instanceof Composite && ((Composite)control).getLayout() != null) {
							text.append("\tLayout: "+getName(((Composite)control).getLayout())+"\n");
						}
						text.append("\n");
						if (control instanceof Composite) {
							text.append("\nChildren:\n");
							for (Control element : ((Composite)control).getChildren()) {
								text.append("\t"+getName(element)+"\n");
							}
						}
						Composite parent = control.getParent();
						if (parent != null) {
							text.append("\nPeers:\n");
							for (Control peer : parent.getChildren()) {
								text.append("\t");
								if (peer == control) text.append("*");
								text.append(getName(peer)+"@"+getOSHandle(peer));
								text.append(" Layout Data: "+getName(peer.getLayoutData()));
								text.append(" Bounds: "+peer.getBounds());
								text.append("\n");
							}
							text.append("\nParent Tree:\n");
							Composite[] parents = new Composite[0];
							while (parent != null) {
								Composite[] newParents = new Composite[parents.length + 1];
								System.arraycopy(parents, 0, newParents, 0, parents.length);
								newParents[parents.length] = parent;
								parents = newParents;
								parent = parent.getParent();
							}
							for (int i = parents.length - 1; i >= 0; i--) {
								String prefix = "\t";
								for (int j = 0; j < parents.length - i - 1; j++) {
									prefix += "\t";
								}
								text.append(prefix + getName(parents[i])+"@"+getOSHandle(parents[i])+"\n");
								text.append(prefix+"\t Style: "+getStyle(parents[i])+"\n");
								text.append(prefix+"\t Bounds: "+parents[i].getBounds()+"\n");
								text.append(prefix+"\t Layout: "+getName(parents[i].getLayout())+"\n");
								text.append(prefix+"\t LayoutData: "+getName(parents[i].getLayoutData())+"\n");
							}
						}
						Error error = (Error)control.getData("StackTrace");
						if (error != null) {
							text.append("\nCreation Stack Trace:\n");
							ByteArrayOutputStream stream = new ByteArrayOutputStream();
							PrintStream s = new PrintStream(stream);
							error.printStackTrace(s);
							text.append(stream.toString());
						}
					}
					output.setText(text.toString());
				}
				lastControl = control;
				display.timerExec(100, this);
			}
		};

		makeActions();
		contributeToActionBars();
	}

	String getName(Object object) {
		if (object == null) return "null";
		String name = object.toString ();
		if (fullyQualifiedAction.isChecked()) {
			int index = name.indexOf(' ');
			if (index >= 0 && name.length() >= 1) {
				name = object.getClass ().getName () + name.substring(index);
			}
		}
		return name;
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		if (output != null & !output.isDisposed()) output.setFocus();
	}

	private String getOSHandle(Control control) {
		if (field == null) {
			for (String fieldName : new String[]{"handle", "view"}) {
				try {
					field = control.getClass().getField(fieldName);
					if (field != null) break;
				} catch (Throwable e) {}
			}
		}
		try {
			return field.get(control).toString();
		} catch (Throwable e) {}
		return "";
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	@Override
	public void dispose() {
		if (keyFilter != null) {
			Display.getCurrent().removeFilter(SWT.KeyDown, keyFilter);
		}
		super.dispose();
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.add(spyAction);
		manager.add(fullyQualifiedAction);
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(spyAction);
	}

	private void makeActions() {
		spyAction = new Action("Spy", IAction.AS_CHECK_BOX) {
			@Override
			public void run() {
				Display.getCurrent().timerExec(TIMEOUT, timer);
			}
		};
		spyAction.setToolTipText("Toggle Spy (CONTROL+ALT+SHIFT+.)");
		spyAction.setImageDescriptor(SPY_IMAGE_DESCRIPTOR);

		fullyQualifiedAction = new Action("Fully Qualify Names", IAction.AS_CHECK_BOX) {
			@Override
			public void run() {
			}
		};
	}

	private String getStyle(Widget w) {
		//MODELESS = 0;
		//BAR = 1 << 1;
		//SEPARATOR = 1 << 1;
		//TOGGLE = 1 << 1;
		//MULTI = 1 << 1;
		//INDETERMINATE = 1 << 1;
		//DBCS = 1 << 1;
		//ALPHA = 1 << 2;
		//TOOL = 1 << 2;
		//SINGLE = 1 << 2;
		//ARROW = 1 << 2;
		//DROP_DOWN = 1 << 2;
		//SHADOW_IN = 1 << 2;
		//POP_UP = 1 << 3;
		//PUSH = 1 << 3;
		//READ_ONLY = 1 << 3;
		//SHADOW_OUT = 1 << 3;
		//NO_TRIM = 1 << 3;
		//NATIVE = 1 << 3;
		//RESIZE = 1 << 4;
		//SHADOW_ETCHED_IN = 1 << 4;
		//RADIO = 1 << 4;
		//PHONETIC = 1 << 4;
		//ROMAN = 1 << 5;
		//CHECK = 1 << 5;
		//SHADOW_NONE = 1 << 5;
		//TITLE = 1 << 5;
		//DATE = 1 << 5;
		//CLOSE = 1 << 6;
		//MENU = CLOSE;
		//CASCADE = 1 << 6;
		//WRAP = 1 << 6;
		//SIMPLE = 1 << 6;
		//SHADOW_ETCHED_OUT = 1 << 6;
		//MIN = 1 << 7;
		//UP = 1 << 7;
		//TOP = UP;
		//TIME = 1 << 7;
		//HORIZONTAL = 1 << 8;
		//H_SCROLL = 1 << 8;
		//V_SCROLL = 1 << 9;
		//VERTICAL = 1 << 9;
		//MAX = 1 << 10;
		//DOWN               = 1 << 10;
		//BOTTOM             = DOWN;
		//CALENDAR = 1 << 10;
		//BORDER = 1 << 11;
		//CLIP_CHILDREN = 1 << 12;
		//BALLOON = 1 << 12;
		//CLIP_SIBLINGS = 1 << 13;
		//ON_TOP = 1 << 14;
		//LEAD               = 1 << 14;
		//LEFT               = LEAD;
		//PRIMARY_MODAL = 1 << 15;
		//HIDE_SELECTION = 1 << 15;
		//SHORT = 1 << 15;
		//MEDIUM = 1 << 16;
		//FULL_SELECTION = 1 << 16;
		//SMOOTH = 1 << 16;
		//APPLICATION_MODAL = 1 << 16;
		//SYSTEM_MODAL = 1 << 17;
		//TRAIL              = 1 << 17;
		//RIGHT              = TRAIL;
		//NO_BACKGROUND = 1 << 18;
		//NO_FOCUS = 1 << 19;
		//NO_REDRAW_RESIZE = 1 << 20;
		//NO_MERGE_PAINTS = 1 << 21;
		//NO_RADIO_GROUP = 1 << 22;
		//PASSWORD = 1 << 22;
		//FLAT = 1 << 23;
		//EMBEDDED = 1 << 24;
		//CENTER = 1 << 24;
		//LEFT_TO_RIGHT = 1 << 25;
		//RIGHT_TO_LEFT = 1 << 26;
		//MIRRORED = 1 << 27;
		//VIRTUAL = 1 << 28;
		//LONG = 1 << 28;
		//DOUBLE_BUFFERED = 1 << 29;

		int style = w.getStyle();
		String result = "";
		if (style == SWT.DEFAULT) {
			return "DEFAULT - bad!";
		}
		if ((style & 1 << 1) != 0) {
			if (w instanceof CTabFolder || w instanceof StyledText || w instanceof List || w instanceof Text || w instanceof Table || w instanceof Tree) {
				result += "MULTI | ";
			} else if (w instanceof Menu) {
				result += "BAR | ";
			} else if (w instanceof Label || w instanceof MenuItem || w instanceof ToolItem) {
				result += "SEPARATOR | ";
			} else if (w instanceof Button) {
				result += "TOGGLE | ";
			} else if (w instanceof ProgressBar) {
				result += "INDETERMINATE | ";
			} else {
				result += "BAR or SEPARATOR or TOGGLE or MULTI or INDETERMINATE or DBCS | ";
			}
		}
		if ((style & 1 << 2) != 0) {
			if (w instanceof Menu || w instanceof ToolItem || w instanceof CoolItem || w instanceof Combo) {
				result += "DROP_DOWN | ";
			} else if (w instanceof Button) {
				result += "ARROW | ";
			} else if (w instanceof CTabFolder || w instanceof StyledText || w instanceof List || w instanceof Text || w instanceof Table || w instanceof Tree) {
				result += "SINGLE | ";
			} else if (w instanceof Label || w instanceof Group) {
				result += "SHADOW_IN | ";
			} else if (w instanceof Decorations) {
				result += "TOOL | ";
			} else {
				result += "ALPHA or TOOL or SINGLE or ARROW or DROP_DOWN or SHADOW_IN | ";
			}
		}
		if ((style & 1 << 3) != 0) {
			if (w instanceof Menu) {
				result += "POP_UP | ";
			} else if (w instanceof Button || w instanceof MenuItem || w instanceof ToolItem) {
				result += "PUSH | ";
			} else if (w instanceof Combo || w instanceof Text || w instanceof StyledText) {
				result += "READ_ONLY | ";
			} else if (w instanceof Label || w instanceof Group || w instanceof ToolBar) {
				result += "SHADOW_OUT | ";
			} else if (w instanceof Decorations) {
				result += "NO_TRIM | ";
			} else {
				result += "POP_UP or PUSH or READ_ONLY or SHADOW_OUT or NO_TRIM or NATIVE | ";
			}
		}
		if ((style & 1 << 4) != 0) {
			if (w instanceof Button || w instanceof MenuItem || w instanceof ToolItem) {
				result += "RADIO | ";
			} else if (w instanceof Group) {
				result += "SHADOW_ETCHED_IN | ";
			} else if (w instanceof Decorations || w instanceof Tracker) {
				result += "RESIZE | ";
			} else {
				result += "RESIZE or SHADOW_ETCHED_IN or RADIO or PHONETIC | ";
			}
		}
		if ((style & 1 << 5) != 0) {
			if (w instanceof Button || w instanceof MenuItem || w instanceof ToolItem || w instanceof Table || w instanceof Tree) {
				result += "CHECK | ";
			} else if (w instanceof Label || w instanceof Group) {
				result += "SHADOW_NONE | ";
			} else if (w instanceof Decorations) {
				result += "TITLE | ";
			} else if (w instanceof DateTime) {
				result += "DATE | ";
			} else {
				result += "ROMAN or CHECK  or SHADOW_NONE or TITLE | ";
			}
		}
		if ((style & 1 << 6) != 0) {
			if (w instanceof MenuItem) {
				result += "CASCADE | ";
			} else if (w instanceof StyledText || w instanceof Label || w instanceof Text || w instanceof ToolBar) {
				result += "WRAP | ";
			} else if (w instanceof Combo) {
				result += "SIMPLE | ";
			} else if (w instanceof Group) {
				result += "SHADOW_ETCHED_OUT | ";
			} else if (w instanceof Decorations || w instanceof CTabFolder || w instanceof CTabItem) {
				result += "CLOSE | ";
			} else {
				result += "CLOSE or MENU or CASCADE or WRAP or SIMPLE or SHADOW_ETCHED_OUT | ";
			}
		}
		if ((style & 1 << 7) != 0) {
			if (w instanceof Decorations) {
				result += "MIN | ";
			} else if (w instanceof Button || w instanceof Tracker) {
				result += "UP | ";
			} else if (w instanceof CTabFolder) {
				result += "TOP | ";
			} else if (w instanceof DateTime) {
				result += "TIME | ";
			} else {
				result += "MIN or UP or TOP | ";
			}
		}
		if ((style & 1 << 8) != 0) {
			result += "HORIZONTAL | ";
		}
		if ((style & 1 << 9) != 0) {
			result += "VERTICAL | ";
		}
		if ((style & 1 << 10) != 0) {
			if (w instanceof Decorations) {
				result += "MAX | ";
			} else if (w instanceof Button || w instanceof Tracker) {
				result += "DOWN | ";
			} else if (w instanceof CTabFolder) {
				result += "BOTTOM | ";
			} else if (w instanceof DateTime) {
				result += "CALENDAR | ";
			} else {
				result += "MAX or DOWN or BOTTOM | ";
			}
		}
		if ((style & 1 << 11) != 0) {
			result += "BORDER | ";
		}
		if ((style & 1 << 12) != 0) {
			if (w instanceof ToolTip) {
				result += "BALLOON | ";
			} else {
				result += "CLIP_CHILDREN | ";
			}
		}
		if ((style & 1 << 13) != 0) {
			result += "CLIP_SIBLINGS | ";
		}
		if ((style & 1 << 14) != 0) {
			result += "ON_TOP or LEAD or LEFT | ";
		}
		if ((style & 1 << 15) != 0) {
			if (w instanceof Shell) {
				result += "PRIMARY_MODAL | ";
			} else if (w instanceof Table || w instanceof Tree) {
				result += "HIDE_SELECTION | ";
			} else if (w instanceof DateTime) {
				result += "SHORT | ";
			} else {
				result += "PRIMARY_MODAL or HIDE_SELECTION | ";
			}
		}
		if ((style & 1 << 16) != 0) {
			if (w instanceof StyledText || w instanceof Table || w instanceof Tree) {
				result += "FULL_SELECTION | ";
			} else if (w instanceof Shell) {
				result += "APPLICATION_MODAL | ";
			} else if (w instanceof ProgressBar) {
				result += "SMOOTH | ";
			} else if (w instanceof DateTime) {
				result += "MEDIUM | ";
			} else {
				result += "FULL_SELECTION or SMOOTH or APPLICATION_MODAL | ";
			}
		}
		if ((style & 1 << 17) != 0) {
			if (w instanceof Shell) {
				result += "SYSTEM_MODAL | ";
			} else if (w instanceof Button || w instanceof Label || w instanceof TableColumn || w instanceof Tracker || w instanceof ToolBar) {
				result += "TRAIL | ";
			} else {
				result += "SYSTEM_MODAL or TRAIL or RIGHT | ";
			}
		}
		if ((style & 1 << 18) != 0) {
			result += "NO_BACKGROUND | ";
		}
		if ((style & 1 << 19) != 0) {
			result += "NO_FOCUS | ";
		}
		if ((style & 1 << 20) != 0) {
			result += "NO_REDRAW_RESIZE | ";
		}
		if ((style & 1 << 21) != 0) {
			result += "NO_MERGE_PAINTS | ";
		}
		if ((style & 1 << 22) != 0) {
			if (w instanceof Text) {
				result += "PASSWORD | ";
			} else if (w instanceof Composite) {
				result += "NO_RADIO_GROUP | ";
			} else {
				result += "NO_RADIO_GROUP or PASSWORD | ";
			}
		}
		if ((style & 1 << 23) != 0) {
			result += "FLAT | ";
		}
		if ((style & 1 << 24) != 0) {
			if (w instanceof Button || w instanceof Label || w instanceof TableColumn) {
				result += "CENTER | ";
			} else {
				result += "EMBEDDED or CENTER | ";
			}
		}
		if ((style & 1 << 25) != 0) {
			result += "LEFT_TO_RIGHT | ";
		}
		if ((style & 1 << 26) != 0) {
			result += "RIGHT_TO_LEFT | ";
		}
		if ((style & 1 << 27) != 0) {
			result += "MIRRORED | ";
		}
		if ((style & 1 << 28) != 0) {
			if (w instanceof DateTime) {
				result += "LONG | ";
			} else {
				result += "VIRTUAL | ";
			}
		}
		if ((style & 1 << 29) != 0) {
			result += "DOUBLE_BUFFERED | ";
		}
		int lastOr = result.lastIndexOf("|");
		if (lastOr == result.length() - 2 ) result = result.substring(0, result.length() - 2);
		return result;
	}
}
