/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.richfaces.renderkit;

import java.io.IOException;
import java.text.DateFormatSymbols;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TimeZone;

import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.DateTimeConverter;

import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSReference;
import org.richfaces.component.AbstractCalendar;
import org.richfaces.component.MetaComponentResolver;
import org.richfaces.component.Positioning;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.component.util.InputUtils;
import org.richfaces.component.util.InputUtils.ConverterLookupStrategy;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.context.ExtendedPartialViewContext;
import org.richfaces.event.CurrentDateChangeEvent;
import org.richfaces.utils.CalendarHelper;

/**
 * @author amarkhel
 *
 */
@ResourceDependencies({ @ResourceDependency(library = "javax.faces", name = "jsf.js"),
        @ResourceDependency(library = "org.richfaces", name = "jquery.js"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces.js"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces-queue.reslib"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces-base-component.js"),
        @ResourceDependency(library = "org.richfaces", name = "jquery.position.js"),
        @ResourceDependency(library = "org.richfaces", name = "richfaces-event.js"),
        @ResourceDependency(library = "org.richfaces", name = "json-dom.js"),
        @ResourceDependency(library = "com.jqueryui", name = "effect.js"),
        @ResourceDependency(library = "com.jqueryui", name = "effect-highlight.js"),
        @ResourceDependency(library = "org.richfaces", name = "JQuerySpinBtn.js"),
        @ResourceDependency(library = "org.richfaces", name = "calendar-utils.js"),
        @ResourceDependency(library = "org.richfaces", name = "calendar.js"),
        @ResourceDependency(library = "org.richfaces", name = "calendar.ecss") })
public class CalendarRendererBase extends InputRendererBase implements MetaComponentRenderer {
    public static final String CALENDAR_BUNDLE = "org.richfaces.renderkit.calendar";
    public static final String OPTION_DISABLED = "disabled";
    public static final String OPTION_MIN_DAYS_IN_FIRST_WEEK = "minDaysInFirstWeek";
    public static final String MONTH_LABELS_SHORT = "monthLabelsShort";
    public static final String MONTH_LABELS = "monthLabels";
    public static final String WEEK_DAY_LABELS_SHORT = "weekDayLabelsShort";
    public static final String WEEK_DAY_LABELS = "weekDayLabels";
    public static final String FIRST_DAY_WEEK = "firstWeekDay";
    public static final String MIN_DAYS_IN_FIRST_WEEK = "minDaysInFirstWeek";
    public static final String CALENDAR_ICON_RESOURCE_NAME = "calendarIcon.png";
    public static final String CALENDAR_DISABLE_ICON_RESOURCE_NAME = "disabledCalendarIcon.png";
    public static final String CURRENT_DATE_INPUT = "InputCurrentDate";
    protected static final Map<String, ComponentAttribute> CALENDAR_INPUT_HANDLER_ATTRIBUTES = Collections
        .unmodifiableMap(ComponentAttribute.createMap(
            new ComponentAttribute(HtmlConstants.ONCLICK_ATTRIBUTE).setEventNames("inputclick").setComponentAttributeName(
                "oninputclick"),
            new ComponentAttribute(HtmlConstants.ONDBLCLICK_ATTRIBUTE).setEventNames("inputdblclick")
                .setComponentAttributeName("oninputdblclick"),
            new ComponentAttribute(HtmlConstants.ONMOUSEDOWN_ATTRIBUTE).setEventNames("inputmousedown")
                .setComponentAttributeName("oninputmousedown"),
            new ComponentAttribute(HtmlConstants.ONMOUSEUP_ATTRIBUTE).setEventNames("inputmouseup").setComponentAttributeName(
                "oninputmouseup"),
            new ComponentAttribute(HtmlConstants.ONMOUSEOVER_ATTRIBUTE).setEventNames("inputmouseover")
                .setComponentAttributeName("oninputmouseover"),
            new ComponentAttribute(HtmlConstants.ONMOUSEMOVE_ATTRIBUTE).setEventNames("inputmousemove")
                .setComponentAttributeName("oninputmousemove"),
            new ComponentAttribute(HtmlConstants.ONMOUSEOUT_ATTRIBUTE).setEventNames("inputmouseout")
                .setComponentAttributeName("oninputmouseout"),
            new ComponentAttribute(HtmlConstants.ONKEYPRESS_ATTRIBUTE).setEventNames("inputkeypress")
                .setComponentAttributeName("oninputkeypress"),
            new ComponentAttribute(HtmlConstants.ONKEYDOWN_ATTRIBUTE).setEventNames("inputkeydown").setComponentAttributeName(
                "oninputkeydown"),
            new ComponentAttribute(HtmlConstants.ONKEYUP_ATTRIBUTE).setEventNames("inputkeyup").setComponentAttributeName(
                "oninputkeyup"),
            new ComponentAttribute(HtmlConstants.ONBLUR_ATTRIBUTE).setEventNames("inputblur").setComponentAttributeName(
                "oninputblur"),
            new ComponentAttribute(HtmlConstants.ONFOCUS_ATTRIBUTE).setEventNames("inputfocus").setComponentAttributeName(
                "oninputfocus"),
            new ComponentAttribute(HtmlConstants.ONCHANGE_ATTRIBUTE).setEventNames("inputchange").setComponentAttributeName(
                "oninputchange"),
            new ComponentAttribute(HtmlConstants.ONSELECT_ATTRIBUTE).setEventNames("inputselect").setComponentAttributeName(
                "oninputselect")));
    private static final String HOURS_VALUE = "hours";
    private static final String MINUTES_VALUE = "minutes";
    private static final String SECONDS_VALUE = "seconds";
    protected final ConverterLookupStrategy calendarConverterLookupStrategy = new ConverterLookupStrategy() {
        public Converter getConverterByValue(FacesContext context, UIComponent component, Object value)
            throws ConverterException {
            AbstractCalendar calendar = (AbstractCalendar) component;
            Converter converter = calendar.getConverter();

            if (converter == null && value != null) {
                converter = InputUtils.getConverterForType(context, value.getClass());
            }

            // in case the converter hasn't been set, try to use default
            // DateTimeConverter
            if (converter == null) {
                converter = createDefaultConverter(context);
            }

            setupConverter(context, converter, (AbstractCalendar) component);
            return converter;
        }

        public Converter getConverterByProperty(FacesContext context, UIComponent component) throws ConverterException {
            AbstractCalendar calendar = (AbstractCalendar) component;
            Converter converter = InputUtils.findConverter(context, calendar, "value");

            // in case the converter hasn't been set, try to use default
            // DateTimeConverter
            if (converter == null) {
                converter = createDefaultConverter(context);
            }

            setupConverter(context, converter, calendar);
            return converter;
        }
    };

    protected void doDecode(FacesContext context, UIComponent component) {
        if (!(component instanceof AbstractCalendar)) {
            return;
        }

        AbstractCalendar calendar = (AbstractCalendar) component;
        if (calendar.isDisabled()) {
            return;
        }

        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();

        String clientId = calendar.getClientId(context);
        String currentDateString = (String) requestParameterMap.get(clientId + CURRENT_DATE_INPUT);
        if (currentDateString != null) {
            calendar.queueEvent(new CurrentDateChangeEvent(calendar, currentDateString));
        }

        String selectedDateString = requestParameterMap.get(clientId + "InputDate");
        if (selectedDateString != null) {
            calendar.setSubmittedValue(selectedDateString);
        }

        if (requestParameterMap.get(component.getClientId(context) + ".ajax") != null) {
            PartialViewContext pvc = context.getPartialViewContext();
            pvc.getRenderIds().add(
                component.getClientId(context) + MetaComponentResolver.META_COMPONENT_SEPARATOR_CHAR
                    + AbstractCalendar.DAYSDATA_META_COMPONENT_ID);

            context.renderResponse();
        }
    }

    public void renderInputHandlers(FacesContext facesContext, UIComponent component) throws IOException {
        RenderKitUtils.renderPassThroughAttributesOptimized(facesContext, component, CALENDAR_INPUT_HANDLER_ATTRIBUTES);
    }

    @Override
    public Object getConvertedValue(FacesContext facesContext, UIComponent component, Object submittedValue)
        throws ConverterException {
        if ((facesContext == null) || (component == null)) {
            throw new NullPointerException();
        }

        // skip conversion of already converted date
        if (submittedValue instanceof Date) {
            return submittedValue;
        }

        return InputUtils.getConvertedValue(facesContext, component, calendarConverterLookupStrategy, submittedValue);
    }

    @Override
    public String getInputValue(FacesContext facesContext, UIComponent component) {
        if (!(component instanceof AbstractCalendar)) {
            return null;
        }

        return InputUtils.getInputValue(facesContext, component, calendarConverterLookupStrategy);
    }

    public String getButtonIcon(FacesContext facesContext, UIComponent component) {
        boolean disable = (Boolean) component.getAttributes().get(OPTION_DISABLED);
        String buttonIcon = (String) component.getAttributes().get("buttonIcon");
        if (disable) {
            buttonIcon = (String) component.getAttributes().get("buttonDisabledIcon");
        }

        if (buttonIcon != null && buttonIcon.trim().length() != 0) {
            buttonIcon = RenderKitUtils.getResourceURL(buttonIcon, facesContext);
        } else {
            buttonIcon = disable ? CALENDAR_DISABLE_ICON_RESOURCE_NAME : CALENDAR_ICON_RESOURCE_NAME;
            buttonIcon = RenderKitUtils.getResourcePath(facesContext, "org.richfaces.images", buttonIcon);
        }
        return buttonIcon;
    }

    public Object getSelectedDate(FacesContext facesContext, UIComponent component) throws IOException {
        Object returnValue = null;
        AbstractCalendar calendar = (AbstractCalendar) component;
        if (calendar.isValid()) {
            Date date;
            Object value = calendar.getValue();
            date = CalendarHelper.getAsDate(facesContext, calendar, value);
            if (date != null) {
                returnValue = formatSelectedDate(CalendarHelper.getTimeZoneOrDefault(calendar), date);
            }
        }
        return returnValue;
    }

    public static Object formatSelectedDate(TimeZone timeZone, Date date) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        AbstractCalendar calendarInstance = (AbstractCalendar) AbstractCalendar.getCurrentComponent(facesContext);
        Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarInstance);

        calendar.setTimeZone(timeZone);
        calendar.setTime(date);

        JSFunction result = new JSFunction("new Date");
        result.addParameter(calendar.get(Calendar.YEAR));
        result.addParameter(calendar.get(Calendar.MONTH));
        result.addParameter(calendar.get(Calendar.DATE));
        result.addParameter(calendar.get(Calendar.HOUR_OF_DAY));
        result.addParameter(calendar.get(Calendar.MINUTE));
        result.addParameter(0);

        return result;
    }

    public Object getCurrentDate(FacesContext facesContext, UIComponent component) throws IOException {
        AbstractCalendar calendar = (AbstractCalendar) component;
        Date date = CalendarHelper.getCurrentDateOrDefault(facesContext, calendar);
        return formatDate(date);
    }

    public String getCurrentDateAsString(FacesContext facesContext, UIComponent component) throws IOException {
        AbstractCalendar calendar = (AbstractCalendar) component;
        Format formatter = new SimpleDateFormat("MM/yyyy");

        Date currentDate = CalendarHelper.getCurrentDateOrDefault(facesContext, calendar);
        return formatter.format(currentDate);
    }

    public static Object formatDate(Date date) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        AbstractCalendar calendarInstance = (AbstractCalendar) AbstractCalendar.getCurrentComponent(facesContext);
        Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarInstance);

        calendar.setTime(date);
        JSFunction result = new JSFunction("new Date");
        result.addParameter(calendar.get(Calendar.YEAR));
        result.addParameter(calendar.get(Calendar.MONTH));
        result.addParameter(calendar.get(Calendar.DATE));

        return result;
    }

    public String getDayCellClass(FacesContext facesContext, UIComponent component) {
        // TODO: refactor this
        /*
         * String cellwidth = (String) component.getAttributes().get("cellWidth"); String cellheight = (String)
         * component.getAttributes().get("cellHeight"); if (cellwidth != null && cellwidth.length() != 0 || cellheight != null
         * && cellheight.length() != 0) { String clientId = component.getClientId(context); String value = clientId.replace(':',
         * '_') + "DayCell"; return value; }
         */
        return null;
    }

    public JSReference getDayEnabled(FacesContext facesContext, UIComponent component) {
        AbstractCalendar calendar = (AbstractCalendar) component;
        String dayEnabled = calendar.getDayDisableFunction();
        return ((dayEnabled != null && dayEnabled.trim().length() != 0)) ? new JSReference(dayEnabled) : null;
    }

    public JSReference getDayStyleClass(FacesContext context, UIComponent component) {
        AbstractCalendar calendar = (AbstractCalendar) component;
        String dayStyleClass = calendar.getDayClassFunction();
        return ((dayStyleClass != null && dayStyleClass.trim().length() != 0)) ? new JSReference(dayStyleClass) : null;
    }

    public Map<String, Object> getLabels(FacesContext facesContext, UIComponent component) {
        AbstractCalendar calendar = (AbstractCalendar) component;

        ResourceBundle bundle1 = null;
        ResourceBundle bundle2 = null;

        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        String messageBundle = facesContext.getApplication().getMessageBundle();
        Locale locale = CalendarHelper.getAsLocale(facesContext, calendar);
        if (null != messageBundle) {
            bundle1 = ResourceBundle.getBundle(messageBundle, locale, loader);
        }

        try {
            bundle2 = ResourceBundle.getBundle(CALENDAR_BUNDLE, locale, loader);
        } catch (MissingResourceException e) {
            // No external bundle was found, ignore this exception.
        }

        ResourceBundle[] bundles = { bundle1, bundle2 };
        String[] names = { "apply", "today", "clean", "cancel", "ok", "close" };

        return getCollectedLabels(bundles, names);
    }

    protected Map<String, Object> getCollectedLabels(ResourceBundle[] bundles, String[] names) {
        Map<String, Object> labels = new HashMap<String, Object>();
        if (bundles != null && names != null) {
            for (String name : names) {
                String label = null;
                String bundleKey = "RICH_CALENDAR_" + name.toUpperCase() + "_LABEL";
                for (ResourceBundle bundle : bundles) {
                    if (bundle != null) {
                        try {
                            label = bundle.getString(bundleKey);
                        } catch (MissingResourceException mre) {
                            // Current key was not found, ignore this exception;
                        }
                    }
                    if (label != null) {
                        break;
                    }
                }
                RenderKitUtils.addToScriptHash(labels, name, label);
            }
        }
        return labels;
    }

    public Map<String, Object> getPreparedDefaultTime(FacesContext facesContext, UIComponent component) {
        AbstractCalendar abstractCalendar = (AbstractCalendar) component;

        Date date = CalendarHelper.getFormattedDefaultTime(abstractCalendar);
        Map<String, Object> result = new HashMap<String, Object>();
        if (date != null) {
            Calendar calendar = CalendarHelper.getCalendar(null, null);
            calendar.setTime(date);
            int hours = calendar.get(Calendar.HOUR_OF_DAY);
            int minutes = calendar.get(Calendar.MINUTE);
            int seconds = calendar.get(Calendar.SECOND);

            if (hours != 12 || minutes != 0 || seconds != 0) {
                result.put(HOURS_VALUE, hours);
                result.put(MINUTES_VALUE, minutes);
                result.put(SECONDS_VALUE, seconds);
            }
        }
        if (result.size() > 0) {
            return result;
        } else {
            return null;
        }
    }

    private static String[] shiftDates(int minimum, int maximum, String[] labels) {
        if (minimum == 0 && (maximum - minimum == labels.length - 1)) {
            return labels;
        }

        String[] shiftedLabels = new String[maximum - minimum + 1];
        System.arraycopy(labels, minimum, shiftedLabels, 0, maximum - minimum + 1);

        return shiftedLabels;
    }

    protected Map<String, Object> getLocaleOptions(FacesContext facesContext, UIComponent component) {
        AbstractCalendar calendarComponent = (AbstractCalendar) component;

        Map<String, Object> map = new HashMap<String, Object>();

        Locale locale = CalendarHelper.getAsLocale(facesContext, calendarComponent);
        DateFormatSymbols dateFormat = new DateFormatSymbols(locale);

        Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarComponent);
        int maximum = calendar.getActualMaximum(Calendar.DAY_OF_WEEK);
        int minimum = calendar.getActualMinimum(Calendar.DAY_OF_WEEK);

        int monthMax = calendar.getActualMaximum(Calendar.MONTH);
        int monthMin = calendar.getActualMinimum(Calendar.MONTH);

        String[] weekDayLabels = RenderKitUtils.asArray(calendarComponent.getWeekDayLabels());
        if (isEmptyArray(weekDayLabels)) {
            weekDayLabels = dateFormat.getWeekdays();
            weekDayLabels = shiftDates(minimum, maximum, weekDayLabels);
        }
        RenderKitUtils.addToScriptHash(map, WEEK_DAY_LABELS, weekDayLabels);

        String[] weekDayLabelsShort = RenderKitUtils.asArray(calendarComponent.getWeekDayLabelsShort());
        if (isEmptyArray(weekDayLabelsShort)) {
            weekDayLabelsShort = dateFormat.getShortWeekdays();
            weekDayLabelsShort = shiftDates(minimum, maximum, weekDayLabelsShort);
        }
        RenderKitUtils.addToScriptHash(map, WEEK_DAY_LABELS_SHORT, weekDayLabelsShort);

        String[] monthLabels = RenderKitUtils.asArray(calendarComponent.getMonthLabels());
        if (isEmptyArray(monthLabels)) {
            monthLabels = dateFormat.getMonths();
            monthLabels = shiftDates(monthMin, monthMax, monthLabels);
        }
        RenderKitUtils.addToScriptHash(map, MONTH_LABELS, monthLabels);

        String[] monthLabelsShort = RenderKitUtils.asArray(calendarComponent.getMonthLabelsShort());
        if (isEmptyArray(monthLabelsShort)) {
            monthLabelsShort = dateFormat.getShortMonths();
            monthLabelsShort = shiftDates(monthMin, monthMax, monthLabelsShort);
        }
        RenderKitUtils.addToScriptHash(map, MONTH_LABELS_SHORT, monthLabelsShort);

        int minDaysInFirstWeek = calendarComponent.getMinDaysInFirstWeek();

        if (1 > minDaysInFirstWeek || minDaysInFirstWeek > 7) {
            minDaysInFirstWeek = calendar.getMinimalDaysInFirstWeek();
        }

        if (0 <= minDaysInFirstWeek && minDaysInFirstWeek <= 7) {
            RenderKitUtils.addToScriptHash(map, MIN_DAYS_IN_FIRST_WEEK, minDaysInFirstWeek);
        }

        int day = calendarComponent.getFirstWeekDay();
        if (day < 0 || 6 < day) {
            day = calendar.getFirstDayOfWeek() - calendar.getActualMinimum(Calendar.DAY_OF_WEEK);
        }

        if (0 <= day && day <= 6) {
            RenderKitUtils.addToScriptHash(map, FIRST_DAY_WEEK, day);
        } else {
            throw new IllegalArgumentException(day + " value of firstWeekDay attribute is not a legal one for component: "
                + MessageUtil.getLabel(facesContext, calendarComponent));
        }
        return map;
    }

    private boolean isEmptyArray(String[] array) {
        if (array != null) {
            for (String str : array) {
                if (str.trim().length() > 0) {
                    return false;
                }
            }
        }
        return true;
    }

    public String getPopupStyle(FacesContext facesContext, UIComponent component) {
        AbstractCalendar calendar = (AbstractCalendar) component;
        int zindex = calendar.getZindex();
        if (zindex < 0) {
            zindex = 3;
        }

        return HtmlUtil.concatStyles("z-index: " + zindex, calendar.getPopupStyle());
    }

    public Locale getAsLocale(FacesContext facesContext, UIComponent component) {
        return CalendarHelper.getAsLocale(facesContext, component);
    }

    public String writePreloadBody(FacesContext context, UIComponent component) throws IOException {
        AbstractCalendar calendar = (AbstractCalendar) component;
        Object preload = calendar.getPreload();
        if (preload != null) {
            return RenderKitUtils.toScriptArgs(preload);
        } else {
            return null;
        }
    }

    public boolean isUseIcons(FacesContext facesContext, UIComponent component) {
        Object label = component.getAttributes().get("buttonLabel");
        return (label == null || ((String) label).trim().length() == 0);
    }

    protected Converter createDefaultConverter(FacesContext facesContext) {
        if (facesContext == null) {
            return null;
        }
        return facesContext.getApplication().createConverter(DateTimeConverter.CONVERTER_ID);
    }

    protected Converter setupConverter(FacesContext facesContext, Converter converter, AbstractCalendar calendar) {
        if (converter == null || calendar == null) {
            return null;
        }

        if (converter instanceof DateTimeConverter) {
            DateTimeConverter defaultConverter = (DateTimeConverter) converter;
            defaultConverter.setPattern(CalendarHelper.getDatePatternOrDefault(calendar));
            defaultConverter.setLocale(CalendarHelper.getAsLocale(facesContext, calendar));
            defaultConverter.setTimeZone(CalendarHelper.getTimeZoneOrDefault(calendar));
        }
        return converter;
    }

    public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) throws IOException {
        if (AbstractCalendar.DAYSDATA_META_COMPONENT_ID.equals(metaComponentId)) {
            Object preload = ((AbstractCalendar) component).getPreload();
            Map<String, Object> dataMap = ExtendedPartialViewContext.getInstance(context).getResponseComponentDataMap();
            dataMap.put(component.getClientId(context), preload);
        } else {
            throw new IllegalArgumentException(metaComponentId);
        }
    }

    public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
        throw new UnsupportedOperationException();
    }

    protected String getTodayControlModeOrDefault(UIComponent component) {
        String value = "";
        if (component instanceof AbstractCalendar) {
            value = ((AbstractCalendar) component).getTodayControlMode();
            if (value == null || value.length() == 0) {
                value = "select";
            }
        }
        return value;
    }

    protected String getJointPoint(UIComponent component) {
        if (component instanceof AbstractCalendar) {
            Positioning jointPoint = ((AbstractCalendar) component).getJointPoint();
            if (jointPoint != null) {
                return jointPoint.getValue();
            }
        }
        // return null to use default value on client
        return null;
    }

    protected String getDirection(UIComponent component) {
        if (component instanceof AbstractCalendar) {
            Positioning direction = ((AbstractCalendar) component).getDirection();
            if (direction != null) {
                return direction.getValue();
            }
        }
        // return null to use default value on client
        return null;
    }

    protected String getBoundaryDatesModeOrDefault(UIComponent component) {
        String value = "";
        if (component instanceof AbstractCalendar) {
            value = ((AbstractCalendar) component).getBoundaryDatesMode();
            if (value == null || value.length() == 0) {
                value = "inactive";
            }
        }
        return value;
    }

    protected AbstractCalendar.Mode getModeOrDefault(UIComponent component) {
        AbstractCalendar.Mode value = ((AbstractCalendar) component).getMode();
        if (value == null) {
            value = AbstractCalendar.Mode.client;
        }
        return value;
    }
}