package me.coley.recaf.ui.controls;

import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import me.coley.recaf.util.struct.Pair;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static me.coley.recaf.util.LangUtil.translate;

/**
 * Basic search bar.
 *
 * @author Matt
 */
public class SearchBar extends GridPane {
	private final Label lblResults = new Label();
	private final TextField txtSearch = new TextField();
	private final Supplier<String> text;
	// actions
	private Runnable onEscape;
	private Consumer<Results> onSearch;
	// inputs
	private boolean dirty = true;
	private String lastSearchText;
	// last result
	private Results results;

	/**
	 * @param text
	 * 		Supplier of searchable text.
	 */
	public SearchBar(Supplier<String> text) {
		setAlignment(Pos.CENTER_LEFT);
		setHgap(7);
		ColumnConstraints column1 = new ColumnConstraints();
		column1.setPercentWidth(75);
		ColumnConstraints column2 = new ColumnConstraints();
		column2.setPercentWidth(25);
		getColumnConstraints().addAll(column1, column2);
		// TODO: Better search field:
		//  - Options
		//     - case sensitivity
		//     - regex
		this.text = text;
		getStyleClass().add("context-menu");
		txtSearch.getStyleClass().add("search-field");
		txtSearch.setOnKeyPressed(e -> {
			// Check if we've updated the search query
			String searchText = e.getText();
			if(!searchText.equals(lastSearchText)) {
				dirty = true;
			}
			lastSearchText = searchText;
			// Handle operations
			if(e.getCode().getName().equals(KeyCode.ESCAPE.getName())) {
				// Escape the search bar
				if(onEscape != null)
					onEscape.run();
			} else if(e.getCode().getName().equals(KeyCode.ENTER.getName())) {
				// Empty check
				if (txtSearch.getText().isEmpty()) {
					results = null;
					return;
				}
				// Find next
				//  - Run search if necessary
				if(dirty) {
					results = search();
					dirty = false;
				}
				if(onSearch != null && results != null)
					onSearch.accept(results);
			}
		});
		add(txtSearch, 0, 0);
		add(lblResults, 1, 0);
	}

	/**
	 * @param onSearch
	 * 		Search result handler to run.
	 */
	public void setOnSearch(Consumer<Results> onSearch) {
		this.onSearch = onSearch;
	}

	/**
	 * @param onEscape
	 * 		Escape handler to run.
	 */
	public void setOnEscape(Runnable onEscape) {
		this.onEscape = onEscape;
	}

	/**
	 * Focus the search bar input text-field.
	 */
	public void focus() {
		txtSearch.requestFocus();
		txtSearch.selectAll();
	}

	/**
	 * Clear the search bar display.
	 */
	public void clear() {
		txtSearch.clear();
		lblResults.setText("");
	}

	/**
	 * @return Search result ranges of the current search parameters.
	 */
	private Results search() {
		Results results = new Results();
		String searchText = txtSearch.getText();
		String targetText = text.get();
		int len = searchText.length();
		int index = targetText.indexOf(searchText);
		while(index >= 0) {
			// Add result
			results.add(index, index + len);
			// Find next
			index = targetText.indexOf(searchText, index + len);
		}
		return results;
	}

	/**
	 * Search results wrapper.
	 *
	 * @author Matt
	 */
	public class Results {
		private final List<Pair<Integer, Integer>> ranges = new ArrayList<>();

		private void add(int start, int end) {
			ranges.add(new Pair<>(start, end));
		}

		/**
		 * @param caret
		 * 		Caret position in text.
		 *
		 * @return Next range immediately after the caret position.
		 */
		public Pair<Integer, Integer> next(int caret) {
			// Check for no matches
			if(ranges.isEmpty()) {
				lblResults.setText(translate("ui.search.results.none"));
				return null;
			}
			// Find first result where the caret is before the result range
			Pair<Integer, Integer> match = null;
			int i = 1;
			for(Pair<Integer, Integer> range : ranges) {
				if(caret < range.getKey()) {
					match = range;
					break;
				}
				i++;
			}
			// No match after caret position, wrap around
			if(match == null) {
				i = 1;
				match = ranges.get(0);
			}
			lblResults.setText(translate("ui.search.results.indexpre") + i + "/" + ranges.size());
			return match;
		}
	}
}