package org.ovirt.engine.ui.common.presenter;

import java.util.List;
import java.util.logging.Logger;

import org.ovirt.engine.core.compat.Event;
import org.ovirt.engine.core.compat.EventArgs;
import org.ovirt.engine.core.compat.IEventListener;
import org.ovirt.engine.core.compat.PropertyChangedEventArgs;
import org.ovirt.engine.ui.common.uicommon.model.DetailModelProvider;
import org.ovirt.engine.ui.common.uicommon.model.SearchableDetailModelProvider;
import org.ovirt.engine.ui.common.uicommon.model.UiCommonInitEvent;
import org.ovirt.engine.ui.common.widget.table.OrderedMultiSelectionModel;
import org.ovirt.engine.ui.uicommonweb.models.EntityModel;
import org.ovirt.engine.ui.uicommonweb.models.ListWithDetailsModel;

import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.gwtplatform.mvp.client.Presenter;
import com.gwtplatform.mvp.client.View;
import com.gwtplatform.mvp.client.annotations.ProxyEvent;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.client.proxy.PlaceRequest;
import com.gwtplatform.mvp.client.proxy.TabContentProxyPlace;

/**
 * Base class for presenters representing sub tabs that react to item selection changes within main tab presenters.
 *
 * @param <T>
 *            Main tab table row data type.
 * @param <M>
 *            Main model type.
 * @param <D>
 *            Detail model type.
 * @param <V>
 *            View type.
 * @param <P>
 *            Proxy type.
 */
public abstract class AbstractSubTabPresenter<T, M extends ListWithDetailsModel, D extends EntityModel, V extends AbstractSubTabPresenter.ViewDef<T>, P extends TabContentProxyPlace<?>>
        extends Presenter<V, P> {

    public interface ViewDef<T> extends View {

        /**
         * Notifies the view that the main tab item selection has changed.
         */
        void setMainTabSelectedItem(T selectedItem);

        /**
         * For table-based sub tab views, returns the selection model used by the sub tab table widget. Returns
         * {@code null} otherwise.
         */
        OrderedMultiSelectionModel<?> getTableSelectionModel();

        /**
         * Resets the scroll position of the sub tab table widget.
         */
        void resetTableScrollPosition();

        /**
         * Sets the loading state of the sub tab view.
         */
        void setLoadingState(LoadingState state);

    }

    private static final Logger logger = Logger.getLogger(AbstractSubTabPresenter.class.getName());

    private final PlaceManager placeManager;
    private final DetailModelProvider<M, D> modelProvider;

    private List<T> mainTabSelectedItems;

    public AbstractSubTabPresenter(EventBus eventBus, V view, P proxy,
            PlaceManager placeManager, DetailModelProvider<M, D> modelProvider) {
        super(eventBus, view, proxy);
        this.placeManager = placeManager;
        this.modelProvider = modelProvider;
    }

    @Override
    protected void onBind() {
        super.onBind();

        OrderedMultiSelectionModel<?> tableSelectionModel = getView().getTableSelectionModel();
        if (tableSelectionModel != null) {
            registerHandler(tableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
                @Override
                public void onSelectionChange(SelectionChangeEvent event) {
                    // Update detail model selection
                    updateDetailModelSelection();
                }
            }));
        }
    }

    /**
     * Updates the detail model with items currently selected in the main tab.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    void updateDetailModelSelection() {
        if (modelProvider instanceof SearchableDetailModelProvider) {
            ((SearchableDetailModelProvider) modelProvider).setSelectedItems(getSelectedItems());
        }
    }

    /**
     * We use manual reveal since we want to prevent users from accessing this presenter when there is nothing selected
     * in the main tab.
     */
    @Override
    public boolean useManualReveal() {
        return true;
    }

    @Override
    protected void onReveal() {
        super.onReveal();

        // Notify model provider that the tab has been revealed
        modelProvider.onSubTabSelected();
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Clear table selection before starting
        clearSelection();

        getView().resetTableScrollPosition();
    }

    @Override
    public void prepareFromRequest(PlaceRequest request) {
        super.prepareFromRequest(request);

        // Reveal presenter only when there is something selected in the main tab
        if (hasMainTabSelection()) {
            getProxy().manualReveal(this);
        } else {
            getProxy().manualRevealFailed();
            placeManager.revealPlace(getMainTabRequest());
        }
    }

    boolean hasMainTabSelection() {
        return mainTabSelectedItems != null && !mainTabSelectedItems.isEmpty();
    }

    T getMainTabSelectedItem() {
        return hasMainTabSelection() ? mainTabSelectedItems.get(0) : null;
    }

    /**
     * Notifies this sub tab presenter that the main tab selection has changed.
     */
    protected void updateMainTabSelection(List<T> mainTabSelectedItems) {
        this.mainTabSelectedItems = mainTabSelectedItems;

        T firstSelectedItem = getMainTabSelectedItem();

        // Notify view of selection change
        if (firstSelectedItem != null) {
            getView().setMainTabSelectedItem(firstSelectedItem);
        }
    }

    /**
     * Returns the place request associated with the main tab presenter for this sub tab.
     * <p>
     * Will be revealed when the user tries to access this sub tab while there is nothing selected in the main tab.
     */
    protected abstract PlaceRequest getMainTabRequest();

    /**
     * Returns {@code true} when the view exposes a table selection model.
     */
    boolean hasTableSelectionModel() {
        return getView().getTableSelectionModel() != null;
    }

    /**
     * Returns items currently selected in the table.
     */
    protected List<?> getSelectedItems() {
        return hasTableSelectionModel() ? getView().getTableSelectionModel().getSelectedList() : null;
    }

    /**
     * Deselects any selected values in the table.
     */
    protected void clearSelection() {
        if (hasTableSelectionModel()) {
            getView().getTableSelectionModel().clear();
        }
    }

    @ProxyEvent
    public void onUiCommonInit(UiCommonInitEvent event) {
        // Notify view when the entity of the detail model changes
        modelProvider.getModel().getEntityChangedEvent().addListener(new IEventListener() {
            @Override
            public void eventRaised(Event ev, Object sender, EventArgs args) {
                Object entity = modelProvider.getModel().getEntity();
                if (entity != null) {
                    onDetailModelEntityChange(entity);
                }
            }
        });

        // Notify view when the detail model indicates progress
        modelProvider.getModel().getPropertyChangedEvent().addListener(new IEventListener() {
            @Override
            public void eventRaised(Event ev, Object sender, EventArgs args) {
                PropertyChangedEventArgs pcArgs = (PropertyChangedEventArgs) args;
                if ("Progress".equals(pcArgs.PropertyName)) { //$NON-NLS-1$
                    if (modelProvider.getModel().getProgress() != null) {
                        getView().setLoadingState(LoadingState.LOADING);
                    }
                }
            }
        });
    }

    /**
     * Override this method in case the detail model entity type is different from main model item type.
     */
    @SuppressWarnings("unchecked")
    protected void onDetailModelEntityChange(Object entity) {
        try {
            getView().setMainTabSelectedItem((T) entity);
        } catch (ClassCastException ex) {
            // Detail model entity type is different from main model item type.
            // This usually happens with synthetic item types that wrap multiple
            // logical entities into single type. Since views can typically edit
            // a single item type, we can do nothing here.
            logger.warning("Detail model entity type is different from main model item type"); //$NON-NLS-1$
        }
    }

    protected DetailModelProvider<M, D> getModelProvider() {
        return modelProvider;
    }

}
