/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.enterprise.gui.coregui.client.components.carousel;

import com.allen_sauer.gwt.log.client.Log;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.types.AnimationEffect;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.AnimationCallback;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.layout.VLayout;

import org.rhq.enterprise.gui.coregui.client.BookmarkableView;
import org.rhq.enterprise.gui.coregui.client.DetailsView;
import org.rhq.enterprise.gui.coregui.client.ViewPath;
import org.rhq.enterprise.gui.coregui.client.components.buttons.BackButton;
import org.rhq.enterprise.gui.coregui.client.components.table.AbstractTableSection;
import org.rhq.enterprise.gui.coregui.client.util.selenium.LocatableVLayout;
import org.rhq.enterprise.gui.coregui.client.util.selenium.SeleniumUtility;

/**
 * Provides the typical carousel view with the additional ability of traversing to "details" views - a masters/detail 
 * view in effect. This class is analogous to {@link AbstractTableSection}.
 * 
 * @author Jay Shaughnessy
 */
public abstract class BookmarkableCarousel extends Carousel implements BookmarkableView {

    private boolean initialDisplay;
    private VLayout detailsHolder;
    private Canvas detailsView;
    private String basePath;

    public BookmarkableCarousel(String locatorId) {
        super(locatorId);
    }

    public BookmarkableCarousel(String locatorId, String titleString) {
        super(locatorId, titleString);
    }

    public BookmarkableCarousel(String locatorId, String titleString, Criteria criteria) {
        super(locatorId, titleString, criteria);
    }

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

        this.initialDisplay = true;

        detailsHolder = new LocatableVLayout(extendLocatorId("carousel"));
        detailsHolder.setAlign(VerticalAlignment.TOP);
        detailsHolder.setMargin(4);
        detailsHolder.hide();

        addMember(detailsHolder);

        // if the detailsView is already defined it means we want the details view to be rendered prior to
        // the master view, probably due to a direct navigation or refresh (like F5 when sitting on the details page)
        if (null != detailsView) {
            switchToDetailsView();
        }
    }

    @Override
    public void renderView(ViewPath viewPath) {
        viewPath.next();
        this.basePath = viewPath.getPathToCurrent();

        if (!viewPath.isEnd()) {
            viewPath.next();
            // get the id, which may be in string format
            String id = convertCurrentViewPathToID(viewPath.getCurrent().getPath());
            detailsView = getDetailsView(id);

            switchToDetailsView();

        } else {
            switchToCarouselView();
        }
    }

    // the main CoreGUI class will assume anything with a digit as the first character in a path segment in the
    // URL is an ID.
    public static final String ID_PREFIX = "0id_"; // the prefix to be placed in front of the string IDs in URLs

    protected String convertCurrentViewPathToID(String path) {
        return path.startsWith(ID_PREFIX) ? path.substring(ID_PREFIX.length()) : path;
    }

    /**
     * Returns the details canvas with information on the item that has the given ID.
     * Note that an empty details view should be returned if the id passed in is 0 (as would
     * be the case if a new item is to be created using the details view).
     *
     * @param id the id of the details to be shown; will be "0" if an empty details view should be shown.
     */
    public abstract Canvas getDetailsView(String id);

    /**
     * Switches to viewing the details canvas, hiding the table. This does not
     * do anything with reloading data or switching to the selected row in the table;
     * this only changes the visibility of canvases.
     */
    protected void switchToDetailsView() {
        Canvas contents = getCarouselContents();

        // If the Carousel has not yet been initialized then ignore
        if (contents != null) {
            // If the carousel view is visible then gracefully switch to the details view.
            if (contents.isVisible()) {
                contents.animateHide(AnimationEffect.WIPE, new AnimationCallback() {
                    @Override
                    public void execute(boolean b) {
                        buildDetailsView();
                    }
                });
            } else {
                // Even if the table view is not visible, it may not be hidden. Instead, it may be the
                // case that its parent (the encompassing Table/HLayout) may not be visible.  This is unusual
                // because typically we switch between the table and detail view while under the subtab, but
                // if we navigate to the detail view from another subtab (for example, the drift tree context
                // menu) the Table may not be visible and the table view may not be hidden.  To make a long
                // story short, ensure the table view is hidden when displaying the details view.
                contents.hide();

                /*
                 * if the programmer chooses to go directly from the detailView in create-mode to the 
                 * detailsView in edit-mode, the content canvas will already be hidden, which means the
                 * animateHide would be a no-op (the event won't fire).  this causes the detailsHolder 
                 * to keep a reference to the previous detailsView (the one in create-mode) instead of the
                 * newly returned reference from getDetailsView(ID) that was called when the renderView
                 * methods were called hierarchically down to render the new detailsView in edit-mode.
                 * therefore, we need to explicitly destroy what's already there (presumably the detailsView
                 * in create-mode), and then rebuild it (presumably the detailsView in edit-mode).
                 */
                SeleniumUtility.destroyMembers(detailsHolder);

                buildDetailsView();
            }
        }
    }

    private void buildDetailsView() {
        detailsView.setWidth100();
        detailsView.setHeight100();

        boolean isEditable = (detailsView instanceof DetailsView && ((DetailsView) detailsView).isEditable());
        if (!isEditable) {
            // Only add the "Back to List" button if the details are definitely not editable, because if they are
            // editable, a Cancel button should already be provided by the details view.
            BackButton backButton = new BackButton(extendLocatorId("BackButton"), MSG.view_tableSection_backButton(),
                basePath);
            detailsHolder.addMember(backButton);
            VLayout verticalSpacer = new LocatableVLayout(extendLocatorId("verticalSpacer"));
            verticalSpacer.setHeight(8);
            detailsHolder.addMember(verticalSpacer);
        }

        detailsHolder.addMember(detailsView);
        detailsHolder.animateShow(AnimationEffect.WIPE);
    }

    /**
     * Switches to viewing the table, hiding the details canvas.
     */
    protected void switchToCarouselView() {
        final Canvas contents = getCarouselContents();
        if (contents != null) {
            // If this is not the initial display of the table, refresh the table's data. Otherwise, a refresh would be
            // redundant, since the data was just loaded when the table was drawn.
            if (this.initialDisplay) {
                this.initialDisplay = false;
            } else {
                Log.debug("Refreshing data for Table [" + getClass().getName() + "]...");
                refresh();
            }
            // if the detailsHolder is visible then gracefully switch views, otherwise just
            // clean up any lingering details holder and show the table view.
            if (detailsHolder != null && detailsHolder.isVisible()) {
                detailsHolder.animateHide(AnimationEffect.WIPE, new AnimationCallback() {
                    @Override
                    public void execute(boolean b) {
                        SeleniumUtility.destroyMembers(detailsHolder);

                        contents.animateShow(AnimationEffect.WIPE);
                    }
                });
            } else {
                if (detailsHolder != null) {
                    SeleniumUtility.destroyMembers(detailsHolder);
                }
                contents.animateShow(AnimationEffect.WIPE);
            }
        }
    }

}
