/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.bebop;

import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.event.PrintListener;
import com.arsdigita.util.Assert;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.xml.Element;

/**
 * A text label. The label can be used to generate either some static,
 * fixed text or a new text string for every request. To set a new
 * text string for each request, use the {@link
 * #setLabel(String,PageState)} method.
 *
 * @author David Lutterkort 
 * @version $Id: //core-platform/dev/src/com/arsdigita/bebop/Label.java#11 $
 */
public class Label extends BlockStylable implements Cloneable {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/bebop/Label.java#11 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final String NO_LABEL = "";

    public static final String BOLD = "b";
    public static final String ITALIC = "i";

    // the default label
    private GlobalizedMessage m_label;

    // a requestlocal set of labels (to avoid printlisteners)
    private RequestLocal m_requestLabel = new RequestLocal();

    private String m_fontWeight;
    private boolean m_escaping;

    private PrintListener m_printListener;

    /**
     * Creates a new <code>Label</code> with empty text.
     */
    public Label() {
        this(NO_LABEL);
    }

    /**
     * Creates a new <code>Label</code> with the specified text.
     *
     * @param label the text to display
     */
    public Label(String label) {
        this(label, true);
    }

    /**
     * Creates a new <code>Label</code> with the specified text and
     * output escaping turned on if <code>escaping</code> is
     * <code>true</code>.
     * The setting foroutput escaping affects how markup in
     * the <code>label</code> is handled. For example:
     * <UL><LI>If output escaping is in effect, &lt;b>text&lt;/b> will
     * appear literally.</LI>
     * <LI>If output escaping is disabled,
     *  &lt;b>text&lt;/b> appears as the word "text" in bold.</LI></UL>
     *
     * @param label the text to display
     * @param <code>true</code> if output escaping will be in effect;
     * <code>false</code> if output escaping
     * will be disabled
     */
    public Label(String label, boolean escaping) {
        setLabel(label);
        setOutputEscaping(escaping);
    }

    /**
     * <p>
     * Creates a new label with the specified text.
     * </p>
     *
     * @param label the text to display
     */
    public Label(GlobalizedMessage label) {
        this(label, true);
    }

    /**
     * <p>
     * Creates a new label with the specified text  and
     * output escaping turned on if <code>escaping</code> is
     * <code>true</code>.
     * </p>
     *
     * @param label the text to display
     * @param escaping Whether or not to perform output escaping
     */
    public Label(GlobalizedMessage label, boolean escaping) {
        setLabel(label);
        setOutputEscaping(escaping);
    }

    /**
     * Creates a new <code>Label</code> that uses the print listener
     * to generate output.
     *
     * @param l the print listener used to produce output
     */
    public Label(PrintListener l) {
        this();
        addPrintListener(l);
    }

    /**
     * Creates a new label with the specified text and fontweight.
     *
     * @param label The text to display
     * @param fontWeight The fontWeight e.g., Label.BOLD
     */
    public Label(String label, String fontWeight) {
        this(label, true);
        m_fontWeight = fontWeight;
    }

    /**
     * .
     *  Although it is not recommended, this method may be overridden
     *  to dynamically generate the text of the label.  Overriding
     *  code may need the page state.  <p>If possible,
     *  derived classes should override {@link #getLabel()} instead,
     *  which is called from this method.  As long as we don't have a
     *  static method to obtain ApplicationContext, this is a way to
     *  get the RequestContext (that is, to determine the locale).  When
     *  ApplicationContext gets available, that will become the
     *  suggested way for overriding code to get context.
     *
     *  @param state the current page state
     *  @return the string produced for this label
     */
    public String getLabel(PageState state) {
        return (String) getGlobalizedMessage(state).localize(state.getRequest());
    }

    /**
     * .
     *
     * This method may be overridden to dynamically generate the default text
     * of the label.
     *
     * @return the string produced for this label.
     *
     * @deprecated Use {@link #getGlobalizedMessage()}
     */
    public String getLabel() {
        return getGlobalizedMessage().getKey();
    }

    /**
     * <p>
     * This should really be getLabel(), but since it was marked STABLE I can't
     * change its return type.
     * </p>
     *
     * @return the default label to display.
     */
    public GlobalizedMessage getGlobalizedMessage() {
        return getGlobalizedMessage(null);
    }

    /**
     * <p>
     * This should really be getLabel(), but since it was marked STABLE I can't
     * change its return type.
     * </p>
     *
     * @param the current PageState
     * @return the label to display for this request, or if state is null,
     * the default label
     */
    public GlobalizedMessage getGlobalizedMessage(PageState state) {
        if (state != null) {
            GlobalizedMessage dynlabel =
                (GlobalizedMessage)m_requestLabel.get(state);
            if (dynlabel != null) {
                return dynlabel;
            }
        }
        return m_label;
    }

    /**
     * Sets new default text for this Label.
     * @param label The new label text; will be used as a key into the
     * current ResourceBundle if possible, or displayed literally.
     */
    public void setLabel(String label) {
        setLabel(label, null);
    }

    /**
     * Sets new request-specific text for this Label to use on this
     * request.  If state is null, then sets the default text instead.
     *
     * @param label The new label text; will be used as a key into the
     * current ResourceBundle if possible, or displayed literally.
     * @param state the page state
     * @pre state == null implies !isLocked()
     */
    public void setLabel(String label, PageState state) {
        if (label == null || label.length() == 0) {
            label = " ";
        }
        setLabel(new GlobalizedMessage(label), state);
    }

    /**
     * Sets the text for this label using a GlobalizedMessage.
     * @param label The GlobalizedMessage containing the label
     * text or the lookup key to use in the ResourceBundle
     * @param state the current page state; if null, sets the
     * default text for all requests.
     * @pre state == null implies !isLocked()
     */
    public void setLabel(GlobalizedMessage label, PageState state) {
        if (state == null) {
            Assert.assertNotLocked(this);
            m_label = label;
        } else {
            m_requestLabel.set(state, label);
        }
    }

    /**
     * Sets the default text for this Label.
     * @param label The GlobalizedMessage containing the label
     * text or the lookup key to use in the ResourceBundle
     */
    public void setLabel(GlobalizedMessage label) {
        setLabel(label, null);
    }

    public final boolean getOutputEscaping() {
        return m_escaping;
    }

    /**
     * Controls whether output is escaped during transformation, by default true.
     * If true, it will be printed literally, and the user will see &lt;b&gt;.
     * When false, the browser will interpret as a bold tag.
     */
    public final void setOutputEscaping(boolean escaping) {
        m_escaping = escaping;
    }

    public final String getFontWeight() {
        return m_fontWeight;
    }

    public void setFontWeight(String fontWeight) {
        Assert.assertNotLocked(this);
        m_fontWeight = fontWeight;
    }

    /**
     * Adds a print listener. Only one print listener can be set for a
     * label, since the <code>PrintListener</code> is expected to modify the
     * target of the <code>PrintEvent</code>.
     * @param listener the print listener
     * @throws IlegalArgumentException if <code>listener</code> is null.
     * @throws IllegalStateException if a print listener has previously been
     *         added.
     * @pre listener != null */
    public void addPrintListener(PrintListener listener)
        throws IllegalStateException, IllegalArgumentException
    {
        if (listener == null) {
            throw new IllegalArgumentException
                ("Argument listener can not be null");
        }
        if (m_printListener != null) {
            throw new IllegalStateException
                ("Too many listeners. Can only have one");
        }
        m_printListener = listener;
    }

    /**
     * Removes a previously added print listener. If <code>listener</code> is
     * not the listener that was added with {@link #addPrintListener
     * addPrintListener}, an IllegalArgumentException will be thrown.
     * @param listener the listener that was added with
     *      <code>addPrintListener</code>
     * @throws IllegalArgumentException if <code>listener</code> is not the
     *      currently registered print listener or is <code>null</code>.
     * @pre listener != null
     */
    public void removePrintListener(PrintListener listener)
        throws IllegalArgumentException
    {
        if (listener == null) {
            throw new IllegalArgumentException("listener can not be null");
        }
        if (listener != m_printListener) {
            throw new IllegalArgumentException
                ("listener is not registered with this widget");
        }
        m_printListener = null;
    }

    public void generateXML(PageState state, Element parent) {

        if (!isVisible(state)) {
            return;
        }

        Label target = firePrintEvent(state);

        Element label = parent.newChildElement("bebop:label", BEBOP_XML_NS);
        target.exportAttributes(label);

        String weight = target.getFontWeight();
        if (weight != null && weight.length() > 0) {
            label.addAttribute("weight", weight);
        }

        if (!target.m_escaping) {
            label.addAttribute("escape", "yes");
        } else {
            label.addAttribute("escape", "no");
        }

        /*
         * This may break with normal JDOM.  We may need to have a node
         * for the case where there is no weight.  The problem comes in that
         * setText *may* kill the other content in the node.  It will kill the
         * other text, so it may be a good idea anyways.
         */
        label.setText(target.getLabel(state));
    }

    protected Label firePrintEvent(PageState state) {
        Label l = this;

        if (m_printListener != null) {
            try {
                l = (Label) this.clone();
                m_printListener.prepare(new PrintEvent(this, state, l));
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException(
                                           "Couldn't clone Label for PrintListener. " +
                                           "This probably indicates a serious programming error: "
                                           + e.getMessage());
            }
        }

        return l;
    }
}
