/*
 * Copyright (c) 2013-2017 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.ui.triview;

import java.util.ArrayList;
import java.util.List;

import org.adoptopenjdk.jitwatch.model.MetaClass;
import org.adoptopenjdk.jitwatch.model.MetaPackage;
import org.adoptopenjdk.jitwatch.model.PackageManager;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;

public class ClassSearch extends Region
{
	private TextField tfSearch;
	private ContextMenu dropMenu = new ContextMenu();
	private PackageManager pm;
	private TriView triView;

	private static final int MAX_SEARCH_RESULTS = 20;

	private boolean ignoreChanges = false;

	public ClassSearch(TriView triView, PackageManager pm)
	{
		this.triView = triView;
		this.pm = pm;

		tfSearch = new TextField();
		tfSearch.setPromptText("Enter class name");
		tfSearch.prefWidthProperty().bind(widthProperty());

		getChildren().add(tfSearch);

		tfSearch.setOnKeyReleased(new EventHandler<KeyEvent>()
		{
			@Override
			public void handle(KeyEvent keyEvent)
			{
				if (keyEvent.getCode() == KeyCode.DOWN)
				{
					dropMenu.requestFocus();
				}
			}
		});

		tfSearch.textProperty().addListener(new ChangeListener<String>()
		{
			@Override
			public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
			{
				if (!ignoreChanges)
				{
					if (tfSearch.getText().length() == 0)
					{
						if (dropMenu != null)
						{
							dropMenu.hide();
						}
					}
					else
					{
						performActionUsingSearchResults();
					}
				}
			}
		});
	}
	
	public void clear()
	{
		tfSearch.clear();
	}

	private void performActionUsingSearchResults()
	{
		List<String> results = search(tfSearch.getText());

		if (results.size() > 0)
		{
			buildResultsMenu(results);

			if (!dropMenu.isShowing())
			{
				dropMenu.show(this, Side.BOTTOM, 10, -5);
			}
		}
		else
		{
			dropMenu.hide();
		}
	}

	public void setText(String text)
	{
		ignoreChanges = true;

		tfSearch.setText(text);

		ignoreChanges = false;
	}

	private List<String> search(String term)
	{
		List<String> results = new ArrayList<>();

		List<MetaPackage> roots = pm.getRootPackages();

		for (MetaPackage mp : roots)
		{
			walkTree(mp, results, term.toLowerCase());

			if (results.size() >= MAX_SEARCH_RESULTS)
			{
				break;
			}
		}

		return results;
	}

	private void walkTree(MetaPackage mp, List<String> results, String term)
	{
		if (results.size() >= MAX_SEARCH_RESULTS)
		{
			return;
		}

		List<MetaPackage> childPackages = mp.getChildPackages();

		for (MetaPackage childPackage : childPackages)
		{
			walkTree(childPackage, results, term);
		}

		List<MetaClass> packageClasses = mp.getPackageClasses();

		for (MetaClass mc : packageClasses)
		{
			if (mc.getFullyQualifiedName().toLowerCase().contains(term))
			{
				results.add(mc.getFullyQualifiedName());

				if (results.size() >= MAX_SEARCH_RESULTS)
				{
					break;
				}
			}
		}
	}

	private void buildResultsMenu(List<String> items)
	{
		dropMenu.getItems().clear();

		for (String item : items)
		{
			final MenuItem mi = new MenuItem(item);
			mi.setOnAction(new EventHandler<ActionEvent>()
			{
				@Override
				public void handle(ActionEvent e)
				{
					dropMenu.hide();

					MetaClass metaClass = pm.getMetaClass(mi.getText());
					triView.setMetaClass(metaClass);
				}
			});

			dropMenu.getItems().add(mi);
		}
	}
}
