/*
 * Copyright (C) 2004-2008 Jive Software, 2022 Ignite Realtime Foundation. All rights reserved.
 *
 * 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 org.jivesoftware.admin;

import org.dom4j.Element;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTagSupport;
import java.io.IOException;
import java.util.List;

/**
 * <p>A simple JSP tag for displaying tab information in the admin console. The
 * {@link SidebarTag} is similiar to this one.</p>
 *
 * <p>Attributes:</p> <ul>
 *      <li>{@code bean} (required) - the id of the request attribute which is a
 *      {@link AdminPageBean} instance. This class holds information
 *      needed to properly render the admin console tabs.</li>
 *      <li>{@code css} (optional) - the CSS class name used to decorate the LI of the tab.</li>
 *      <li>{@code currentcss} (optional) - the CSS class name used to decorate the LI of the
 *      currently selected tab.</li></ul>
 *
 * <p>This class assumes there is a request attribute with the name specified by the bean attribute.</p>
 *
 * <p>This tag prints out minimal HTML. It basically prints an unordered list (UL element) with each
 * LI containing an "A" tag specfied by the body content of this tag. For example, the body should contain
 * a template A tag which will have its values replaced at runtime:</p> <pre>{@code
 *
 *      &lt;jive:tabs bean="jivepageinfo"&gt; <br>
 *          &nbsp;&nbsp;&nbsp;&lt;a href="[url]" title="[description]"&gt;[name]&lt;/a&gt; <br>
 *      &lt;/jive:tabs&gt;}</pre>
 *
 * Available token are: <ul>
 *      <li>{@code [id]} - the ID of the tab, usually not needed.</li>
 *      <li>{@code [name]} - the name of the tab, should be thought of as the display name.</li>
 *      <li>{@code [url]} - the URL of the tab.</li>
 *      <li>{@code [description]} - the description of the tab, good for mouse rollovers.</li></ul>
 */
public class TabsTag extends BodyTagSupport {

    private String bean;
    private String css;
    private String currentcss;
    private Boolean justlinks = false;

    /**
     * The name of the request attribute which holds a {@link AdminPageBean} instance.
     * @return the name of the bean
     */
    public String getBean() {
        return bean;
    }

    /**
     * Sets the name of the request attribute to hold a {@link AdminPageBean} instance.
     * @param bean the name of the bean
     */
    public void setBean(String bean) {
        this.bean = bean;
    }

    /**
     * Returns the value of the CSS class to be used for tab decoration. If not set will return a blank string.
     * @return the CSS
     */
    public String getCss() {
        return clean(css);
    }

    /**
     * Sets the CSS used for tab decoration.
     * @param css the CSS
     */
    public void setCss(String css) {
        this.css = css;
    }

    /**
     * Returns the value of the CSS class to be used for the currently selected LI (tab). If not set will
     * return a blank string.
     * @return the CSS class
     */
    public String getCurrentcss() {
        return clean(currentcss);
    }

    /**
     * Sets the CSS class value for the currently selected tab.
     * @param currentcss the CSS class
     */
    public void setCurrentcss(String currentcss) {
        this.currentcss = currentcss;
    }

    /**
     * Returns whether we are in just links mode.
     * @return {@code true} if just displaying links, otherwise {@code false}
     */
    public Boolean getJustlinks() {
        return justlinks;
    }

    /**
     * Sets whether we are just to display links, no list.
     * @param justlinks {@code true} to just display links, otherwise {@code false}
     */
    public void setJustlinks(Boolean justlinks) {
        this.justlinks = justlinks;
    }

    /**
     * Does nothing, returns {@link #EVAL_BODY_BUFFERED} always.
     */
    @Override
    public int doStartTag() throws JspException {
        return EVAL_BODY_BUFFERED;
    }

    /**
     * Gets the {@link AdminPageBean} instance from the request. If it doesn't exist then execution is stopped
     * and nothing is printed. If it exists, retrieve values from it and render the tabs. The body content
     * of the tag is assumed to have an A tag in it with tokens to replace (see class description).
     *
     * @return {@link #EVAL_PAGE} after rendering the tabs.
     * @throws JspException if an exception occurs while rendering the tabs.
     */
    @Override
    public int doEndTag() throws JspException {
        HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
        // Get the page data bean from the request:
        // If the page info bean is not in the request then no tab will be selected - so, it'll fail gracefully
        String pageID = (String)request.getAttribute("pageID");
        String subPageID = (String)request.getAttribute("subPageID");

        if (pageID == null) {
            Element subPage = AdminConsole.getElemnetByID(subPageID);
            if (subPage != null) {
                pageID = subPage.getParent().getParent().attributeValue("id");
            }
        }
        
        // Get tabs from the model:
        List tabs = AdminConsole.getModel().selectNodes("//tab");
        if (tabs.size() > 0) {
            JspWriter out = pageContext.getOut();
            // Build up the output in a buffer (is probably faster than a bunch of out.write's)
            StringBuilder buf = new StringBuilder();
            if (!justlinks) { buf.append("<ul>"); }
            String body = getBodyContent().getString();
            // For each tab, print out an <LI>.
            Element currentTab = null;
            if (pageID != null) {
                currentTab = (Element)AdminConsole.getModel().selectSingleNode(
                            "//*[@id='" + pageID + "']/ancestor::tab");
            }
            for (int i=0; i<tabs.size(); i++) {
                Element tab = (Element)tabs.get(i);
                String value = body;
                if (value != null) {
                    // The URL for the tab should be the URL of the first item in the tab.
                    String pluginName = tab.attributeValue("plugin");
                    value = value.replaceAll("\\[id]", clean(tab.attributeValue("id")));
                    value = value.replaceAll("\\[url]",request.getContextPath() + "/" + clean(tab.attributeValue("url")));
                    value = value.replaceAll("\\[name]", clean(AdminConsole.getAdminText(tab.attributeValue("name"), pluginName)));
                    value = value.replaceAll("\\[description]", clean(AdminConsole.getAdminText(tab.attributeValue("description"), pluginName)));
                }
                String css = getCss();
                if (tab.equals(currentTab)) {
                    css = getCurrentcss();
                }
                if (!justlinks) { buf.append("<li class=\"").append(css).append("\">"); }
                if (justlinks && i > 0) { buf.append(" | "); }
                buf.append(value);
                if (!justlinks) { buf.append("</li>"); }
            }

            if (!justlinks) { buf.append("</ul>"); }
            try {
                out.write(buf.toString());
            }
            catch (IOException ioe) {
                throw new JspException(ioe.getMessage());
            }
        }
        return EVAL_PAGE;
    }

    /**
     * Cleans the given string - if it's null, it's converted to a blank string. If it has ' then those are
     * converted to double ' so HTML isn't screwed up.
     *
     * @param in the string to clean
     * @return a cleaned version - not null and with escaped characters.
     */
    private String clean(String in) {
        return (in == null ? "" : in.replaceAll("'", "\\'"));
    }
}
