package org.seasar.jsf.util;

import java.io.IOException;
import java.util.Iterator;

import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIOutput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.ValueBinding;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;

import org.seasar.framework.log.Logger;
import org.seasar.jsf.JsfConstants;

/**
 * @author higa
 *  
 */
public final class RenderUtil {

    private static Logger logger = Logger.getLogger(RenderUtil.class);

    private RenderUtil() {
    }

    public static void encodeChildren(FacesContext context,
            UIComponent component) throws IOException {

        Iterator i = component.getChildren().iterator();
        while (i.hasNext()) {
            encodeChild(context, (UIComponent) i.next());
            if (context.getResponseComplete()) {
                break;
            }
        }
    }

    public static void encodeChild(FacesContext context, UIComponent component)
            throws IOException {

        component.encodeBegin(context);
        if (component.getRendersChildren()) {
            component.encodeChildren(context);
        } else {
            encodeChildren(context, component);
        }
        component.encodeEnd(context);
    }

    public static boolean renderAttributes(ResponseWriter writer,
            UIComponent component, String[] attributeNames) throws IOException {

        boolean somethingDone = false;
        for (int i = 0, len = attributeNames.length; i < len; i++) {
            String attrName = attributeNames[i];
            if (renderAttribute(writer, component, attrName)) {
                somethingDone = true;
            }
        }
        return somethingDone;
    }

    public static boolean renderAttribute(ResponseWriter writer,
            UIComponent component, String attributeName) throws IOException {

        Object value = component.getAttributes().get(attributeName);
        return renderAttribute(writer, attributeName, value, attributeName);
    }

    public static boolean renderAttribute(ResponseWriter writer,
            String attributeName, Object value, String propertyName)
            throws IOException {

        if (value == null) {
            return false;
        }
        if (attributeName.equalsIgnoreCase(JsfConstants.STYLE_CLASS_ATTR)) {
            attributeName = JsfConstants.CLASS_ATTR;
        }
        writer.writeAttribute(attributeName, value, propertyName);
        return true;
    }

    public static void renderIdIfNecessary(ResponseWriter writer,
            UIComponent component, FacesContext context) throws IOException {

        String id = component.getId();
        if (id != null && !id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
            writer.writeAttribute(JsfConstants.ID_ATTR, component
                    .getClientId(context), null);
        }
    }

    public static Object getConvertedUIOutputValue(FacesContext context,
            UIOutput output, Object submittedValue) throws ConverterException {

        if (submittedValue == null) {
            return null;
        }
        if (!(submittedValue instanceof String)) {
            submittedValue = submittedValue.toString();
        }
        Converter converter = findConverterForSubmittedValue(context, output);
        if (converter == null) {
            return submittedValue;
        }
        return converter.getAsObject(context, output, (String) submittedValue);
    }

    public static Converter findConverterForSubmittedValue(
            FacesContext context, UIOutput component) {

        Converter converter = component.getConverter();
        if (converter != null) {
            return converter;
        }
        ValueBinding vb = component.getValueBinding("value");
        if (vb == null) {
            return null;
        }
        Class valueType = vb.getType(context);
        if (valueType == null) {
            return null;
        }
        if (String.class.equals(valueType) || Object.class.equals(valueType)) {
            return null;
        }
        try {
            return context.getApplication().createConverter(valueType);
        } catch (FacesException ex) {
            logger.log(ex);
            return null;
        }
    }

    public static Renderer getRenderer(FacesContext context,
            UIComponent component) {
        
        String rendererType = component.getRendererType();
        if (rendererType == null) {
            return null;
        }
        String renderKitId = context.getViewRoot().getRenderKitId();
        RenderKitFactory rkf = (RenderKitFactory) FactoryFinder
                .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
        RenderKit renderKit = rkf.getRenderKit(context, renderKitId);
        return renderKit.getRenderer(component.getFamily(), rendererType);
    }

    public static Object getConvertedValue(FacesContext context,
            UIInput component, Object submittedValue) {
        try {
            Renderer renderer = getRenderer(context, component);
            if (renderer != null) {
                return renderer.getConvertedValue(context, component,
                        submittedValue);
            } else if (submittedValue instanceof String) {
                return getConvertedUIOutputValue(context, component, submittedValue);
            }
        } catch (ConverterException e) {
            FacesMessage facesMessage = e.getFacesMessage();
            if (facesMessage != null) {
                context.addMessage(component.getClientId(context), facesMessage);
            } else {
                context.addMessage(component.getClientId(context),
                        MessageUtil.getErrorMessage(UIInput.CONVERSION_MESSAGE_ID, new Object[] { UIComponentUtil.getLabel(component) }));
            }
            component.setValid(false);
        }
        return submittedValue;
    }
}