/*
 * $Id$
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 *
 * http://izpack.org/
 * http://izpack.codehaus.org/
 *
 * 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 com.izforge.izpack.installer.gui;

import com.izforge.izpack.api.GuiId;
import com.izforge.izpack.api.adaptator.IXMLElement;
import com.izforge.izpack.api.data.Panel;
import com.izforge.izpack.api.handler.AbstractUIHandler;
import com.izforge.izpack.api.installer.ISummarisable;
import com.izforge.izpack.api.resource.Resources;
import com.izforge.izpack.core.handler.PromptUIHandler;
import com.izforge.izpack.gui.*;
import com.izforge.izpack.installer.data.GUIInstallData;

import javax.swing.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Defines the base class for the IzPack panels. Any panel should be a subclass of it and should
 * belong to the <code>com.izforge.izpack.panels</code> package. Since IzPack version 3.9 the
 * layout handling will be delegated to the class LayoutHelper which can be accessed by
 * <code>getLayoutHelper</code>. There are some layout helper methods in this class which will be
 * exist some time longer, but they are deprecated. At a redesign or new panel use the layout
 * helper. There is a special layout manager for IzPanels. This layout manager will be supported by
 * the layout helper. There are some points which should be observed at layouting. One point e.g. is
 * the anchor. All IzPanels have to be able to use different anchors, as minimum CENTER and
 * NORTHWEST. To use a consistent appearance use this special layout manger and not others.
 *
 * @author Julien Ponge
 * @author Klaus Bartz
 */
public abstract class IzPanel extends JPanel implements AbstractUIHandler, LayoutConstants, ISummarisable
{
    private static final long serialVersionUID = 3256442495255786038L;

    /**
     * The helper object which handles layout
     */
    protected transient  LayoutHelper layoutHelper;

    /**
     * The component which should get the focus at activation
     */
    protected Component initialFocus = null;

    /**
     * The installer internal data (actually a melting-pot class with all-public fields.
     */
    protected final GUIInstallData installData;

    /**
     * The parent IzPack installer frame.
     */
    protected final InstallerFrame parent;

    /**
     * internal headline Label
     */
    protected JLabel headLineLabel;

    /**
     * HEADLINE = "headline"
     */
    public final static String HEADLINE = "headline";

    /**
     * DELIMITER = "." ( dot )
     */
    public final static String DELIMITER = ".";

    /**
     * The resources.
     */
    private transient final Resources resources;

    /**
     * The panel meta-data.
     */
    private final Panel metadata;


    private String helpUrl = null;

    /**
     * Constructs an <tt>IzPanel</tt>.
     *
     * @param panel       the panel meta-data
     * @param parent      the parent IzPack installer frame
     * @param installData the installation data
     * @param resources   the resources
     */
    public IzPanel(Panel panel, InstallerFrame parent, GUIInstallData installData, Resources resources)
    {
        this(panel, parent, installData, (LayoutManager2) null, resources);
    }

    /**
     * Constructs an <tt>IzPanel</tt> with the given layout manager.
     * <p/>
     * Valid layout manager are the  {@link IzPanelLayout} and <tt>GridBagLayout</tt>.
     * New panels should be use IzPanelLayout. If layoutManager is
     * null, no layout manager will be created or initialized.
     *
     * @param panel         the panel meta-data
     * @param parent        the parent IzPack installer frame
     * @param installData   the installation data
     * @param layoutManager layout manager to be used with this IzPanel
     * @param resources     the resources
     */
    public IzPanel(Panel panel, InstallerFrame parent, GUIInstallData installData, LayoutManager2 layoutManager,
                   Resources resources)
    {
        super();
        this.metadata = panel;
        this.parent = parent;
        this.installData = installData;
        this.resources = resources;
        initLayoutHelper();
        if (layoutManager != null)
        {
            getLayoutHelper().startLayout(layoutManager);
        }
    }

    /**
     * Constructs an <tt>IzPanel</tt>.
     *
     * @param panel       the panel meta-data
     * @param parent      the parent IzPack installer frame
     * @param installData the installation data
     * @param iconName    the Headline icon name
     * @param resources   the resources
     */
    @Deprecated
    // FIXME: This constructor is used just in ShortcutPanel, move it there
    public IzPanel(Panel panel, InstallerFrame parent, GUIInstallData installData, String iconName,
                   Resources resources)
    {
        this(panel, parent, installData, resources);
        buildHeadline(iconName);
    }

    /**
     * Build the IzPanel internal Headline. If an external headline# is used, this method returns
     * immediately with false. Allows also to display a leading Icon for the PanelHeadline. This
     * Icon can also be different if the panel has more than one Instances. The UserInputPanel is
     * one of these Candidates. <p/> by marc.eppelmann&#064;gmx.de
     *
     * @param imageIconName  an Iconname
     */
    @Deprecated
    private void buildHeadline(String imageIconName)
    {
        if (parent.isHeading(this))
        {
            return;
        }

        String headline = null;
        String searchkey = getMetadata().getPanelId() + DELIMITER + "headline";
        if (getMetadata().hasPanelId() && installData.getMessages().getMessages().containsKey(searchkey))
        {
            headline = getString(getMetadata().getPanelId() + DELIMITER + "headline");
        }
        else {
            searchkey = getClass().getSimpleName() + DELIMITER + "headline";
            if (installData.getMessages().getMessages().containsKey(searchkey))
            {
                headline = getString(searchkey);
            }
        }
        if (headline != null)
        {
            if ((imageIconName != null) && !"".equals(imageIconName))
            {
                headLineLabel = new JLabel(headline, getImageIcon(imageIconName), SwingConstants.LEADING);
            }
            else
            {
                headLineLabel = new JLabel(headline);
            }

            Font font = headLineLabel.getFont();
            float size = font.getSize();
            font = font.deriveFont(Font.PLAIN, (size * 1.5f));
            headLineLabel.setFont(font);

            GridBagConstraints gbc = new GridBagConstraints();

            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.gridwidth = 1;
            gbc.gridheight = 1;

            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.insets = new Insets(0, 0, 0, 0);
            headLineLabel.setName(HEADLINE);
            ((GridBagLayout) getLayout()).addLayoutComponent(headLineLabel, gbc);

            add(headLineLabel);
        }
    }

    /**
     * Helper to return a language resource string.
     *
     * @param key the search key
     * @return the corresponding string, or {@code key} if the string is not found
     */
    public String getString(String key)
    {
        return installData.getMessages().get(key);
    }

    /**
     * Gets a named image icon
     *
     * @param iconName a valid image icon
     * @return the icon
     */
    public ImageIcon getImageIcon(String iconName)
    {
        return parent.getIcons().get(iconName);
    }

    /**
     * Inits and sets the internal layout helper object.
     */
    protected void initLayoutHelper()
    {
        layoutHelper = new LayoutHelper(this, installData);
    }

    /**
     * Indicates wether the panel has been validated or not. The installer won't let the user go
     * further through the installation process until the panel is validated. Default behaviour is
     * to return <code>true</code>.
     *
     * @return A boolean stating whether the panel has been validated or not.
     */
    protected boolean isValidated()
    {
        return true;
    }

    protected void saveData()
    {
        //Save Data
    }

    public boolean panelValidated()
    {
        return isValidated();
    }

    /**
     * This method is called when the panel becomes active. Default is to do nothing : feel free to
     * implement what you need in your subclasses. A panel becomes active when the user reaches it
     * during the installation process.
     */
    public void panelActivate()
    {
    }

    /**
     * This method is called when the panel gets desactivated, when the user switches to the next
     * panel. By default it doesn't do anything.
     */
    public void panelDeactivate()
    {
    }

    public void createInstallationRecord(IXMLElement rootElement)
    {
        // Default method, override to record panel contents
    }

    /**
     * Ask the user a question.
     *
     * @param title    Message title.
     * @param question The question.
     * @param choices  The set of choices to present.
     * @return The user's choice.
     * @see AbstractUIHandler#askQuestion(String, String, int)
     */
    @Override
    public int askQuestion(String title, String question, int choices)
    {
        return askQuestion(title, question, choices, -1);
    }

    /**
     * Ask the user a question.
     *
     * @param title         Message title.
     * @param question      The question.
     * @param choices       The set of choices to present.
     * @param defaultChoice The default choice. (-1 = no default choice)
     * @return The user's choice.
     * @see AbstractUIHandler#askQuestion(String, String, int, int)
     */
    @Override
    public int askQuestion(final String title, final String question, int choices, int defaultChoice)
    {
        return new PromptUIHandler(new GUIPrompt(this)).askQuestion(title, question, choices, defaultChoice);
    }

    @Override
    public int askWarningQuestion(final String title, final String question, int choices, int defaultChoice)
    {
        return new PromptUIHandler(new GUIPrompt(this)).askWarningQuestion(title, question, choices, defaultChoice);
    }

    public boolean emitNotificationFeedback(final String message)
    {
        return emitWarning(getString("installer.Message"), message);
    }

    /**
     * Notify the user about something.
     *
     * @param message The notification.
     */
    @Override
    public void emitNotification(final String message)
    {
        new PromptUIHandler(new GUIPrompt(this)).emitNotification(message);
    }

    /**
     * Warn the user about something.
     *
     * @param message The warning message.
     */
    @Override
    public boolean emitWarning(final String title, final String message)
    {
        return new PromptUIHandler(new GUIPrompt(this)).emitWarning(title, message);
    }

    /**
     * Notify the user of some error.
     *
     * @param message The error message.
     */
    @Override
    public void emitError(final String title, final String message)
    {
        new PromptUIHandler(new GUIPrompt(this)).emitError(title, message);
    }

    /**
     * Returns the component which should be get the focus at activation of this panel.
     *
     * @return the component which should be get the focus at activation of this panel
     */
    public Component getInitialFocus()
    {
        return initialFocus;
    }

    /**
     * Sets the component which should be get the focus at activation of this panel.
     *
     * @param component which should be get the focus at activation of this panel
     */
    public void setInitialFocus(Component component)
    {
        initialFocus = component;
    }

    /**
     * Calls the langpack of parent InstallerFrame for the String <tt>RuntimeClassName.subkey</tt>.
     * Do not add a point infront of subkey, it is always added in this method. If
     * <tt>RuntimeClassName.subkey</tt> is not found, the super class name will be used until it
     * is <tt>IzPanel</tt>. If no key will be found, null returns.
     *
     * @param subkey the subkey for the string which should be returned
     * @return the founded string
     */
    public String getI18nStringForClass(String subkey)
    {
        return getI18nStringForClass(subkey, null);
    }

    /**
     * Calls the langpack of parent InstallerFrame for the String <tt>RuntimeClassName.subkey</tt>.
     * Do not add a point infront of subkey, it is always added in this method. If no key will be
     * found the key or - if alternate class is null - null returns.
     *
     * @param subkey         the subkey for the string which should be returned
     * @param alternateClass the short name of the class which should be used if no string is
     *                       present with the runtime class name
     * @return the founded string
     */
    public String getI18nStringForClass(String subkey, String alternateClass)
    {
        String retval = null;

        List<String> prefixes = new ArrayList<>();
        String panelId = getMetadata().getPanelId();
        Class<?> clazz = this.getClass();

        String fullClassname = alternateClass==null?clazz.getName():alternateClass;
        String simpleClassname = alternateClass==null?clazz.getSimpleName():alternateClass;

        do
        {
            prefixes.add(fullClassname + "." + panelId);
            prefixes.add(simpleClassname + "." + panelId);
            prefixes.add(fullClassname);
            prefixes.add(simpleClassname);

            clazz = clazz.getSuperclass();
            fullClassname = clazz.getName();
            simpleClassname = clazz.getSimpleName();
        } while (alternateClass == null && !clazz.equals(IzPanel.class));
        prefixes.add(2, panelId);

        for (String prefix : prefixes)
        {
            String searchkey = prefix + "." + subkey;
            if (installData.getMessages().getMessages().containsKey(searchkey))
            {
                retval = getString(searchkey);
            }
            if (retval != null)
            {
                break;
            }
        }

        if (retval != null && retval.indexOf('$') > -1)
        {
            retval = installData.getVariables().replace(retval);
        }
        return (retval);
    }

    /**
     * Returns the parent of this IzPanel (which is a InstallerFrame).
     *
     * @return the parent of this IzPanel
     */
    public InstallerFrame getInstallerFrame()
    {
        return (parent);
    }

    // ------------- Helper for common used components ----- START ---

    /**
     * Creates a label via LabelFactory using iconId, pos and method getI18nStringForClass for
     * resolving the text to be used. If the icon id is null, the label will be created also. If
     * isFullLine true a LabelFactory.FullLineLabel will be created instead of a JLabel. The
     * difference between both classes are a different layout handling.
     *
     * @param subkey         the subkey which should be used for resolving the text
     * @param alternateClass the short name of the class which should be used if no string is
     *                       present with the runtime class name
     * @param iconId         id string for the icon
     * @param pos            horizontal alignment
     * @param isFullLine     determines whether a FullLineLabel or a JLabel should be created
     * @return the newly created label
     */
    public JLabel createLabel(String subkey, String alternateClass, String iconId, int pos,
                              boolean isFullLine)
    {
        ImageIcon imageIcon = (iconId != null) ? parent.getIcons().get(iconId) : null;
        String msg = getI18nStringForClass(subkey, alternateClass);
        JLabel label = LabelFactory.create(msg, imageIcon, pos, isFullLine);
        label.setFont(getControlTextFont());
        return (label);
    }

    /**
     * The Font of Labels in many cases
     */
    public Font getControlTextFont()
    {
        Font fontObj = (getLAF() != null) ?
                MetalLookAndFeel.getControlTextFont() : getFont();
        //if guiprefs 'labelFontSize' multiplier value
        // has been setup then apply it to the font:
        final float val;
        if ((val = LabelFactory.getLabelFontSize()) != 1.0f)
        {
            fontObj = fontObj.deriveFont(fontObj.getSize2D() * val);
        }
        return fontObj;
    }

    protected static MetalLookAndFeel getLAF()
    {
        LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
        if (lookAndFeel instanceof MetalLookAndFeel)
        {
            return ((MetalLookAndFeel) lookAndFeel);
        }
        return (null);
    }

    // ------------------- Summary stuff -------------------- START ---

    @Override
    public String getSummaryBody()
    {
        return null;
    }

    @Override
    public String getSummaryCaption()
    {
        String caption;
        if (parent.isHeading(this) && this.installData.guiPrefs.modifier.containsKey("useHeadingForSummary")
                && (this.installData.guiPrefs.modifier.get("useHeadingForSummary")).equalsIgnoreCase("yes"))
        {
            caption = getI18nStringForClass("headline", null);
        }
        else
        {
            caption = getI18nStringForClass("summaryCaption", null);
        }

        return (caption);
    }

    @Override
    public boolean isVisited()
    {
        return getMetadata().isVisited();
    }

    // ------------------- Summary stuff -------------------- END ---

    // ------------------- Inner classes ------------------- START ---

    public static class Filler extends JComponent
    {

        private static final long serialVersionUID = 3258416144414095153L;

    }

    // ------------------- Inner classes ------------------- END ---

    /**
     * Returns the used layout helper. Can be used in a derived class to create custom layout.
     *
     * @return the used layout helper
     */
    public LayoutHelper getLayoutHelper()
    {
        return layoutHelper;
    }

    /**
     * Returns the panel metadata.
     *
     * @return the panel metadata
     */
    public Panel getMetadata()
    {
        return metadata;
    }

    /**
     * Parses the text for special variables.
     */
    protected String parseText(String string_to_parse)
    {
        string_to_parse = installData.getVariables().replace(string_to_parse);
        return string_to_parse;
    }

    /**
     * Indicates wether the panel can display help. The installer will hide Help button if current
     * panel does not support help functions. Default behaviour is to return <code>false</code>.
     *
     * @return A boolean stating wether the panel supports Help function.
     */
    public boolean canShowHelp()
    {
        return helpUrl != null;
    }

    /**
     * This method is called when Help button has been clicked. By default it doesn't do anything.
     */
    public void showHelp()
    {
        // System.out.println("Help function called, helpName: " + helpName);
        if (helpUrl != null)
        {
            URL resourceUrl = resources.getURL(helpUrl);
            getHelpWindow().showHelp(getString("installer.help"), resourceUrl);
        }
    }

    private HelpWindow helpWindow = null;

    private HelpWindow getHelpWindow()
    {
        if (this.helpWindow != null)
        {
            return this.helpWindow;
        }

        this.helpWindow = new HelpWindow(parent, getString("installer.help.close"));
        helpWindow.setName(GuiId.HELP_WINDOWS.id);
        return this.helpWindow;
    }

    public void setHelpUrl(String helpUrl)
    {
        this.helpUrl = helpUrl;
    }

    @Override
    public String toString()
    {
        return "IzPanel{" +
                "class=" + getClass().getSimpleName() +
                '}';
    }

    /**
     * Returns the resources.
     *
     * @return the resources
     */
    protected Resources getResources()
    {
        return resources;
    }
}
