package org.jboss.as.console.client.widgets.tables;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.SingleSelectionModel;
import org.jboss.as.console.client.Console;

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

/**
 * @author Heiko Braun
 * @date 7/31/12
 */
public class DataProviderFilter<T> {

    private ListDataProvider<T> delegate;
    private ArrayList<T> origValues = new ArrayList<T>();
    private Predicate predicate;
    private TextBox filter;
    private Range origVisibleRange;


    public interface Predicate<T> {

        boolean apply(String prefix, T candiate);
    }

    /**
     * @param delegate
     * @param predicate
     */
    public DataProviderFilter(ListDataProvider<T> delegate, Predicate<T> predicate) {
        this.delegate = delegate;
        this.predicate = predicate;
        this.filter = new TextBox();
    }

    private void clearSelection() {
        for (HasData<T> table : delegate.getDataDisplays()) {
            SelectionModel<? super T> selectionModel = table.getSelectionModel();
            if (selectionModel instanceof SingleSelectionModel) {
                SingleSelectionModel<T> sm = (SingleSelectionModel<T>) selectionModel;
                T selectedObject = sm.getSelectedObject();
                if (selectedObject != null) {
                    ((SingleSelectionModel<T>) selectionModel).setSelected(selectedObject, false);
                }
            }
        }
    }

    /**
     * a new backup if the data provider list will be made. <br/>
     * you need to do it when the data provider gets new records.
     */
    public void reset() {
        origVisibleRange = new Range(0, this.delegate.getList().size());
        snapshot();
        clearFilter();
        filter.setText("");
    }

    /**
     * creates a backup of the orig data provider values
     * that are used to reset the provider state when the filter is cleared.
     */
    private void snapshot() {
        // backup original
        this.origValues.clear();
        this.origValues.addAll(delegate.getList());
    }

    public Widget asWidget() {


        filter.setMaxLength(30);
        filter.setVisibleLength(20);
        filter.getElement().setAttribute("style", "width:120px;");

        filter.addKeyUpHandler(new KeyUpHandler() {
            @Override
            public void onKeyUp(KeyUpEvent keyUpEvent) {

                Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {

                    @Override
                    public void execute() {
                        String prefix = filter.getText();

                        if (prefix != null && !prefix.equals("")) {
                            // filter by prefix
                            filterByPrefix(prefix);
                        } else {
                            clearFilter();
                        }
                    }

                });
            }
        });
        HorizontalPanel panel = new HorizontalPanel();
        Label label = new Label(Console.CONSTANTS.common_label_filter() + ": ");
        panel.add(label);
        panel.add(filter);

        label.getElement().setAttribute("style", "padding-top:8px;margin-right:8px");
        return panel;
    }

    private void filterByPrefix(String prefix) {

        clearSelection();
        delegate.getDataDisplays().iterator().next().setVisibleRange(origVisibleRange);

        final List<T> next = new ArrayList<T>();
        for (T item : origValues) {
            if (predicate.apply(prefix, item)) { next.add(item); }
        }


        delegate.getList().clear(); // cannot call setList() as that breaks the sort handler
        delegate.getList().addAll(next);

        if (next.size() < origVisibleRange.getLength()) {
            for (HasData table : delegate.getDataDisplays()) {
                table.setVisibleRange(0, next.size());
            }
        }
        delegate.flush();

    }

    public void clearFilter() {
        delegate.getList().clear(); // cannot call setList() as that breaks the sort handler
        delegate.getList().addAll(origValues);
        if (origVisibleRange != null) {
            delegate.getDataDisplays().iterator().next().setVisibleRange(origVisibleRange);
        }
        delegate.flush();
    }
}