/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.autocomplete;

import br.com.ctecinf.event.SelectEvent;
import br.com.ctecinf.event.SelectListener;
import br.com.ctecinf.text.UpperCaseFormatter;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFormattedTextField;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;

/**
 *
 * @author Cássio Conceição
 * @param <T>
 * @since 06/06/2019
 * @version 1906
 * @see http://ctecinf.com.br/
 */
public class AutoCompleteField<T> extends JFormattedTextField implements FocusListener {

    private JList<T> list;
    private JPopupMenu popup;
    private SelectListener listener;
    private JPopupMenu wait;

    public AutoCompleteField() {
        this(null, new AutoCompleteModel(), null, null);
    }

    public AutoCompleteField(AutoCompleteModel<T> model) {
        this(null, model, null, null);
    }

    public AutoCompleteField(String name, AutoCompleteModel<T> model) {
        this(name, model, null, null);
    }

    public AutoCompleteField(AutoCompleteModel<T> model, AbstractFormatter formatter) {
        this(null, model, null, formatter);
    }

    public AutoCompleteField(String name, AutoCompleteModel<T> model, AbstractFormatter formatter) {
        this(name, model, null, formatter);
    }

    public AutoCompleteField(String name, AutoCompleteModel<T> model, T selectedValue) {
        this(name, model, selectedValue, null);
    }

    public AutoCompleteField(String name, AutoCompleteModel<T> model, T selectedValue, AbstractFormatter formatter) {

        super(formatter == null ? new UpperCaseFormatter() : formatter);

        if (name != null) {
            super.setName(name);
        }

        list = new JList(model);

        this.setValue(selectedValue);

        initUI();
    }

    private void initUI() {

        setMargin(new Insets(1, 1, 1, 1));

        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(8);
        list.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                if (e.getClickCount() == 2) {

                    if (listener != null) {
                        listener.onSelected(new SelectEvent(AutoCompleteField.this, e.getID(), list.getSelectedValue()));
                    }

                    AutoCompleteField.this.setValue(list.getSelectedValue());
                    popup.setVisible(false);
                }
            }
        });

        popup = new JPopupMenu();
        popup.setOpaque(false);
        popup.add(new JScrollPane(list));

        super.addKeyListener(new KeyAdapter() {

            @Override
            public void keyReleased(KeyEvent e) {

                if (getModel().isLoaded()) {

                    if (wait != null) {
                        wait.setVisible(false);
                    }

                    if (e.getKeyCode() == KeyEvent.VK_DOWN && popup.isVisible()) {

                        int index = list.getSelectedIndex();

                        index++;

                        if (index == list.getModel().getSize()) {
                            index = 0;
                        }

                        list.setSelectedIndex(index);

                    } else if (e.getKeyCode() == KeyEvent.VK_UP && popup.isVisible()) {

                        int index = list.getSelectedIndex();

                        index--;

                        if (index == -1) {
                            index = list.getModel().getSize() - 1;
                        }

                        list.setSelectedIndex(index);

                    } else if (e.getKeyCode() == KeyEvent.VK_ENTER && popup.isVisible() && list.getSelectedIndex() > -1) {

                        if (listener != null) {
                            listener.onSelected(new SelectEvent(AutoCompleteField.this, e.getID(), list.getSelectedValue()));
                        }

                        AutoCompleteField.this.setValue(list.getSelectedValue());
                        popup.setVisible(false);

                    } else if (getText().length() > 0) {

                        getModel().filter(getText());

                        if (getModel().isEmpty()) {
                            popup.setVisible(false);
                        } else {
                            popup.revalidate();
                            popup.repaint();
                            popup.setLocation(AutoCompleteField.this.getLocationOnScreen().x, AutoCompleteField.this.getLocationOnScreen().y + AutoCompleteField.this.getHeight());
                            popup.setVisible(true);
                        }

                    } else {
                        popup.setVisible(false);
                    }

                } else if (wait == null) {
                    wait = new JPopupMenu();
                    wait.setOpaque(false);
                    wait.add("Aguarde Carregando...");
                    wait.setLocation(AutoCompleteField.this.getLocationOnScreen().x, AutoCompleteField.this.getLocationOnScreen().y + AutoCompleteField.this.getHeight());
                    wait.setVisible(true);
                }
            }
        });

        addFocusListener(this);
    }

    @Override
    public synchronized void addKeyListener(KeyListener l) {
        super.addKeyListener(l);
    }

    @Override
    public void focusLost(FocusEvent e) {
        popup.setVisible(false);
    }

    @Override
    public void focusGained(FocusEvent e) {
        selectAll();
    }

    @Override
    public T getValue() {
        return (T) super.getValue();
    }

    public AutoCompleteModel<T> getModel() {
        return (AutoCompleteModel<T>) list.getModel();
    }

    public void setModel(AutoCompleteModel<T> model) {
        list.setModel(model);
    }

    public void addSelectListener(SelectListener sl) {
        listener = sl;
    }

}
