/*
 * Copyright 1999,2004 The Apache Software Foundation.
 * 
 * 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.apache.roller.weblogger.ui.tags;

import javax.servlet.jsp.JspException;
import org.apache.commons.lang3.math.NumberUtils;

/**
 * Code grabbed from retired Jakarta Tablibs project: http://jakarta.apache.org/taglibs/string/
 *
 * A more intelligent substring.  It attempts to cut off a string after
 * a space, following predefined or user-supplied lower and upper limits,
 * useful for making short descriptions from long text.  Can also strip
 * HTML, or if not, intelligently close any tags that were left open.
 * It adds on a user-defined ending.
 *
 * <dl>
 * <dt>lower</dt><dd>
 *             Minimum length to truncate at.
 *             Required.
 * </dd>
 * <dt>upper</dt><dd>
 *             Maximum length to truncate at.
 *             Required.
 * </dd>
 * <dt>upper</dt><dd>
 *             String to append to end of truncated string.
 * </dd>
 * </dl>
 *
 * @author timster@mac.com
 */
public class TruncateNicelyTag extends StringTagSupport {

    private String lower;
    private String upper;
    private String appendToEnd;

    public TruncateNicelyTag() {
        super();
    }

    /**
     * Get the lower property
     *
     * @return String lower property
     */
    public String getLower() {
        return this.lower;
    }

    /**
     * Set the upper property
     *
     * @param lower String property
     */
    public void setLower(String lower) {
        this.lower = lower;
    }

    /**
     * Get the upper property
     *
     * @return String upper property
     */
    public String getUpper() {
        return this.upper;
    }

    /**
     * Set the upper property
     *
     * @param upper String property
     */
    public void setUpper(String upper) {
        this.upper = upper;
    }

    public String getAppendToEnd() {
        return this.appendToEnd;
    }

    public void setAppendToEnd(String s) {
        this.appendToEnd = s;
    }

    @Override
    public String changeString(String text) throws JspException {

        int l = NumberUtils.toInt(lower);
        int u = NumberUtils.toInt(upper);

        return truncateNicely(text, l, u, this.appendToEnd);
    }

    @Override
    public void initAttributes() {

        this.lower = "10";
        this.upper = "-1";
        this.appendToEnd = "...";

    }

    /**
     * Truncates a string nicely. It will search for the first space
     * after the lower limit and truncate the string there.  It will
     * also append any string passed as a parameter to the end of the
     * string.  The hard limit can be specified to forcibily truncate a
     * string (in the case of an extremely long word or such).  All
     * HTML/XML markup will be stripped from the string prior to
     * processing for truncation.
     *
     * @param str         String the string to be truncated.
     * @param lower       int value of the lower limit.
     * @param upper       int value of the upper limit, -1 if no limit is
     *                    desired. If the uppper limit is lower than the
     *                    lower limit, it will be adjusted to be same as
     *                    the lower limit.
     * @param appendToEnd String to be appended to the end of the
     *                    truncated string.
     *                    This is appended ONLY if the string was indeed
     *                    truncated. The append is does not count towards
     *                    any lower/upper limits.
     */
    private static String truncateNicely(String str, int lower, int upper, String appendToEnd)
    {
        // strip markup from the string
        str = removeXml(str);

        // quickly adjust the upper if it is set lower than 'lower'
        if(upper < lower) {
            upper = lower;
        }

        // now determine if the string fits within the upper limit
        // if it does, go straight to return, do not pass 'go' and collect $200
        if(str.length() > upper) {
            // the magic location int
            int loc;

            // first we determine where the next space appears after lower
            loc = str.lastIndexOf(' ', upper);

            // now we'll see if the location is greater than the lower limit
            if(loc >= lower) {
                // yes it was, so we'll cut it off here
                str = str.substring(0, loc);
            } else {
                // no it wasn't, so we'll cut it off at the upper limit
                str = str.substring(0, upper);
            }

            // the string was truncated, so we append the appendToEnd String
            str = str + appendToEnd;
        }

        return str;
    }

    /**
     * Remove any xml tags from a String.
     */
    private static String removeXml(String str) {
        int sz = str.length();
        StringBuilder buffer = new StringBuilder(sz);
        boolean inTag = false;
        for (int i=0; i<sz; i++) {
            char ch = str.charAt(i);
            if (ch == '<') {
                inTag = true;
            } else
            if (ch == '>') {
                inTag = false;
                continue;
            }
            if (!inTag) {
                buffer.append(ch);
            }
        }
        return buffer.toString();
    }

}
