package org.vaadin.gridutil.demo;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.server.SerializablePredicate;
import com.vaadin.server.VaadinRequest;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.ui.*;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Notification.Type;
import com.vaadin.ui.components.grid.FooterCell;
import com.vaadin.ui.components.grid.FooterRow;
import com.vaadin.ui.components.grid.HeaderCell;
import com.vaadin.ui.components.grid.HeaderRow;
import com.vaadin.ui.renderers.DateRenderer;
import com.vaadin.ui.themes.ValoTheme;
import org.vaadin.gridutil.GridUtil;
import org.vaadin.gridutil.cell.CellFilterChangedListener;
import org.vaadin.gridutil.cell.CellFilterComponent;
import org.vaadin.gridutil.cell.GridCellFilter;
import org.vaadin.gridutil.cell.RangeCellFilterComponent;
import org.vaadin.gridutil.demo.data.Country.Continent;
import org.vaadin.gridutil.demo.data.DummyDataGen;
import org.vaadin.gridutil.demo.data.Inhabitants;
import org.vaadin.gridutil.renderer.BooleanRenderer;
import org.vaadin.gridutil.renderer.EditButtonValueRenderer;
import org.vaadin.gridutil.renderer.EditDeleteButtonValueRenderer;
import org.vaadin.gridutil.renderer.IndicatorRenderer;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;


@SpringUI()
@Theme("valo")
@Widgetset("org.vaadin.gridutil.demo.DemoWidgetSet")
public class DemoUI extends UI {

    private GridCellFilter<Inhabitants> filter;

    @Override
    protected void init(final VaadinRequest request) {

        VerticalLayout layout = new VerticalLayout();
        layout.setSizeFull();
        layout.setMargin(true);
        layout.setSpacing(true);

        Grid<Inhabitants> grid = genGrid();
        layout.addComponent(grid);
        layout.setExpandRatio(grid, 1);

        setContent(layout);

    }

    private Grid<Inhabitants> genGrid() {
        // init Grid
        final Grid<Inhabitants> grid = new Grid<>(Inhabitants.class);
        grid.setSizeFull();

        // init Container
        List<Inhabitants> items = DummyDataGen.genInhabitants(1000);
        grid.setItems(items);

        setColumnRenderes(grid);

        grid.setColumnOrder("id", "gender", "name", "bodySize", "birthday", "onFacebook", "country");

        initFilter(grid);
        initFooterRow(grid, items);
        initExtraHeaderRow(grid);

        initColumnAlignments(grid);
        grid.getColumn("country")
                .setHidden(true);
        return grid;
    }

    private void setColumnRenderes(final Grid grid) {
        grid.getColumn("id")
                .setRenderer(
                        new EditDeleteButtonValueRenderer<Inhabitants>(edit -> {
                            Notification.show(edit.getItem()
                                    .toString() + " want's to get edited", Type.HUMANIZED_MESSAGE);
                        }, delete -> {
                            Notification.show(delete.getItem()
                                    .toString() + " want's to get deleted", Type.WARNING_MESSAGE);

                        }))
                .setWidth(160);

        grid.getColumn("bodySize")
                .setRenderer(new IndicatorRenderer(1.8, 1.1))
                .setWidth(150);
        grid.getColumn("birthday")
                .setRenderer(new DateRenderer(DateFormat.getDateInstance()))
                .setWidth(210);
        grid.getColumn("onFacebook")
                .setRenderer(new BooleanRenderer())
                .setWidth(130);

        /*
         * the icon of the editButton will get overwritten below by css styling @see DemoUI.initColumnAlignments
         */
        grid.addColumn((ValueProvider<Inhabitants, String>) value -> String.format("%s <i>(%d)</i>",
                value.getCountry()
                        .getName(),
                value.getCountry()
                        .getPopulation()), new EditButtonValueRenderer<Inhabitants>(e -> {
            Notification.show("Goto Link for " + e.getItem()
                    .getCountry()
                    .getName(), Type.HUMANIZED_MESSAGE);
        }));
    }

    /**
     * generates a simple totalCount footer
     *
     * @param grid
     * @param items
     */
    private void initFooterRow(final Grid<Inhabitants> grid, List<Inhabitants> items) {
        final FooterRow footerRow = grid.appendFooterRow();
        footerRow.getCell("id")
                .setHtml("total:");
        final FooterCell footerCell = footerRow.join("gender", "name", "bodySize", "birthday", "onFacebook", "country");
        // inital total count
        footerCell.setHtml("<b>" + items.size() + "</b>");

        // filter change count recalculate
        grid.getDataProvider().addDataProviderListener(event -> {
            List<Inhabitants> data = event.getSource()
                    .fetch(new Query<>()).collect(Collectors.toList());
            footerCell.setHtml("<b>" + data.size() + "</b>");
        });
    }

    /**
     * example of a custom FilterComponent
     *
     * @param cellFilter needed to link filter to container
     * @param columnId
     * @return
     */
    private CellFilterComponent<HorizontalLayout> customFilterComponent(final GridCellFilter cellFilter, final String columnId) {
        CellFilterComponent<HorizontalLayout> filter = new CellFilterComponent<HorizontalLayout>() {

            ComboBox<Continent> comboBox = new ComboBox<>();

            public void triggerUpdate() {
                if (comboBox.getValue() != null) {
                    // this will add filter to container and replace old version if existing
                    cellFilter.replaceFilter(new CustomFilter(comboBox.getValue()), columnId);
                } else {
                    // remove filter by columnId
                    cellFilter.removeFilter(columnId);
                }
            }

            @Override
            public HorizontalLayout layoutComponent() {

                this.comboBox.setWidth(100, Unit.PERCENTAGE);
                comboBox.setItemCaptionGenerator(e -> e.getDisplay());
                this.comboBox.addStyleName(ValoTheme.TEXTFIELD_TINY);
                this.comboBox.addValueChangeListener(e -> triggerUpdate());
                HorizontalLayout hLayout = new HorizontalLayout();
                hLayout.addStyleName("filter-header");
                Label label = new Label("Continents: ");
                label.setWidth(100, Unit.PIXELS);
                hLayout.addComponent(label);
                hLayout.addComponent(this.comboBox);
                hLayout.setExpandRatio(this.comboBox, 1);
                return hLayout;
            }

            @Override
            public void clearFilter() {
                this.comboBox.setValue(null);
            }
        };
        return filter;
    }

    /**
     * initialize a GridCellFilter
     *
     * @param grid
     */
    private void initFilter(final Grid<Inhabitants> grid) {
        this.filter = new GridCellFilter<>(grid, Inhabitants.class);
        this.filter.setNumberFilter("id", Long.class);

        // set gender Combo with custom icons
        CellFilterComponent<ComboBox<Inhabitants.Gender>> genderFilter = this.filter.setComboBoxFilter("gender",
                Inhabitants.Gender.class,
                Arrays.asList(Inhabitants.Gender.MALE, Inhabitants.Gender.FEMALE));
        genderFilter.getComponent()
                .setItemIconGenerator(i -> i.getIcon());

        // simple filters
        this.filter.setTextFilter("name", true, true, "name starts with");
        this.filter.setNumberFilter("bodySize", Double.class, "invalid input", "smallest", "biggest");

        RangeCellFilterComponent<DateField, HorizontalLayout> dateFilter = this.filter.setDateFilter("birthday",
                new SimpleDateFormat("yyyy-MMM-dd"),
                true);
        dateFilter.getSmallestField()
                .setParseErrorMessage("da ist was schief gegangen :)");

        this.filter.setBooleanFilter("onFacebook",
                new GridCellFilter.BooleanRepresentation(true, VaadinIcons.THUMBS_UP, "yes"),
                new GridCellFilter.BooleanRepresentation(false, VaadinIcons.THUMBS_DOWN, "nope"));

        // set country combo with custom caption
        this.filter.setCustomFilter("country", customFilterComponent(this.filter, "country"));
    }

    /**
     * interacts with the GridCellFilter
     *
     * @param grid
     */
    private void initExtraHeaderRow(final Grid grid) {
        HeaderRow fistHeaderRow = grid.prependHeaderRow();
        fistHeaderRow.join("id", "gender", "name", "bodySize");
        fistHeaderRow.getCell("id")
                .setHtml("GridCellFilter simplify the filter settings for a grid");
        HeaderCell join = fistHeaderRow.join("birthday", "onFacebook", "country");
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setSpacing(true);
        join.setComponent(buttonLayout);
        Button clearAllFilters = new Button("clearAllFilters", event -> DemoUI.this.filter.clearAllFilters());
        clearAllFilters.setIcon(VaadinIcons.CLOSE);
        clearAllFilters.addStyleName(ValoTheme.BUTTON_BORDERLESS_COLORED);
        buttonLayout.addComponent(clearAllFilters);

        final Button changeVisibility = new Button("changeVisibility");
        changeVisibility.addClickListener(new Button.ClickListener() {

            private boolean visibile = true;

            @Override
            public void buttonClick(final ClickEvent event) {
                this.visibile = !this.visibile;
                changeVisibility.setIcon(this.visibile ? VaadinIcons.EYE_SLASH : VaadinIcons.EYE);
                DemoUI.this.filter.setVisible(this.visibile);
            }
        });
        changeVisibility.setIcon(VaadinIcons.EYE_SLASH);
        changeVisibility.addStyleName(ValoTheme.BUTTON_BORDERLESS_COLORED);
        buttonLayout.addComponent(changeVisibility);


        final Button presetFilter = new Button("presetFilter");
        presetFilter.addClickListener(new Button.ClickListener() {


            @Override
            public void buttonClick(final ClickEvent event) {
                CellFilterComponent<TextField> filter = DemoUI.this.filter.getCellFilter("name");
                filter.triggerUpdate();
            }
        });
        presetFilter.setIcon(VaadinIcons.PENCIL);
        presetFilter.addStyleName(ValoTheme.BUTTON_BORDERLESS_COLORED);
        buttonLayout.addComponent(presetFilter);

        // listener's on filter
        this.filter.addCellFilterChangedListener(new CellFilterChangedListener() {

            @Override
            public void changedFilter(final GridCellFilter cellFilter) {
                Notification.show("cellFilter changed " + new Date().toLocaleString(), Type.TRAY_NOTIFICATION);
            }
        });
    }

    /**
     * uses the inbuild alignments
     *
     * @param grid
     */
    private void initColumnAlignments(final Grid grid) {
        grid.getColumn("id")
                .setStyleGenerator(e -> GridUtil.ALIGN_CELL_RIGHT);
        grid.getColumn("birthday")
                .setStyleGenerator(e -> GridUtil.ALIGN_CELL_CENTER);
        grid.getColumn("country")
                .setStyleGenerator(e -> {
                    /*
                     * example how to change the icon of the buttons
                     *
                     * @formatter:off
                     * .v-grid-cell.link-icon .v-button-bar button.v-edit span:before {
                     *   color: blue; // recolor icon
                     *   content: "\f0c1"; // content-code of FontAwesome that is served by vaadin!
                     * }
                     * @formatter:on
                     */
                    return "link-icon";
                });
    }

    /**
     * example of a custom Filter that filter's by a subpropertiy of an object
     */
    private class CustomFilter implements SerializablePredicate<Inhabitants> {

        private final Continent continent;

        public CustomFilter(final Continent continent) {
            this.continent = continent;
        }

        @Override
        public boolean test(Inhabitants value) {
            if (this.continent == null) {
                return true;
            }

            if (value == null) {
                return false;
            }
            if (value.getCountry() != null) {
                return value.getCountry().equals(continent);
            }
            return false;
        }
    }

}