/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package help.screenshot;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.swing.*;

import org.junit.Test;

import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.references.*;
import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.LoadResults;
import ghidra.app.util.opinion.LoaderService;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;

public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {

	public ReferencesPluginScreenShots() {
		super();
	}

	@Test
	public void testAddReferenceDialog() {
		goToListing(0x401008);
		performAction("Add Reference From", "ReferencesPlugin", false);
		captureDialog();
	}

	@Test
	public void testChoose_external_prog() throws Exception {
		importFile(getTestDataFile("pe/w7sp1/user32.dll"));
		importFile(getTestDataFile("pe/w7sp1/shlwapi.dll"));

		runSwing(() -> {
			DataTreeDialog dialog = new DataTreeDialog(tool.getToolFrame(),
				"Choose External Program (" + "Kernel32.dll" + ")", DataTreeDialog.OPEN);
			tool.showDialog(dialog);
		}, false);
		captureDialog();
	}

	@Test
	public void testCreateOffsetTable() {
		goToListing(0x400fd0);
		makeSelection(0x400fd0, 0x400fd8);
		performAction("Create Offset References", "OffsetTablePlugin", false);
		JDialog d = waitForJDialog("Create Offset References");
		captureDialog();
		pressButtonByText(d, "Cancel");
	}

	@Test
	public void testDropZones() {
		goToListing(0x4010ae);

		performAction("View/Edit References From", "ReferencesPlugin", false);
		EditReferencesProvider provider = getProvider(EditReferencesProvider.class);
		captureIsolatedProvider(provider, 700, 300);
		captureProvider(provider);
		int topMargin = 60;
		int leftMargin = 10;
		padImage(Colors.BACKGROUND, topMargin, leftMargin, 10, 10);
		JComponent rootComp = getDockableComponent(EditReferencesProvider.class);
		JComponent comp =
			(JComponent) findComponentByName(provider.getComponent(), "operandLabels[0]");
		Point origin = new Point(leftMargin, topMargin);
		explainComponent(rootComp, comp, Palette.GREEN, origin, new Point(250, 20),
			"Operand-specific Drop Zones");
		comp = (JComponent) findComponentByName(provider.getComponent(), "mnemonicLabel");
		explainComponent(rootComp, comp, Palette.GREEN, origin, new Point(250, 20),
			"Operand-specific Drop Zones");

		comp = (JComponent) findComponentByName(provider.getComponent(), "RefsTable");
		explainComponent(rootComp, comp, Palette.GREEN, origin, new Point(450, 40),
			"Active-operand Drop Zones");

	}

	private void explainComponent(JComponent rootComp, JComponent comp, Color color, Point origin,
			Point point, String text) {
		Rectangle bounds = getBounds(comp);
		Rectangle converted = SwingUtilities.convertRectangle(comp.getParent(), bounds, rootComp);
		converted.x += origin.x;
		converted.y += origin.y;
		drawRectangle(color, converted, 2);
		Point startLine = new Point(converted.x + converted.width / 2, converted.y);
		drawLine(color, 2, startLine, point);
		Point p2 = new Point(point.x + 20, point.y);
		drawLine(color, 2, point, p2);
		Point p3 = new Point(p2.x + 4, p2.y + 5);
		drawText(text, Colors.FOREGROUND, p3, 12f);
	}

	@Test
	public void testExternal_names_dialog() {
		showProvider(ExternalReferencesProvider.class);
		captureProvider(ExternalReferencesProvider.class);
	}

	@Test
	public void testExtRefPanel() {
		goToListing(0x0401008);

		CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x401008));
		ReferencesPlugin plugin = getPlugin(tool, ReferencesPlugin.class);
		final EditReferenceDialog dialog = new EditReferenceDialog(plugin);
		dialog.initDialog(cu, 0, 0, null);
		runSwing(() -> {
			JRadioButton choiceButton = (JRadioButton) getInstanceField("extRefChoice", dialog);
			invokeInstanceMethod("refChoiceActivated", dialog,
				new Class<?>[] { JRadioButton.class }, new Object[] { choiceButton });
		}, true);
		showDialogWithoutBlocking(tool, dialog);

		JPanel panel = (JPanel) getInstanceField("extRefPanel", dialog);
		Rectangle bounds = panel.getBounds();
		bounds = SwingUtilities.convertRectangle(panel.getParent(), bounds, null);
		captureDialog();
		takeSnippet(bounds);
	}

	@Test
	public void testOffsetRefsExample() throws MemoryAccessException { // gif
		removeFlowArrows();
		goToListing(0x0400280);
		int id = program.startTransaction("Test");
		Memory memory = program.getMemory();
		memory.setByte(addr(0x400284), (byte) 0x14);
		memory.setByte(addr(0x400288), (byte) 0x18);
		memory.setByte(addr(0x40028c), (byte) 0x1c);
		memory.setByte(addr(0x400290), (byte) 0x20);
		program.endTransaction(id, true);
		makeSelection(0x400284, 0x400293);

		performAction("Create Offset References", "OffsetTablePlugin", false);
		runSwing(() -> {
			OffsetTableDialog dialog = (OffsetTableDialog) getDialog();
			dialog.setBaseAddress(addr(0x4f5000));
		});
		pressOkOnDialog();
		captureIsolatedProvider(CodeViewerProvider.class, 800, 600);
	}

	@Test
	public void testRefProvider() {
		goToListing(0x402355);
		performAction("View/Edit References From", "ReferencesPlugin", false);
		EditReferencesProvider provider = getProvider(EditReferencesProvider.class);
		captureIsolatedProvider(provider, 700, 400);
	}

	@Test
	public void testRegRefPanel() {
		goToListing(0x0401008);

		CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x401008));
		ReferencesPlugin plugin = getPlugin(tool, ReferencesPlugin.class);
		final EditReferenceDialog dialog = new EditReferenceDialog(plugin);
		dialog.initDialog(cu, 0, 0, null);
		runSwing(() -> {
			JRadioButton choiceButton = (JRadioButton) getInstanceField("regRefChoice", dialog);
			invokeInstanceMethod("refChoiceActivated", dialog,
				new Class<?>[] { JRadioButton.class }, new Object[] { choiceButton });
		}, true);
		showDialogWithoutBlocking(tool, dialog);

		JPanel panel = (JPanel) getInstanceField("regRefPanel", dialog);
		Rectangle bounds = panel.getBounds();
		bounds.height = bounds.height / 2;
		bounds = SwingUtilities.convertRectangle(panel.getParent(), bounds, null);
		captureDialog();
		takeSnippet(bounds);
	}

	@Test
	public void testStackRefPanel() {
		goToListing(0x0401008);

		CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x401008));
		ReferencesPlugin plugin = getPlugin(tool, ReferencesPlugin.class);
		final EditReferenceDialog dialog = new EditReferenceDialog(plugin);
		dialog.initDialog(cu, 0, 0, null);
		runSwing(() -> {
			JRadioButton choiceButton = (JRadioButton) getInstanceField("stackRefChoice", dialog);
			invokeInstanceMethod("refChoiceActivated", dialog,
				new Class<?>[] { JRadioButton.class }, new Object[] { choiceButton });
		}, true);
		showDialogWithoutBlocking(tool, dialog);

		JPanel panel = (JPanel) getInstanceField("stackRefPanel", dialog);
		Rectangle bounds = panel.getBounds();
		bounds.height = bounds.height / 2;
		bounds = SwingUtilities.convertRectangle(panel.getParent(), bounds, null);
		captureDialog();
		takeSnippet(bounds);
	}

	@Test
	public void testMemRefPanel() {
		goToListing(0x0401008);

		CodeUnit cu = program.getListing().getCodeUnitAt(addr(0x401008));
		ReferencesPlugin plugin = getPlugin(tool, ReferencesPlugin.class);
		final EditReferenceDialog dialog = new EditReferenceDialog(plugin);
		dialog.initDialog(cu, 0, 0, null);
		runSwing(() -> {
			JRadioButton choiceButton = (JRadioButton) getInstanceField("memRefChoice", dialog);
			invokeInstanceMethod("refChoiceActivated", dialog,
				new Class<?>[] { JRadioButton.class }, new Object[] { choiceButton });
		}, true);
		showDialogWithoutBlocking(tool, dialog);

		final JPanel panel = (JPanel) getInstanceField("memRefPanel", dialog);
		JButton button = (JButton) getInstanceField("addrHistoryButton", panel);
		Rectangle buttonBounds = button.getBounds();
		buttonBounds = SwingUtilities.convertRectangle(button.getParent(), buttonBounds, panel);
		buttonBounds.x += 20;  // padding added by takeSnippet
		buttonBounds.y += buttonBounds.height / 2 + 20;  // half button height + padding added by takeSnippet()
		System.out.println("Button bounds = " + buttonBounds);
		Rectangle bounds = panel.getBounds();
		bounds.height = 3 * bounds.height / 5;  // get rid of empty space
		bounds = SwingUtilities.convertRectangle(panel.getParent(), bounds, null);
		captureDialog();
		takeSnippet(bounds);
		Image image1 = image;

		runSwing(() -> {
			JCheckBox checkbox = (JCheckBox) getInstanceField("offsetCheckbox", panel);
			checkbox.setSelected(true);
		});
		bounds = panel.getBounds();
		bounds.height = 3 * bounds.height / 5;
		bounds = SwingUtilities.convertRectangle(panel.getParent(), bounds, null);
		captureDialog();
		takeSnippet(bounds);

		Image image2 = image;
		int gap = 40;
		int width = image1.getWidth(null);
		int height = image1.getHeight(null);
		BufferedImage newImage = createEmptyImage(width, 2 * height + gap);

		Graphics2D g2 = (Graphics2D) newImage.getGraphics();
		g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
			RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g2.drawImage(image1, 0, 0, null);
		g2.drawImage(image2, 0, height + gap, null);
		g2.setColor(Colors.FOREGROUND);
		String label = "Address History";
		int x = 150;
		int y = height + gap / 2;
		g2.drawString(label, x, y);
		x += g2.getFontMetrics().stringWidth(label) + 5;
		y -= g2.getFontMetrics().getAscent() / 3;
		g2.drawLine(x, y, x + 10, y);
		x += 10;
		g2.drawLine(buttonBounds.x, buttonBounds.y, x, y);
		g2.drawLine(buttonBounds.x, buttonBounds.y + height + gap, x, y);
		image = newImage;

	}

	private void importFile(File file) throws CancelledException, DuplicateNameException,
			InvalidNameException, VersionException, IOException {
		String programNameOverride = null;
		Project project = env.getProject();
		LoadResults<Program> loadResults = AutoImporter.importFresh(file, project,
			project.getProjectData().getRootFolder().getPathname(), this, new MessageLog(),
			TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED,
			programNameOverride, OptionChooser.DEFAULT_OPTIONS);

		try {
			loadResults.getPrimary().save(project, new MessageLog(), TaskMonitor.DUMMY);
		}
		finally {
			loadResults.release(this);
		}
	}

}
