/*
 * Copyright ©1998-2020 by Richard A. Wilkes. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, version 2.0. If a copy of the MPL was not distributed with
 * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, version 2.0.
 */

package com.trollworks.gcs.ui.widget.search;

import com.trollworks.gcs.ui.UIUtilities;
import com.trollworks.gcs.ui.layout.FlexRow;
import com.trollworks.gcs.utility.I18n;
import com.trollworks.gcs.utility.text.Numbers;
import com.trollworks.gcs.utility.text.Text;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Collections;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/** A standard search control. */
public class Search extends JPanel implements DocumentListener, KeyListener, FocusListener {
    private SearchTarget   mTarget;
    private JLabel         mHits;
    private JTextField     mFilterField;
    private SearchDropDown mFloater;
    private String         mFilter;

    /**
     * Creates the search panel.
     *
     * @param target The search target.
     */
    public Search(SearchTarget target) {
        mTarget = target;

        mFilterField = new JTextField(10);
        mFilterField.getDocument().addDocumentListener(this);
        mFilterField.addKeyListener(this);
        mFilterField.addFocusListener(this);
        mFilterField.setToolTipText(Text.wrapPlainTextForToolTip(I18n.Text("Enter text here and press RETURN to select all matching items")));
        // This client property is specific to Mac OS X
        mFilterField.putClientProperty("JTextField.variant", "search");
        mFilterField.setMinimumSize(new Dimension(60, mFilterField.getPreferredSize().height));
        add(mFilterField);

        mHits = new JLabel();
        mHits.setToolTipText(Text.wrapPlainTextForToolTip(I18n.Text("The number of matches found")));
        adjustHits();
        add(mHits);

        FlexRow row = new FlexRow();
        row.add(mFilterField);
        row.add(mHits);
        row.apply(this);
    }

    @Override
    public boolean requestFocusInWindow() {
        return mFilterField.requestFocusInWindow();
    }

    private void searchSelect() {
        if (mFloater != null) {
            List<Object> selection = mFloater.getSelectedValues();
            if (!selection.isEmpty()) {
                mTarget.searchSelect(selection);
                return;
            }
        }
        mTarget.searchSelect(adjustHits());
    }

    /**
     * Adjust the hits count.
     *
     * @return The current hits.
     */
    public List<Object> adjustHits() {
        List<Object> hits = mFilter != null ? mTarget.search(mFilter) : Collections.emptyList();
        mHits.setText(Numbers.format(hits.size()));
        if (mFloater != null) {
            mFloater.adjustToHits(hits);
        }
        return hits;
    }

    @Override
    public void changedUpdate(DocumentEvent event) {
        documentChanged();
    }

    @Override
    public void insertUpdate(DocumentEvent event) {
        documentChanged();
    }

    @Override
    public void removeUpdate(DocumentEvent event) {
        documentChanged();
    }

    private void documentChanged() {
        String filterText = mFilterField.getText();
        mFilter = filterText.isEmpty() ? null : filterText;
        adjustHits();
    }

    private boolean redirectKeyEventToFloater(KeyEvent event) {
        if (mFloater != null && !event.isConsumed() && (event.getModifiersEx() & getToolkit().getMenuShortcutKeyMaskEx()) == 0) {
            int keyCode = event.getKeyCode();
            if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) {
                mFloater.handleKeyPressed(event);
                return true;
            }
        }
        return false;
    }

    @Override
    public void keyPressed(KeyEvent event) {
        if (!event.isConsumed() && (event.getModifiersEx() & getToolkit().getMenuShortcutKeyMaskEx()) == 0 && !redirectKeyEventToFloater(event) && event.getKeyCode() == KeyEvent.VK_ENTER) {
            searchSelect();
        }
    }

    @Override
    public void keyReleased(KeyEvent event) {
        redirectKeyEventToFloater(event);
    }

    @Override
    public void keyTyped(KeyEvent event) {
        redirectKeyEventToFloater(event);
    }

    @Override
    public void focusGained(FocusEvent event) {
        if (mFloater == null) {
            Point where = new Point(0, mFilterField.getHeight() + 1);
            mFloater = new SearchDropDown(mTarget.getSearchRenderer(), mFilterField, mTarget);
            JLayeredPane layeredPane = getRootPane().getLayeredPane();
            UIUtilities.convertPoint(where, mFilterField, layeredPane);
            layeredPane.add(mFloater, JLayeredPane.POPUP_LAYER);
            mFloater.repaint();
            adjustHits();
        }
    }

    @Override
    public void focusLost(FocusEvent event) {
        removeFloater();
    }

    /** @return The filter edit field. */
    public JTextField getFilterField() {
        return mFilterField;
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        mHits.setEnabled(enabled);
        mFilterField.setEnabled(enabled);
        if (!enabled) {
            removeFloater();
        }
    }

    private void removeFloater() {
        if (mFloater != null) {
            JRootPane rootPane = getRootPane();
            Container parent   = mFloater.getParent();
            Rectangle bounds   = mFloater.getBounds();
            UIUtilities.convertRectangle(bounds, parent, rootPane);
            if (parent != null) {
                parent.remove(mFloater);
            }
            mFloater = null;
            if (rootPane != null) {
                rootPane.repaint(bounds);
            }
        }
    }
}