/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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 com.android.ide.eclipse.adt.internal.editors.ui;

import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;

import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.SectionPart;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;

import java.lang.reflect.Method;

/**
 * Helper for the AndroidManifest form editor.
 *
 * Helps create a new SectionPart with sensible default parameters,
 * create default layout or add typical widgets.
 *
 * IMPORTANT: This is NOT a generic class. It makes a lot of assumptions on the
 * UI as used by the form editor for the AndroidManifest.
 *
 * TODO: Consider moving to a common package.
 */
public final class SectionHelper {

    /**
     * Utility class that derives from SectionPart, constructs the Section with
     * sensible defaults (with a title and a description) and provide some shorthand
     * methods for creating typically UI (label and text, form text.)
     */
    static public class ManifestSectionPart extends SectionPart {

        /**
         * Construct a SectionPart that uses a title bar and a description.
         * It's up to the caller to call setText() and setDescription().
         * <p/>
         * The section style includes a description and a title bar by default.
         *
         * @param body The parent (e.g. FormPage body)
         * @param toolkit Form Toolkit
         */
        public ManifestSectionPart(Composite body, FormToolkit toolkit) {
            this(body, toolkit, 0, false);
        }

        /**
         * Construct a SectionPart that uses a title bar and a description.
         * It's up to the caller to call setText() and setDescription().
         * <p/>
         * The section style includes a description and a title bar by default.
         * You can add extra styles, like Section.TWISTIE.
         *
         * @param body The parent (e.g. FormPage body).
         * @param toolkit Form Toolkit.
         * @param extra_style Extra styles (on top of description and title bar).
         * @param use_description True if the Section.DESCRIPTION style should be added.
         */
        public ManifestSectionPart(Composite body, FormToolkit toolkit,
                int extra_style, boolean use_description) {
            super(body, toolkit, extra_style |
                    Section.TITLE_BAR |
                    (use_description ? Section.DESCRIPTION : 0));
        }

        // Create non-static methods of helpers just for convenience

        /**
         * Creates a new composite with a TableWrapLayout set with a given number of columns.
         *
         * If the parent composite is a Section, the new composite is set as a client.
         *
         * @param toolkit Form Toolkit
         * @param numColumns Desired number of columns.
         * @return The new composite.
         */
        public Composite createTableLayout(FormToolkit toolkit, int numColumns) {
            return SectionHelper.createTableLayout(getSection(), toolkit, numColumns);
        }

        /**
         * Creates a label widget.
         * If the parent layout if a TableWrapLayout, maximize it to span over all columns.
         *
         * @param parent The parent (e.g. composite from CreateTableLayout())
         * @param toolkit Form Toolkit
         * @param label The string for the label.
         * @param tooltip An optional tooltip for the label and text. Can be null.
         * @return The new created label
         */
        public Label createLabel(Composite parent, FormToolkit toolkit, String label,
                String tooltip) {
            return SectionHelper.createLabel(parent, toolkit, label, tooltip);
        }

        /**
         * Creates two widgets: a label and a text field.
         *
         * This expects the parent composite to have a TableWrapLayout with 2 columns.
         *
         * @param parent The parent (e.g. composite from CreateTableLayout())
         * @param toolkit Form Toolkit
         * @param label The string for the label.
         * @param value The initial value of the text field. Can be null.
         * @param tooltip An optional tooltip for the label and text. Can be null.
         * @return The new created Text field (the label is not returned)
         */
        public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label,
                String value, String tooltip) {
            return SectionHelper.createLabelAndText(parent, toolkit, label, value, tooltip);
        }

        /**
         * Creates a FormText widget.
         *
         * This expects the parent composite to have a TableWrapLayout with 2 columns.
         *
         * @param parent The parent (e.g. composite from CreateTableLayout())
         * @param toolkit Form Toolkit
         * @param isHtml True if the form text will contain HTML that must be interpreted as
         *               rich text (i.e. parse tags & expand URLs).
         * @param label The string for the label.
         * @param setupLayoutData indicates whether the created form text receives a TableWrapData
         * through the setLayoutData method. In some case, creating it will make the table parent
         * huge, which we don't want.
         * @return The new created FormText.
         */
        public FormText createFormText(Composite parent, FormToolkit toolkit, boolean isHtml,
                String label, boolean setupLayoutData) {
            return SectionHelper.createFormText(parent, toolkit, isHtml, label, setupLayoutData);
        }

        /**
         * Forces the section to recompute its layout and redraw.
         * This is needed after the content of the section has been changed at runtime.
         */
        public void layoutChanged() {
            Section section = getSection();

            // Calls getSection().reflow(), which is the same that Section calls
            // when the expandable state is changed and the height changes.
            // Since this is protected, some reflection is needed to invoke it.
            try {
                Method reflow;
                reflow = section.getClass().getDeclaredMethod("reflow", (Class<?>[])null);
                reflow.setAccessible(true);
                reflow.invoke(section);
            } catch (Exception e) {
                AdtPlugin.log(e, "Error when invoking Section.reflow");
            }

            section.layout(true /* changed */, true /* all */);
        }
    }

    /**
     * Creates a new composite with a TableWrapLayout set with a given number of columns.
     *
     * If the parent composite is a Section, the new composite is set as a client.
     *
     * @param composite The parent (e.g. a Section or SectionPart)
     * @param toolkit Form Toolkit
     * @param numColumns Desired number of columns.
     * @return The new composite.
     */
    static public Composite createTableLayout(Composite composite, FormToolkit toolkit,
            int numColumns) {
        Composite table = toolkit.createComposite(composite);
        TableWrapLayout layout = new TableWrapLayout();
        layout.numColumns = numColumns;
        table.setLayout(layout);
        toolkit.paintBordersFor(table);
        if (composite instanceof Section) {
            ((Section) composite).setClient(table);
        }
        return table;
    }

    /**
     * Creates a new composite with a GridLayout set with a given number of columns.
     *
     * If the parent composite is a Section, the new composite is set as a client.
     *
     * @param composite The parent (e.g. a Section or SectionPart)
     * @param toolkit Form Toolkit
     * @param numColumns Desired number of columns.
     * @return The new composite.
     */
    static public Composite createGridLayout(Composite composite, FormToolkit toolkit,
            int numColumns) {
        Composite grid = toolkit.createComposite(composite);
        GridLayout layout = new GridLayout();
        layout.numColumns = numColumns;
        grid.setLayout(layout);
        toolkit.paintBordersFor(grid);
        if (composite instanceof Section) {
            ((Section) composite).setClient(grid);
        }
        return grid;
    }

    /**
     * Creates two widgets: a label and a text field.
     *
     * This expects the parent composite to have a TableWrapLayout with 2 columns.
     *
     * @param parent The parent (e.g. composite from CreateTableLayout())
     * @param toolkit Form Toolkit
     * @param label_text The string for the label.
     * @param value The initial value of the text field. Can be null.
     * @param tooltip An optional tooltip for the label and text. Can be null.
     * @return The new created Text field (the label is not returned)
     */
    static public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label_text,
            String value, String tooltip) {
        Label label = toolkit.createLabel(parent, label_text);
        label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
        Text text = toolkit.createText(parent, value);
        text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));

        addControlTooltip(label, tooltip);
        return text;
    }

    /**
     * Creates a label widget.
     * If the parent layout if a TableWrapLayout, maximize it to span over all columns.
     *
     * @param parent The parent (e.g. composite from CreateTableLayout())
     * @param toolkit Form Toolkit
     * @param label_text The string for the label.
     * @param tooltip An optional tooltip for the label and text. Can be null.
     * @return The new created label
     */
    static public Label createLabel(Composite parent, FormToolkit toolkit, String label_text,
            String tooltip) {
        Label label = toolkit.createLabel(parent, label_text);

        TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB);
        if (parent.getLayout() instanceof TableWrapLayout) {
            twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns;
        }
        label.setLayoutData(twd);

        addControlTooltip(label, tooltip);
        return label;
    }

    /**
     * Associates a tooltip with a control.
     *
     * This mirrors the behavior from org.eclipse.pde.internal.ui.editor.text.PDETextHover
     *
     * @param control The control to which associate the tooltip.
     * @param tooltip The tooltip string. Can use \n for multi-lines. Will not display if null.
     */
    static public void addControlTooltip(final Control control, String tooltip) {
        if (control == null || tooltip == null || tooltip.length() == 0) {
            return;
        }

        // Some kinds of controls already properly implement tooltip display.
        if (control instanceof Button) {
            control.setToolTipText(tooltip);
            return;
        }

        control.setToolTipText(null);

        final DefaultInformationControl ic = new DefaultInformationControl(control.getShell());
        ic.setInformation(tooltip);
        Point sz = ic.computeSizeHint();
        ic.setSize(sz.x, sz.y);
        ic.setVisible(false); // initially hidden

        control.addMouseTrackListener(new MouseTrackListener() {
            public void mouseEnter(MouseEvent e) {
            }

            public void mouseExit(MouseEvent e) {
                ic.setVisible(false);
            }

            public void mouseHover(MouseEvent e) {
                ic.setLocation(control.toDisplay(10, 25));  // same offset as in PDETextHover
                ic.setVisible(true);
            }
        });
        control.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                ic.dispose();
            }
        });
    }

    /**
     * Creates a FormText widget.
     *
     * This expects the parent composite to have a TableWrapLayout with 2 columns.
     *
     * @param parent The parent (e.g. composite from CreateTableLayout())
     * @param toolkit Form Toolkit
     * @param isHtml True if the form text will contain HTML that must be interpreted as
     *               rich text (i.e. parse tags & expand URLs).
     * @param label The string for the label.
     * @param setupLayoutData indicates whether the created form text receives a TableWrapData
     * through the setLayoutData method. In some case, creating it will make the table parent
     * huge, which we don't want.
     * @return The new created FormText.
     */
    static public FormText createFormText(Composite parent, FormToolkit toolkit,
            boolean isHtml, String label, boolean setupLayoutData) {
        FormText text = toolkit.createFormText(parent, true /* track focus */);
        if (setupLayoutData) {
            TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB);
            twd.maxWidth = AndroidXmlEditor.TEXT_WIDTH_HINT;
            if (parent.getLayout() instanceof TableWrapLayout) {
                twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns;
            }
            text.setLayoutData(twd);
        }
        text.setWhitespaceNormalized(true);
        if (isHtml && !label.startsWith("<form>")) {          //$NON-NLS-1$
            assert label.startsWith("<form>") : "HTML for FormText must be wrapped in <form>...</form>"; //$NON-NLS-1$
            label = "<form>" + label + "</form>";   //$NON-NLS-1$ //$NON-NLS-2$
        }
        text.setText(label, isHtml /* parseTags */, isHtml /* expandURLs */);
        return text;
    }
}
