/*****************************************************************************
 * Copyright (c) 2015, 2020 CEA LIST.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *		Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
 *
 *****************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.e4.css;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.e4.ui.css.core.engine.CSSEngine;
import org.eclipse.e4.ui.css.core.impl.engine.AbstractCSSEngine;
import org.eclipse.e4.ui.css.swt.dom.WidgetElement;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@SuppressWarnings("restriction")
public class NatTableElementAdapter extends WidgetElement {

    private static final Logger LOG = LoggerFactory.getLogger(NatTableElementAdapter.class);

    /**
     * Static flag to ensure that the update listener is only applied once to
     * the display.
     */
    private static AtomicBoolean listenerApplied = new AtomicBoolean(false);

    /**
     * Collection of virtual children that can be used as child selectors in CSS
     * that will be mapped to NatTable labels.
     */
    ArrayList<Node> virtualChildren = new ArrayList<>();

    public NatTableElementAdapter(final NatTable natTable, final CSSEngine engine) {
        super(natTable, engine);

        addStaticPseudoInstance("normal");
        addStaticPseudoInstance("select");
        addStaticPseudoInstance("edit");
        addStaticPseudoInstance("hover");
        addStaticPseudoInstance("select-hover");

        // add virtual children for all labels that can be applied in the given
        // NatTable instance
        for (String label : natTable.getProvidedLabels()) {
            addVirtualChild(label);
        }

        natTable.addDisposeListener(e -> dispose());

        // the special NatTable related update listener should only be
        // registered once, there is no need to add additional listeners for
        // additional NatTable instances
        if (listenerApplied.compareAndSet(false, true)) {
            new NatTableSkinListener(natTable.getDisplay(), engine);
        }
    }

    @Override
    public Node getParentNode() {
        Control control = getControl();
        Composite parent = control.getParent();
        if (parent != null) {
            return getElement(parent);
        }
        return null;
    }

    protected NatTable getControl() {
        return (NatTable) getNativeWidget();
    }

    @Override
    public NodeList getChildNodes() {
        // only need to return a non-null value
        // strange implementation
        return this;
    }

    @Override
    public int getLength() {
        return this.virtualChildren.size();
    }

    @Override
    public Node item(int index) {
        return this.virtualChildren.get(index);
    }

    /**
     * Add a virtual child to the {@link NatTableElementAdapter}. This way the
     * given label can be used as child selector in the CSS file.
     *
     * @param label
     *            The label that should be usable as child selector.
     */
    public void addVirtualChild(String label) {
        this.virtualChildren.add(
                new NatTableWrapperElementAdapter(
                        new NatTableWrapper(getControl(), label), this.engine, this));
    }

    /**
     * @since 2.0
     */
    @Override
    public void dispose() {
        super.dispose();
        for (Node node : this.virtualChildren) {
            if (node instanceof NatTableWrapperElementAdapter) {
                ((NatTableWrapperElementAdapter) node).dispose();

                if (this.engine instanceof AbstractCSSEngine) {
                    try {
                        Method method = AbstractCSSEngine.class.getDeclaredMethod("handleWidgetDisposed", Object.class);
                        if (method != null) {
                            method.setAccessible(true);
                            method.invoke(this.engine, ((NatTableWrapperElementAdapter) node).natTableWrapper);
                        }
                    } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                        LOG.error("Error on disposal", e);
                    }
                }
            }
        }
    }

    /**
     * Add a listener for {@link SWT#Skin} to apply styles to a NatTable.
     * <p>
     * The listener added via CSSSWTApplyStylesListener does not apply styles
     * for the children. But as NatTable styling is done via virtual children
     * for the labels, it is important to apply the styles also to the children
     * on skinning.
     * </p>
     * <p>
     * Extracted to a separate class instead of an anonymous inner class, to
     * avoid memory leakage.
     * </p>
     */
    private static class NatTableSkinListener {

        CSSEngine engine;

        public NatTableSkinListener(Display display, final CSSEngine cssEngine) {
            this.engine = cssEngine;

            display.addListener(SWT.Skin, event -> {
                if (NatTableSkinListener.this.engine != null && event.widget instanceof NatTable) {
                    NatTableSkinListener.this.engine.applyStyles(event.widget, true);
                }
            });
        }

    }
}
