/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.uberfire.ext.security.management.client.widgets.management.list;

import java.util.Collection;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;
import javax.inject.Inject;

import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import org.gwtbootstrap3.client.ui.constants.HeadingSize;
import org.uberfire.client.mvp.UberView;
import org.uberfire.ext.security.management.api.AbstractEntityManager;
import org.uberfire.ext.security.management.client.widgets.popup.LoadingBox;

/**
 * <p>Presenter class for listing entities.</p>
 */
@Dependent
@Alternative
public class EntitiesList<T> implements IsWidget {

    protected static final int DEFAULT_PAGE_SIZE = 5;
    public View view;
    LoadingBox loadingBox;
    int pageSize = DEFAULT_PAGE_SIZE;
    HeadingSize headingSize = HeadingSize.H3;
    Callback callback;
    PaginationConstraints paginationConstraints;
    int totalPages = -1;
    String emptyEntitiesText = null;
    @Inject
    public EntitiesList(LoadingBox loadingBox,
                        View view) {
        this.loadingBox = loadingBox;
        this.view = view;
    }

    @PostConstruct
    protected void init() {
        view.init(this);
    }

    public void show(final AbstractEntityManager.SearchResponse<T> response,
                     final Callback<T> callback) {

        if (callback != null && response != null) {
            show(response.getResults(),
                 createPaginationCallback(response),
                 callback);
        }
    }

    public void select(final String identifier) {
        doSelectEntity(identifier,
                       true);
    }

    public void unselect(final String identifier) {
        doSelectEntity(identifier,
                       false);
    }

    /*  ******************************************************************************************************
                                 PUBLIC PRESENTER API
     ****************************************************************************************************** */

    public void clear() {
        callback = null;
        paginationConstraints = null;
        totalPages = -1;
        emptyEntitiesText = null;
        view.clear();
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public void setEmptyEntitiesText(String emptyEntitiesText) {
        this.emptyEntitiesText = emptyEntitiesText;
    }

    public void setEntityTitleSize(HeadingSize headingSize) {
        this.headingSize = headingSize;
    }

    @Override
    public Widget asWidget() {
        return view.asWidget();
    }

    String getEntityType() {
        return callback.getEntityType();
    }

    void onReadEntity(final String identifier) {
        callback.onReadEntity(identifier);
    }

    void onRemoveEntity(final String identifier) {
        callback.onRemoveEntity(identifier);
    }

    /*  ******************************************************************************************************
                                 PACKAGE PROTECTED METHODS FOR USING AS CALLBACKS FOR THE VIEW
     ****************************************************************************************************** */

    void onGoToFirstPage() {
        final int currentPage = paginationConstraints.getCurrentPage();
        callback.onChangePage(currentPage,
                              1);
    }

    void onGoToPrevPage() {
        final int currentPage = paginationConstraints.getCurrentPage();
        callback.onChangePage(currentPage,
                              currentPage - 1);
    }

    void onGoToNextPage() {
        final int currentPage = paginationConstraints.getCurrentPage();
        callback.onChangePage(currentPage,
                              currentPage + 1);
    }

    void onGoToLastPage() {
        final int currentPage = paginationConstraints.getCurrentPage();
        if (totalPages > 0) {
            callback.onChangePage(currentPage,
                                  totalPages + 1);
        }
    }

    void onSelectEntity(String identifier,
                        int index,
                        boolean isSelected) {
        doSelectEntity(identifier,
                       isSelected);
    }

    private void doSelectEntity(String identifier,
                                boolean isSelected) {
        callback.onSelectEntity(identifier,
                                isSelected);
    }

    protected void show(final Collection<T> entities,
                        final PaginationConstraints paginationConstraints,
                        final Callback<T> callback) {
        this.callback = callback;
        this.paginationConstraints = paginationConstraints;
        if (callback != null && entities != null) {
            showLoadingView();

            // Configure view and the paginator.
            view.configure(emptyEntitiesText,
                           paginationConstraints);

            // Add the entities.
            if (!entities.isEmpty()) {
                int index = 0;
                for (final T entity : entities) {
                    final String id = callback.getIdentifier(entity);
                    final String title = callback.getTitle(entity);
                    final boolean isSelected = callback.isSelected(id);
                    view.add(index,
                             id,
                             title,
                             headingSize,
                             callback.canRead(),
                             callback.canRemove(),
                             callback.canSelect(),
                             isSelected);
                    index++;
                }
            }

            hideLoadingView();
        }
    }

    protected PaginationConstraints createPaginationCallback(final AbstractEntityManager.SearchResponse searchResponse) {
        if (searchResponse != null) {
            final int page = searchResponse.getPage();
            final int total = searchResponse.getTotal();

            // If the SPI is able to get max row count, calculate total pages.
            final int totalPagesRounded = (int) Math.ceil(total / (double) searchResponse.getPageSize());
            EntitiesList.this.totalPages = total > -1 ? total / searchResponse.getPageSize() : -1;
            final boolean hasNextPage = totalPages > -1 ? page < totalPagesRounded : searchResponse.hasNextPage();
            final boolean notInFistPage = page > 1;
            final boolean isLastPageButtonEnabled = EntitiesList.this.totalPages > -1 && hasNextPage;

            return (new PaginationConstraints() {

                @Override
                public boolean isFirstPageEnabled() {
                    return notInFistPage;
                }

                @Override
                public boolean isFirstPageVisible() {
                    return notInFistPage;
                }

                @Override
                public boolean isPrevPageEnabled() {
                    return notInFistPage;
                }

                @Override
                public boolean isPrevPageVisible() {
                    return notInFistPage;
                }

                @Override
                public int getCurrentPage() {
                    return page;
                }

                @Override
                public boolean isNextPageEnabled() {
                    return hasNextPage;
                }

                @Override
                public boolean isNextPageVisible() {
                    return hasNextPage;
                }

                @Override
                public boolean isLastPageEnabled() {
                    return isLastPageButtonEnabled;
                }

                @Override
                public boolean isLastPageVisible() {
                    return isLastPageButtonEnabled;
                }

                @Override
                public Integer getTotal() {
                    return total > -1 ? total : null;
                }
            });
        }
        return null;
    }


     /*  ******************************************************************************************************
                                     PROTECTED METHODS FOR INTERNAL PRESENTER LOGIC
         ****************************************************************************************************** */

    protected void showLoadingView() {
        loadingBox.show();
    }

    protected void hideLoadingView() {
        loadingBox.hide();
    }

    public interface View extends UberView<EntitiesList> {

        View configure(final String emptyEntitiesText,
                       final PaginationConstraints paginationConstraints);

        View add(final int index,
                 final String identifier,
                 final String title,
                 final HeadingSize titleSize,
                 final boolean canRead,
                 final boolean canRemove,
                 final boolean canSelect,
                 final boolean isSelected);

        View clear();
    }

    /**
     * <p>The pagination constraints for view's pager component.</p>
     */
    public interface PaginationConstraints {

        /**
         * <p>First page button status.</p>
         * @return The first page button will be enabled if <code>true</code>, otherwise disabled.
         */
        boolean isFirstPageEnabled();

        /**
         * <p>First page button visibility.</p>
         * @return The first page button will be visible if <code>true</code>, otherwise hidden.
         */
        boolean isFirstPageVisible();

        /**
         * <p>Previous page button status.</p>
         * @return The previous page button will be enabled if <code>true</code>, otherwise disabled.
         */
        boolean isPrevPageEnabled();

        /**
         * <p>Previous page button visibility.</p>
         * @return The previous page button will be visible if <code>true</code>, otherwise hidden.
         */
        boolean isPrevPageVisible();

        /**
         * <p>Current page.</p>
         * @return The current page.
         */
        int getCurrentPage();

        /**
         * <p>Next page button status.</p>
         * @return The next page button will be enabled if <code>true</code>, otherwise disabled.
         */
        boolean isNextPageEnabled();

        /**
         * <p>Next page button visibility.</p>
         * @return The next page button will be visible if <code>true</code>, otherwise hidden.
         */
        boolean isNextPageVisible();

        /**
         * <p>Last page button status.</p>
         * @return The last page button will be enabled if <code>true</code>, otherwise disabled.
         */
        boolean isLastPageEnabled();

        /**
         * <p>Last page button visibility.</p>
         * @return The last page button will be visible if <code>true</code>, otherwise hidden.
         */
        boolean isLastPageVisible();

        /**
         * <p>Show the total number of entities, if available.</p>
         * @return The total number of entities or <code>null</code> if not available.
         */
        Integer getTotal();
    }

    /**
     * <p>Callback methods for view's user actions.</p>
     */
    public interface Callback<T> {

        /**
         * <p>The title for the entity type to manage, such as "user" or "group".</p>
         * @return The entity type title.
         */
        String getEntityType();

        /**
         * <p>Allows enabling or disabling the entities read feature.</p>
         * @return <p>Two possible values:</p>
         * <ul>
         * <li><code>true</code> - The entities list widget enables the read feature.</li>
         * <li><code>false</code> - The entities list widget disables the read feature.</li>
         * </ul>
         */
        boolean canRead();

        /**
         * <p>Allows enabling or disabling the entities delete feature.</p>
         * @return <p>Two possible values:</p>
         * <ul>
         * <li><code>true</code> - The entities list widget enables the delete feature.</li>
         * <li><code>false</code> - The entities list widget disables the delete feature.</li>
         * </ul>
         */
        boolean canRemove();

        /**
         * <p>Allows enabling or disabling the entities selection feature.</p>
         * @return <p>Two possible values:</p>
         * <ul>
         * <li><code>true</code> - The entities list widget enables the selection feature.</li>
         * <li><code>false</code> - The entities list widget disables the selection feature.</li>
         * </ul>
         */
        boolean canSelect();

        /**
         * <p>Specify if the entity must be marked as selected..</p>
         */
        boolean isSelected(final String identifier);

        /**
         * <p>The entity identifier.</p>
         * @param entity The entity.
         * @return The entity identifier.
         */
        String getIdentifier(final T entity);

        /**
         * <p>The entity title.</p>
         * @param entity The title.
         * @return The entity identifier.
         */
        String getTitle(final T entity);

        /**
         * <p>Read an entity</p>
         * @param identifier The entity's identifier to read.
         */
        void onReadEntity(final String identifier);

        /**
         * <p>Remove an entity</p>
         * @param identifier The entity's identifier to remove.
         */
        void onRemoveEntity(final String identifier);

        /**
         * <p>Select or unselect an entity from the list.</p>
         * @param identifier The entity's identifier to remove.
         * @param isSelected If <code>true</code>, the entity has been selected, otherwise has been unselected.
         */
        void onSelectEntity(final String identifier,
                            final boolean isSelected);

        /**
         * <p>Change current page.</p>
         * @param currentPage Current page.
         * @param goToPage The target page number to navigate.
         */
        void onChangePage(final int currentPage,
                          final int goToPage);
    }
}
