package org.richfaces.component;

import javax.faces.component.UIForm;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostAddToViewEvent;

import org.ajax4jsf.component.AjaxOutput;
import org.richfaces.cdk.annotations.Attribute;
import org.richfaces.cdk.annotations.JsfComponent;
import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.cdk.annotations.Tag;
import org.richfaces.cdk.annotations.TagType;
import org.richfaces.renderkit.FocusRendererBase;
import org.richfaces.renderkit.focus.FocusRendererInterface;
import org.richfaces.renderkit.util.RendererUtils;

/**
 * <p>
 * Focus component allows to set focus based on validation of components or alternatively it can preserve focus on currently
 * focused form input.
 * </p>
 *
 * <p>
 * Focus can be bound to form (in case of placement to h:form) or to whole view (when placed outside of forms) - in latter case,
 * all forms will be managed by one Focus. There can be at most one Focus per form. When there is one view-scoped Focus and form
 * defines own Focus, form-scoped Focus settings will be used.
 * </p>
 *
 * <p>
 * Focus is applied each time the component is rendered - for each full page submit and for each partial page request (in case
 * of ajaxRendered=true). Alternatively, you can use JavaScript API: <tt>applyFocus()</tt> function will immediately cause.
 * </p>
 */
@JsfComponent(type = AbstractFocus.COMPONENT_TYPE, family = AbstractFocus.COMPONENT_FAMILY, renderer = @JsfRenderer(type = FocusRendererBase.RENDERER_TYPE), tag = @Tag(type = TagType.Facelets))
@ListenerFor(systemEventClass = PostAddToViewEvent.class)
public abstract class AbstractFocus extends UIOutput implements AjaxOutput {

    public static final String COMPONENT_TYPE = "org.richfaces.Focus";
    public static final String COMPONENT_FAMILY = "org.richfaces.Focus";

    /**
     * Defines whether focus state should be updated during each AJAX request automatically. (default: true)
     */
    @Attribute(defaultValue = "true")
    public abstract boolean isAjaxRendered();

    /**
     * <p>
     * Defines if focus should respect validation of inputs.
     * </p>
     *
     * <p>
     * If true, only invalid form fields will be focused when focus applied.
     * </p>
     *
     * <p>
     * (default: true)
     * </p>
     */
    @Attribute(defaultValue = "true")
    public abstract boolean isValidationAware();

    /**
     * Defines whether focus should be preserved on last focused input before request was done (default: false)
     */
    @Attribute(defaultValue = "false")
    public abstract boolean isPreserve();

    /**
     * Defines whether focus should not be applied after initial page load, but will need to be triggered by JavaScript function
     * from Focus API: <tt>applyFocus()</tt> or via re-rendering (postback).
     */
    @Attribute(defaultValue = "false")
    public abstract boolean isDelayed();

    /**
     * Hide keepTransient attribute from AjaxOutput
     */
    @Attribute(hidden = true)
    public abstract boolean isKeepTransient();

    /**
     * Returns a mode of Focus component
     */
    public Mode getMode() {
        UIForm form = (UIForm) RendererUtils.getInstance().getNestingForm(this);
        if (form == null) {
            return Mode.VIEW;
        }

        return Mode.FORM;
    }

    public static enum Mode {
        /**
         * In this mode, focus is processed every time form is submitted.
         *
         * There can be only one focus of this type per form.
         */
        FORM,
        /**
         * This mode brings focus functionality to all forms in a view.
         *
         * The view focus settings can be overridden for each specific form by using {{@link #FORM} mode.
         */
        VIEW
    }

    @Override
    public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {

        if (event.getSource() == this) {
            if (event instanceof PostAddToViewEvent) {

                FacesContext context = FacesContext.getCurrentInstance();

                FocusRendererInterface renderer = (FocusRendererInterface) context.getRenderKit().getRenderer(COMPONENT_FAMILY,
                        FocusRendererBase.RENDERER_TYPE);
                renderer.postAddToView(context, this);
            }
        }

        super.processEvent(event);
    }
}
