/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.webui.core;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;

import org.exoplatform.commons.serialization.api.annotations.Serialized;
import org.exoplatform.commons.utils.HTMLEntityEncoder;
import org.exoplatform.util.ReflectionUtil;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.exoplatform.webui.form.UIForm;

/**
 * Author : Nhu Dinh Thuan nhudinhthuan@exoplatform.com Jul 7, 2006
 *
 * A component that represents a tree. Can contain a UIRightClickPopupMenu
 *
 * @see UIRightClickPopupMenu
 */
@ComponentConfig(template = "system:/groovy/webui/core/UITree.gtmpl", events = @EventConfig(listeners = UITree.ChangeNodeActionListener.class, csrfCheck = false))
@Serialized
public class UITree extends UIComponent {
    /**
     * The css class name to show the expand icon
     */
    private String expandIcon = "expandIcon";

    /**
     * The css class name to show the collapse icon
     */
    private String colapseIcon = "collapseIcon";

    /**
     * The css class name to show the null icon (item has no child)
     */
    private String nullItemIcon = "uiIconEmpty";

    /**
     * The css class name to show the selected icon
     */
    private String selectedIcon = "";

    private String icon = "";

    /**
     * The bean field that holds the id of the bean
     */
    private String beanIdField_;

    /**
     * The bean field that holds the count number of the children This help to express the node have childs or not
     */
    private String beanChildCountField_;

    /**
     * The bean field that holds the label of the bean
     */
    private String beanLabelField_;

    /**
     * The bean field that holds the icon of the bean
     */
    private String beanIconField_ = "";

    /**
     * The field that holds max character's title of node
     */
    private Integer maxTitleCharacter_ = 0;

    /**
     * A list of sibling nodes
     */
    private Collection<?> sibbling;

    /**
     * A list of children nodes
     */
    private Collection<?> children;

    /**
     * The selected node
     */
    private Object selected;

    /**
     * The parent node of the selected node
     */
    private Object parentSelected;

    /**
     * A right click popup menu
     */
    private UIRightClickPopupMenu uiPopupMenu_;

    /**
     * Encode the value before rendering or not
     */
    private boolean escapeHTML_ = false;

    public Object getFieldValue(Object bean, String field) throws Exception {
        Method method = ReflectionUtil.getGetBindingMethod(bean, field);
        return method.invoke(bean, ReflectionUtil.EMPTY_ARGS);
    }

    public void setBeanIdField(String beanIdField_) {
        this.beanIdField_ = beanIdField_;
    }

    public void setBeanIconField(String beanIconField_) {
        this.beanIconField_ = beanIconField_;
    }

    public void setBeanLabelField(String beanLabelField_) {
        this.beanLabelField_ = beanLabelField_;
    }

    public void setBeanChildCountField(String beanChildCountField) {
        this.beanChildCountField_ = beanChildCountField;
    }

    public Object getId(Object object) throws Exception {
        return getFieldValue(object, beanIdField_);
    }

    public String getActionLink() throws Exception {
        if (selected == null)
            return "#";
        if (parentSelected == null)
            return event("ChangeNode");
        return event("ChangeNode", (String) getId(parentSelected));
    }

    // TODO review equals object with id
    public boolean isSelected(Object obj) throws Exception {
        if (selected == null)
            return false;
        return getId(obj).equals(getId(selected));
    }

    public String getColapseIcon() {
        return colapseIcon;
    }

    public void setCollapseIcon(String colapseIcon) {
        this.colapseIcon = colapseIcon;
    }

    public String getExpandIcon() {
        return expandIcon;
    }

    public void setExpandIcon(String expandIcon) {
        this.expandIcon = expandIcon;
    }

    public String getIcon() {
        return icon;
    }

    public void setIcon(String icon) {
        this.icon = icon;
    }

    public String getSelectedIcon() {
        return selectedIcon;
    }

    public void setSelectedIcon(String selectedIcon) {
        this.selectedIcon = selectedIcon;
    }

    public Collection<?> getChildren() {
        return children;
    }

    public void setChildren(Collection<?> children) {
        this.children = children;
    }

    @SuppressWarnings("unchecked")
    public <T> T getSelected() {
        return (T) selected;
    }

    public void setSelected(Object selectedObject) {
        this.selected = selectedObject;
    }

    @SuppressWarnings("unchecked")
    public <T> T getParentSelected() {
        return (T) parentSelected;
    }

    public void setParentSelected(Object parentSelected) {
        this.parentSelected = parentSelected;
    }

    public Collection<?> getSibbling() {
        return sibbling;
    }

    public void setSibbling(Collection<?> sibbling) {
        this.sibbling = sibbling;
    }

    public UIRightClickPopupMenu getUIRightClickPopupMenu() {
        return uiPopupMenu_;
    }

    public void setUIRightClickPopupMenu(UIRightClickPopupMenu uiPopupMenu) {
        uiPopupMenu_ = uiPopupMenu;
        if (uiPopupMenu_ != null)
            uiPopupMenu_.setParent(this);
    }

    public void setEscapeHTML(boolean escape) {
        escapeHTML_ = escape;
    }

    public boolean getEscapeHTML() {
        return escapeHTML_;
    }

    public String event(String name, String beanId) throws Exception {
        UIForm uiForm = getAncestorOfType(UIForm.class);
        if (uiForm != null)
            return uiForm.event(name, getId(), beanId);
        return super.event(name, beanId);
    }

    public String renderNode(Object obj) throws Exception {
        String nodeIcon =colapseIcon;
        String iconGroup = icon;

        String note = "";
        if (isSelected(obj)) {
            nodeIcon = expandIcon;
            iconGroup = selectedIcon;
            note = " nodeSelected";
        }

        if (getBeanChildCountField() != null) {
            Object childCount = getFieldValue(obj, getBeanChildCountField());
            if (childCount != null && childCount.getClass().isAssignableFrom(Integer.class) && (Integer) childCount == 0) {
                nodeIcon = nullItemIcon;
            }
        }

        //hard code this one because we can't change code on exoadmin webapp
        if ("DefaultPageIcon".equals(iconGroup)) {
            iconGroup = "uiIconFileMini uiIconLightGray";
        } else if ("GroupAdminIcon".equals(iconGroup) || "PortalIcon".equals(iconGroup)) {
            iconGroup = "uiIconGroup uiIconLightGray";
        }

        if (beanIconField_ != null && beanIconField_.length() > 0) {
            if (getFieldValue(obj, beanIconField_) != null)
                iconGroup = (String) getFieldValue(obj, beanIconField_);
        }
        String objId = String.valueOf(getId(obj));
        String actionLink = event("ChangeNode", objId);
        String fieldValue = (String) getFieldValue(obj, beanLabelField_);
        StringBuilder builder = new StringBuilder();

        // if field's length > max field's length then cut field value
        if ((fieldValue.length() > getMaxTitleCharacter()) && (getMaxTitleCharacter() != 0)) {
            fieldValue = fieldValue.substring(0, getMaxTitleCharacter() - 3) + "...";
        }

        if (escapeHTML_) {
            fieldValue = fieldValue != null ? HTMLEntityEncoder.getInstance().encode(fieldValue) : fieldValue;
        }
        CharSequence rightClick = "";
        if (uiPopupMenu_ != null) {
            rightClick = uiPopupMenu_.getJSOnclickShowPopup(objId, null);
        }
        if (nodeIcon.equals(expandIcon)) {
            builder.append(" <a href=\"javascript:void(0);\" class=\"uiIconNode ").append(nodeIcon).append(note).append("\" ").append(rightClick)
            .append(" title=\"").append(fieldValue).append("\"").append(">");
        } else {
            builder.append(" <a href=\"javascript:void(0);\" class=\"uiIconNode ").append(nodeIcon).append(note).append("\" ").append(rightClick)
            .append(" title=\"").append(fieldValue).append("\" onclick=\"").append(actionLink).append("\">");
        }
        builder.append("<i class=\"" + iconGroup + "\"></i>").append(fieldValue);
        builder.append(" </a>");
        return builder.toString();
    }

    public void renderUIComponent(UIComponent uicomponent) throws Exception {
        uicomponent.processRender((WebuiRequestContext) WebuiRequestContext.getCurrentInstance());
    }

    @SuppressWarnings("unchecked")
    public <T extends UIComponent> T findComponentById(String id) {
        if (getId().equals(id))
            return (T) this;
        if (uiPopupMenu_ == null)
            return null;
        return (T) uiPopupMenu_.findComponentById(id);
    }

    public <T extends UIComponent> T findFirstComponentOfType(Class<T> type) {
        if (type.isInstance(this))
            return type.cast(this);
        if (uiPopupMenu_ == null)
            return null;
        return uiPopupMenu_.findFirstComponentOfType(type);
    }

    public <T> void findComponentOfType(List<T> list, Class<T> type) {
        if (type.isInstance(this))
            list.add(type.cast(this));
        if (uiPopupMenu_ == null)
            return;
        uiPopupMenu_.findComponentOfType(list, type);
    }

    public static class ChangeNodeActionListener extends EventListener<UITree> {
        public void execute(Event<UITree> event) throws Exception {
            event.getSource().<UIComponent> getParent().broadcast(event, event.getExecutionPhase());
        }
    }

    public String getBeanIdField() {
        return beanIdField_;
    }

    public String getBeanChildCountField() {
        return beanChildCountField_;
    }

    public String getBeanLabelField() {
        return beanLabelField_;
    }

    public String getBeanIconField() {
        return beanIconField_;
    }

    public void setMaxTitleCharacter(Integer maxTitleCharacter_) {
        this.maxTitleCharacter_ = maxTitleCharacter_;
    }

    public Integer getMaxTitleCharacter() {
        return maxTitleCharacter_;
    }

    public UIRightClickPopupMenu getUiPopupMenu() {
        return uiPopupMenu_;
    }

    public void setUiPopupMenu(UIRightClickPopupMenu uiPopupMenu) {
        this.uiPopupMenu_ = uiPopupMenu;
    }

    public void setColapseIcon(String colapseIcon) {
        this.colapseIcon = colapseIcon;
    }
}
