/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.intro.impl.swt;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.internal.intro.impl.model.AbstractBaseIntroElement;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroContainer;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroElement;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroIdElement;
import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage;
import org.eclipse.ui.internal.intro.impl.model.IntroGroup;
import org.eclipse.ui.internal.intro.impl.model.IntroImage;
import org.eclipse.ui.internal.intro.impl.model.IntroLink;
import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot;
import org.eclipse.ui.internal.intro.impl.model.IntroText;
import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
import org.eclipse.ui.internal.intro.impl.util.ImageUtil;
import org.eclipse.ui.internal.intro.impl.util.Log;
import org.osgi.framework.Bundle;

public class PageStyleManager extends SharedStyleManager {

    private AbstractIntroPage page;
	private Map<Properties, StyleContext> altStyleContexts = new HashMap<>();
    private IntroModelRoot root;


    /**
     * Constructor used when page styles need to be loaded. The plugin's bundle
     * is retrieved from the page model class. The default properties are
     * assumed to be the presentation shared properties. The inherrited
     * properties are properties that we got from included and extension styles.
     *
     * @param modelRoot
     */
    public PageStyleManager(AbstractIntroPage page, Properties sharedProperties) {
        this.page = page;
        context = new StyleContext();
        context.bundle = page.getBundle();

        // honor shared-style.
        if (page.injectSharedStyle())
            properties = new Properties(sharedProperties);
        else
            properties = new Properties();
        String altStyle = page.getAltStyle();
        if (altStyle != null) {
            load(properties, altStyle, context);
        }

        // AltStyles Hashtable has alt-styles as keys, the bundles as
        // values.
		Map<String, Bundle> altStyles = page.getAltStyles();
        if (altStyles != null) {
			for (Entry<String, Bundle> entry : altStyles.entrySet()) {
				String style = entry.getKey();
                Properties inheritedProperties = new Properties();
				Bundle bundle = entry.getValue();
                StyleContext sc = new StyleContext();
                sc.bundle = bundle;
                load(inheritedProperties, style, sc);
                altStyleContexts.put(inheritedProperties, sc);
            }
        }

        // cache root
        root = (IntroModelRoot) page.getParentPage().getParent();
    }


    // Override parent method to include alt-styles. Use implicit keys as well.
    @Override
	public String getProperty(String key) {
        return getProperty(key, true);
    }

    // Override parent method to include alt-styles. If useImplicit is true, we
    // try to resolve a key without its pageId.
    private String getProperty(String key, boolean useImplicitKey) {
        Properties aProperties = findPropertyOwner(key);
        String value = super.doGetProperty(aProperties, key);
        if (useImplicitKey) {
            if (value == null && page.getId() != null
                    && key.startsWith(page.getId())) {
                // did not find the key as-is. Trim pageId and try again.
                String relativeKey = key.substring(page.getId().length());
                return getProperty(relativeKey);
            }
        }
        return value;
    }


    /**
     * Finds a Properties that represents an inherited shared style, or this
     * current pages style. If the given key is not found, the pageId is trimmed
     * from the begining of the key, and the key is looked up again. If key does
     * not start with a pageId, lookup only the key as is.
     *
     * @param key
     * @return
     */
    private Properties findPropertyOwner(String key) {
        // search for the key in this page's properties first.
        if (properties.containsKey(key))
            return properties;

        // search inherited properties second.
		for (Properties aProperties : altStyleContexts.keySet()) {
            if (aProperties.containsKey(key))
                return aProperties;
        }
        // we did not find the key. Return the local properties anyway.
        return properties;
    }



    /**
     * Finds the context from which this key was loaded. If the key is not from
     * an inherited alt style, then use the context corresponding to this page.
     *
     * @param key
     * @return
     */

    @Override
	protected StyleContext getAssociatedContext(String key) {
        Properties aProperties = findPropertyOwner(key);
        StyleContext context = altStyleContexts.get(aProperties);
        if (context != null)
            return context;
        return super.getAssociatedContext(key);
    }


    /*
     * For number of columns, do not return 1 as the default, to allow for
     * further processing. At the root page level, getting a 0 as ncolumns means
     * that the number of columns is the number of children. At the page level,
     * default is 1.
     */
    public int getPageNumberOfColumns() {
        return getIntProperty(page, ".layout.ncolumns", 0); //$NON-NLS-1$
    }


    public int getNumberOfColumns(IntroGroup group) {
        return getIntProperty(group, ".layout.ncolumns", 0); //$NON-NLS-1$
    }

    public boolean getEqualWidth(IntroGroup group) {
    	return getBooleanProperty(group, ".layout.equalWidth", false); //$NON-NLS-1$
    }

    public int getPageVerticalSpacing() {
        return getIntProperty(page, ".layout.vspacing", 5); //$NON-NLS-1$
    }

    public int getVerticalSpacing(IntroGroup group) {
        return getIntProperty(group, ".layout.vspacing", 5); //$NON-NLS-1$
    }

    public int getPageHorizantalSpacing() {
        return getIntProperty(page, ".layout.hspacing", 5); //$NON-NLS-1$
    }

    public int getHorizantalSpacing(IntroGroup group) {
        return getIntProperty(group, ".layout.hspacing", 5); //$NON-NLS-1$
    }

    public int getColSpan(AbstractBaseIntroElement element) {
        return getIntProperty(element, ".layout.colspan", 1); //$NON-NLS-1$
    }

    public int getRowSpan(AbstractBaseIntroElement element) {
        return getIntProperty(element, ".layout.rowspan", 1); //$NON-NLS-1$
    }

    private int getIntProperty(AbstractBaseIntroElement element,
            String qualifier, int defaultValue) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
        if (buff == null)
            return defaultValue;
        String key = buff.append(qualifier).toString();
        return getIntProperty(key, defaultValue);
    }

    private boolean getBooleanProperty(AbstractBaseIntroElement element,
            String qualifier, boolean defaultValue) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
        if (buff == null)
            return defaultValue;
        String key = buff.append(qualifier).toString();
        return getBooleanProperty(key, defaultValue);
    }

    private int getIntProperty(String key, int defaulValue) {
        int intValue = defaulValue;
        String value = getProperty(key);
        if (value == null)
            return intValue;

        try {
            intValue = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            Log.error("Failed to parse key: " + key + " as an integer.", e); //$NON-NLS-1$ //$NON-NLS-2$
        }
        return intValue;
    }

    private boolean getBooleanProperty(String key, boolean defaultValue) {
        boolean booleanValue = defaultValue;
        String value = getProperty(key);
        if (value != null)
        	booleanValue = value.equalsIgnoreCase("true"); //$NON-NLS-1$
        return booleanValue;
    }


    /**
     * Finds the description text of the given group. Looks for the Text child
     * element whos id is specified as follows:
     * <p>
     * <pageId>. <path_to_group>.description-id= <id of child description Text
     * element>
     * </p>
     * If not found, use the default description style.
     *
     * Returns null if no default style found, or any id in path is null.
     *
     * @param group
     * @return
     */
    public String getDescription(IntroGroup group) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(group, true);
        if (buff == null)
            return null;
        String key = buff.append(".description-id").toString(); //$NON-NLS-1$
        return doGetDescription(group, key);
    }


    /**
     * Finds the description text of the associated page. Looks for the Text
     * child element whos id is specified as follows:
     * <p>
     * <pageId>.description-id= <id of child description Text element>
     * </p>
     * If not found, use the default description style.
     *
     * Returns null if no default style found, or any id in path is null.
     *
     * @param group
     * @return
     */
    public String getPageDescription() {
        if (page.getId() == null)
            return null;
        String key = page.getId() + ".description-id"; //$NON-NLS-1$
        return doGetDescription(page, key);
    }

    private String doGetDescription(AbstractIntroContainer parent, String key) {
        String path = getProperty(key);
        String description = null;
        if (path != null)
            description = findTextFromPath(parent, path);
        if (description != null)
            return description;
        return findTextFromStyleId(parent, getDescriptionStyleId());
    }

    private String getDescriptionStyleId() {
        String key = "description-style-id"; //$NON-NLS-1$
        return getProperty(key);
    }

    /**
     * Finds the subtitle of the associated page. Looks for the Text child
     * element whose id is specified as follows:
     * <p>
     * <pageId>.description-id= <id of child description Text element>
     * </p>
     * If not found, use the default description style.
     *
     * @param group
     * @return
     */
    public String getPageSubTitle() {
        String key = page.getId() + ".subtitle-id"; //$NON-NLS-1$
        String path = getProperty(key);
        String description = null;
        if (path != null)
            description = findTextFromPath(page, path);
        if (description != null)
            return description;
        return findTextFromStyleId(page, getPageSubTitleStyleId());
    }

    private String getPageSubTitleStyleId() {
        String key = "subtitle-style-id"; //$NON-NLS-1$
        return getProperty(key);
    }

    private String findTextFromPath(AbstractIntroContainer parent, String path) {
        AbstractIntroElement child = parent.findTarget(root, path);
        if (child != null && child.isOfType(AbstractIntroElement.TEXT)) {
            makeFiltered(child);
            return ((IntroText) child).getText();
        }
        return null;
    }

    /**
     * Returns the first direct child text element with the given style-id.
     *
     * @return
     */
    private String findTextFromStyleId(AbstractIntroContainer parent,
            String styleId) {
        IntroText[] allText = (IntroText[]) parent
            .getChildrenOfType(AbstractIntroElement.TEXT);
        for (int i = 0; i < allText.length; i++) {
            if (allText[i].getStyleId() == null)
                // not all elements have style id.
                continue;
            if (allText[i].getStyleId().equals(styleId)) {
                makeFiltered(allText[i]);
                return allText[i].getText();
            }
        }
        return null;
    }

    /**
     * Util method to check model type, and filter model element out if it is of
     * the correct type.
     *
     * @param element
     */
    private AbstractIntroElement makeFiltered(AbstractIntroElement element) {
        if (element.isOfType(AbstractIntroElement.BASE_ELEMENT))
            ((AbstractBaseIntroElement) element).setFilterState(true);
        return element;
    }



    public boolean getShowLinkDescription() {
        String key = page.getId() + ".show-link-description"; //$NON-NLS-1$
        String value = getProperty(key);
        if (value == null) {
            key = ".show-link-description"; //$NON-NLS-1$
            value = getProperty(key);
        }
        if (value == null)
            value = "true"; //$NON-NLS-1$
        return value.toLowerCase().equals("true"); //$NON-NLS-1$
    }

    public boolean showHomePageNavigation() {
        String key = page.getId() + ".show-home-page-navigation"; //$NON-NLS-1$
        String value = getProperty(key);
        if (value == null) {
            key = ".show-home-page-navigation"; //$NON-NLS-1$
            value = getProperty(key);
        }
        if (value == null)
            value = "true"; //$NON-NLS-1$
        return value.equalsIgnoreCase("true"); //$NON-NLS-1$
    }


    public Color getColor(FormToolkit toolkit, AbstractBaseIntroElement element) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
        if (buff == null)
            return null;
        String key = buff.append(".font.fg").toString(); //$NON-NLS-1$
        return getColor(toolkit, key);
    }

    public Color getBackgrond(FormToolkit toolkit, AbstractBaseIntroElement element) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
        if (buff == null)
            return null;
        String key = buff.append(".bg").toString(); //$NON-NLS-1$
        return getColor(toolkit, key);
    }

    public boolean isBold(IntroText text) {
        String value = null;
        /*
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(text, true);
        if (buff != null) {
            String key = buff.append(".font.bold").toString(); //$NON-NLS-1$
            value = getProperty(key);
            if (value != null)
                return value.toLowerCase().equals("true"); //$NON-NLS-1$
            else {
                buff = ModelLoaderUtil.createPathToElementKey(text, true);
            }
        }
        */
        value = getPropertyValue(text, ".font.bold"); //$NON-NLS-1$
        if (value == null) {
            // bold is not specified by ID. Check to see if there is a style-id
            // specified for bold.
            value = getProperty("bold-style-id"); //$NON-NLS-1$
            if (value != null && text.getStyleId() != null)
                return text.getStyleId().equals(value);
        }
        return false;
    }

    private String getPropertyValue(AbstractIntroIdElement element, String suffix) {
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(element, true);
        if (buff != null) {
        	String key = buff.append(suffix).toString();
        	String value = getProperty(key);
        	if (value != null)
        		return value;
        	// try the page.id key
        	buff = ModelLoaderUtil.createPathToElementKey(element, false);
        	if (buff!= null) {
        		key = buff.append(suffix).toString();
        		value = getProperty(key);
        		return value;
        	}
        }
        return null;
    }

    public static Font getBannerFont() {
        return JFaceResources.getBannerFont();
    }

    public static Font getHeaderFont() {
        return JFaceResources.getHeaderFont();
    }

    /**
     * Retrieves an image for a link in a page. If not found, uses the page's
     * default link image. If still not found, uses the passed default.
     *
     * @param link
     * @param qualifier
     * @return
     */
    public Image getImage(IntroLink link, String qualifier, String defaultKey) {
    	// try the Id first
    	String key = createImageByIdKey(page, link, qualifier);
    	String value = getProperty(key, false);
    	if (value==null) {
    		key = createImageKey(page, link, qualifier);
    		// special case where we have to handle this because extended code does
    		// not go through getProperty() in this method.
    		value = getProperty(key, false);
    	}
        if (value == null && page.getId() != null
                && key.startsWith(page.getId()))
            // did not use the key as-is. Trim pageId and try again.
            key = key.substring(page.getId().length());

        // pageKey can not become an implicit key.
        String pageKey = createImageKey(page, null, qualifier);

        return getImage(key, pageKey, defaultKey);
    }

    private String createImageKey(AbstractIntroPage page, IntroLink link,
            String qualifier) {
        StringBuffer buff = null;
        if (link != null) {
            buff = ModelLoaderUtil.createPathToElementKey(link, true);
            if (buff == null)
                return ""; //$NON-NLS-1$
        } else {
            buff = new StringBuffer();
            buff.append(page.getId());
        }
        buff.append("."); //$NON-NLS-1$
        buff.append(qualifier);
        return buff.toString();
    }

    private String createImageByIdKey(AbstractIntroPage page, IntroLink link,
            String qualifier) {
    	if (link==null || link.getId()==null)
    		return ""; //$NON-NLS-1$
        StringBuilder buff = new StringBuilder();
        buff.append(page.getId());
        buff.append("."); //$NON-NLS-1$
        buff.append(link.getId());
        buff.append("."); //$NON-NLS-1$
        buff.append(qualifier);
        return buff.toString();
    }

    public Image getImage(IntroImage introImage) {
        String imageLocation = introImage.getSrcAsIs();
        StringBuffer buff = ModelLoaderUtil.createPathToElementKey(introImage, true);
        String key;
        if (buff == null) {
        	key = "//" + imageLocation; //$NON-NLS-1$
        } else {
        	key = buff!=null?buff.toString():null;
        }
        if (ImageUtil.hasImage(key))
            return ImageUtil.getImage(key);
        // key not already registered.
        if (buff != null) {
        	StyleContext acontext = getAssociatedContext(key);
            if (acontext.inTheme) {
               	ImageUtil.registerImage(key, acontext.path, imageLocation);
                return ImageUtil.getImage(key);
            }
        }
        Bundle bundle = introImage.getBundle();
		if (FileLocator.find(bundle, new Path(imageLocation), null) == null) {
			return null;
		}
        ImageUtil.registerImage(key, bundle, imageLocation);
        return ImageUtil.getImage(key);

    }


}
