/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * 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
 * 2.1 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.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.processor.handlers.xhtml;

import org.apache.commons.lang3.StringUtils;
import org.orbeon.oxf.xforms.StaticStateGlobalOps;
import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.analysis.ElementAnalysis;
import org.orbeon.oxf.xforms.analysis.controls.StaticLHHASupport;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.processor.handlers.XFormsControlLifecycleHandlerDelegate;
import org.orbeon.oxf.xml.XMLReceiverHelper;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.XMLUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * This class is a helper base class which handles the lifecycle of producing markup for a control. The following
 * phases are handled:
 *
 * o Give the handler a chance to do some prep work: prepareHandler()
 * o Get custom information: addCustomClasses()
 * o Check whether the control wants any output at all: isMustOutputControl()
 * o Output label, control, hint, help, and alert in order specified by properties
 *
 * Outputting the control is split into two parts: handleControlStart() and handleControlEnd(). In most cases, only
 * handleControlStart() is used, but container controls will use handleControlEnd().
 */
public abstract class XFormsControlLifecyleHandler extends XFormsBaseHandlerXHTML {

	private XFormsControlLifecycleHandlerDelegate xFormsControlLifecycleHandlerDelegate;
	private Attributes attributes;
    
    private String[] endConfig;
    private String containingElementQName;

    protected XFormsControlLifecyleHandler(boolean repeating) {
        super(repeating, false);
    }

    protected XFormsControlLifecyleHandler(boolean repeating, boolean forwarding) {
        super(repeating, forwarding);
    }

    protected String getContainingElementName() {
        // By default, controls are enclosed with a <span>
        return "span";
    }

    protected String getContainingElementQName() {
        if (containingElementQName == null) {
            final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
            containingElementQName = XMLUtils.buildQName(xhtmlPrefix, getContainingElementName());
        }
        return containingElementQName;
    }
    
	protected boolean isTemplate() {
		return xFormsControlLifecycleHandlerDelegate.isTemplate();
	}

	protected String getPrefixedId() {
		return xFormsControlLifecycleHandlerDelegate.prefixedId();
	}

	protected String getEffectiveId() {
		return xFormsControlLifecycleHandlerDelegate.effectiveId();
	}

	protected XFormsControl currentControlOrNull() {
		return xFormsControlLifecycleHandlerDelegate.currentControlOrNull();
	}

    protected scala.Option<XFormsControl> currentControlOpt() {
		return xFormsControlLifecycleHandlerDelegate.currentControlOpt();
	}

	@Override
    public void init(String uri, String localname, String qName, Attributes attributes, Object matched) throws SAXException {
        super.init(uri, localname, qName, attributes, matched);
        xFormsControlLifecycleHandlerDelegate = new XFormsControlLifecycleHandlerDelegate(handlerContext, containingDocument, attributes);
    }

    @Override
    public final void start(String uri, String localname, String qName, Attributes attributes) throws SAXException {
        if (isMustOutputControl(currentControlOrNull())) {

            final ContentHandler contentHandler = handlerContext.getController().getOutput();
            if (isMustOutputContainerElement()) {
                // Open control element, usually <span>
                final AttributesImpl containerAttributes = getContainerAttributes(uri, localname, attributes);
                contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, getContainingElementName(), getContainingElementQName(), containerAttributes);
            }

            // Get local order for control
            final String localOrder = attributes.getValue(XFormsConstants.XXFORMS_ORDER_QNAME.getNamespaceURI(),
                    XFormsConstants.XXFORMS_ORDER_QNAME.getName());

            // Use local or default config
            final String[] config = (localOrder != null) ? StringUtils.split(localOrder) : handlerContext.getDocumentOrder();

            // 2012-12-17: Removed nested <a name="effective-id"> because the enclosing <span> for the control has the
            // same id and will be handled first by the browser as per HTML 5. This means the named anchor is actually
            // redundant.

            // Process everything up to and including the control
            for (int i = 0; i < config.length; i++) {
                final String current = config[i];

                if ("control".equals(current)) {
                    // Handle control
                    handleControlStart(uri, localname, qName, attributes, getEffectiveId(), currentControlOrNull());
                    // Do the rest in end() below if needed
                    if (i < config.length - 1) {
                        // There remains stuff to process
                        final int endConfigLength = config.length - i;
                        endConfig = new String[endConfigLength];
                        System.arraycopy(config, i, endConfig, 0, endConfigLength);
                        this.attributes = new AttributesImpl(attributes);
                    }
                    break;
                } else if ("label".equals(current)) {
                    // xf:label
                    if (hasLocalLabel())
                        handleLabel();
                } else if ("alert".equals(current)) {
                    // xf:alert
                    if (hasLocalAlert())
                        handleAlert();
                } else if ("hint".equals(current)) {
                    // xf:hint
                    if (hasLocalHint())
                        handleHint();
                } else {
                    // xf:help
                    if (hasLocalHelp())
                        handleHelp();
                }
            }
        }
    }

    @Override
    public final void end(String uri, String localname, String qName) throws SAXException {
        if (isMustOutputControl(currentControlOrNull())) {
            // Process everything after the control has been shown
            if (endConfig != null) {

                for (final String current : endConfig) {
                    if ("control".equals(current)) {
                        // Handle control
                        handleControlEnd(uri, localname, qName, attributes, getEffectiveId(), currentControlOrNull());
                    } else if ("label".equals(current)) {
                        // xf:label
                        if (hasLocalLabel())
                            handleLabel();
                    } else if ("alert".equals(current)) {
                        // xf:alert
                        if (hasLocalAlert())
                            handleAlert();
                    } else if ("hint".equals(current)) {
                        // xf:hint
                        if (hasLocalHint())
                            handleHint();
                    } else {
                        // xf:help
                        if (hasLocalHelp())
                            handleHelp();
                    }
                }
            }

            if (isMustOutputContainerElement()) {
                // Close control element, usually <span>
                final ContentHandler contentHandler = handlerContext.getController().getOutput();
                contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, getContainingElementName(), getContainingElementQName());
            }
        }
    }

    private boolean hasLocalLabel() {
        return hasLocalLHHA("label");
    }

    private boolean hasLocalHint() {
        return hasLocalLHHA("hint");
    }

    private boolean hasLocalHelp() {
        return hasLocalLHHA("help");
    }

    private boolean hasLocalAlert() {
        return hasLocalLHHA("alert");
    }

    private boolean hasLocalLHHA(String lhhaType) {
        final StaticStateGlobalOps globalOps = containingDocument.getStaticOps();
        final ElementAnalysis analysis = globalOps.getControlAnalysis(getPrefixedId());
        if (analysis instanceof StaticLHHASupport)
            return ((StaticLHHASupport) analysis).hasLocal(lhhaType);
        else
            return false;
    }

    protected boolean isMustOutputControl(XFormsControl control) {
        // May be overridden by subclasses
        return true;
    }

    protected boolean isMustOutputContainerElement() {
        // May be overridden by subclasses
        return true;
    }

    protected void addCustomClasses(StringBuilder classes, XFormsControl control) {
        // May be overridden by subclasses
    }

    protected boolean isDefaultIncremental() {
        // May be overridden by subclasses
        return false;
    }

    protected void handleLabel() throws SAXException {
        // May be overridden by subclasses
        handleLabelHintHelpAlert(getStaticLHHA(getPrefixedId(), LHHAC.LABEL), getEffectiveId(), getForEffectiveId(getEffectiveId()), LHHAC.LABEL, currentControlOrNull(), isTemplate(), false);
    }

    protected void handleAlert() throws SAXException {
        // May be overridden by subclasses
        handleLabelHintHelpAlert(getStaticLHHA(getPrefixedId(), LHHAC.ALERT), getEffectiveId(), getForEffectiveId(getEffectiveId()), LHHAC.ALERT, currentControlOrNull(), isTemplate(), false);
    }

    protected void handleHint() throws SAXException {
        // May be overridden by subclasses
        handleLabelHintHelpAlert(getStaticLHHA(getPrefixedId(), LHHAC.HINT), getEffectiveId(), getForEffectiveId(getEffectiveId()), LHHAC.HINT, currentControlOrNull(), isTemplate(), false);
    }

    protected void handleHelp() throws SAXException {
        // May be overridden by subclasses
        handleLabelHintHelpAlert(getStaticLHHA(getPrefixedId(), LHHAC.HELP), getEffectiveId(), getForEffectiveId(getEffectiveId()), LHHAC.HELP, currentControlOrNull(), isTemplate(), false);
    }

    // Must be overridden by subclasses
    protected abstract void handleControlStart(String uri, String localname, String qName, Attributes attributes, String effectiveId, XFormsControl control) throws SAXException;

    protected void handleControlEnd(String uri, String localname, String qName, Attributes attributes, String effectiveId, XFormsControl control) throws SAXException {
        // May be overridden by subclasses
    }

    protected AttributesImpl getEmptyNestedControlAttributesMaybeWithId(String uri, String localname, Attributes attributes, String effectiveId, XFormsControl control, boolean addId) {
        reusableAttributes.clear();
        final AttributesImpl containerAttributes = reusableAttributes;
        if (addId)
            containerAttributes.addAttribute("", "id", "id", XMLReceiverHelper.CDATA, getLHHACId(containingDocument, effectiveId, LHHAC_CODES.get(LHHAC.CONTROL)));

        return containerAttributes;
    }

    private AttributesImpl getContainerAttributes(String uri, String localname, Attributes attributes) {

        // NOTE: Only reason we do not use the class members directly is to handle boolean xf:input, which delegates
        // its output to xf:select1. Should be improved some day.

        final String prefixedId = getPrefixedId();
        final String effectiveId = getEffectiveId();
        final XFormsControl xformsControl = currentControlOrNull();

        // Get classes
        final StringBuilder classes;
        {
            // Initial classes: xforms-control, xforms-[control name], incremental, appearance, mediatype, xforms-static
            classes = getInitialClasses(uri, localname, attributes, xformsControl, isDefaultIncremental());
            // All MIP-related classes
            handleMIPClasses(classes, prefixedId, xformsControl);
            // Static classes
            containingDocument.getStaticOps().appendClasses(classes, prefixedId);
            // Dynamic classes added by the control
            addCustomClasses(classes, xformsControl);
        }

        // Get attributes
        final AttributesImpl newAttributes = getIdClassXHTMLAttributes(attributes, classes.toString(), effectiveId);

        // Add extension attributes in no namespace if possible
        if (xformsControl != null) {
            xformsControl.addExtensionAttributesExceptClassAndAcceptForHandler(newAttributes, "");
        }
        return newAttributes;
    }

    /**
     * Return the effective id of the element to which label/@for, etc. must point to.
     */
    public String getForEffectiveId(String effectiveId) {
        // Default: point to foo$bar$$c.1-2-3
        return getLHHACId(containingDocument, getEffectiveId(), LHHAC_CODES.get(LHHAC.CONTROL));
    }
}
