org.libreplan.web.calendars.BaseCalendarEditionController.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.calendars.BaseCalendarEditionController.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico de Galicia
 * Copyright (C) 2010-2012 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.libreplan.web.calendars;

import static org.libreplan.web.I18nHelper._;
import static org.libreplan.web.common.Util.findOrCreate;

import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.CalendarAvailability;
import org.libreplan.business.calendars.entities.CalendarData;
import org.libreplan.business.calendars.entities.CalendarData.Days;
import org.libreplan.business.calendars.entities.CalendarException;
import org.libreplan.business.calendars.entities.CalendarExceptionType;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.EffortDuration.Granularity;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.components.CapacityPicker;
import org.libreplan.web.common.components.EffortDurationPicker;
import org.zkoss.util.Locales;
import org.zkoss.zk.au.out.AuInvoke;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.ComboitemRenderer;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Calendar;
import org.zkoss.zul.Window;

/**
 * Controller for edit and create one {@link BaseCalendar}.
 * It's separated of {@link BaseCalendarCRUDController} to be used from other parts of the application.
 *
 * @author Manuel Rego Casasnovas <mrego@igalia.com>
 * @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
 */
public abstract class BaseCalendarEditionController extends GenericForwardComposer {

    private static final String TEXT_COLOR_HIGHLIGHTED_DAY = "white";

    private static final String BACKGROUND_COLOR_ZERO_HOURS_DAY = "lightgrey";

    private IBaseCalendarModel baseCalendarModel;

    private Window window;

    private Window createNewVersionWindow;

    private HoursPerDayRenderer hoursPerDayRenderer = new HoursPerDayRenderer();

    private HistoryVersionsRenderer historyVersionsRenderer = new HistoryVersionsRenderer();

    private ParentCalendarsComboitemRenderer parentCalendarsComboitemRenderer = new ParentCalendarsComboitemRenderer();

    private CalendarExceptionRenderer calendarExceptionRenderer = new CalendarExceptionRenderer();

    private CalendarAvailabilityRenderer calendarAvailabilityRenderer = new CalendarAvailabilityRenderer();

    private boolean creatingNewVersion = false;

    private Date newWorkWeekStartDate;

    private Date newWorkWeekExpiringDate;

    private CapacityPicker capacityPicker;

    private IMessagesForUser messagesForUser;

    private Combobox exceptionTypes;

    public BaseCalendarEditionController(IBaseCalendarModel baseCalendarModel, Window window,
            Window createNewVersionWindow, IMessagesForUser messagesForUser) {

        this.baseCalendarModel = baseCalendarModel;
        this.window = window;
        this.createNewVersionWindow = createNewVersionWindow;
        this.messagesForUser = messagesForUser;
    }

    private static String asString(Capacity capacity) {
        String extraEffortString = capacity.isOverAssignableWithoutLimit() ? _("unl")
                : asString(capacity.getAllowedExtraEffort());

        return asString(capacity.getStandardEffort()) + " [" + extraEffortString + "]";
    }

    private static String asString(EffortDuration duration) {
        if (duration == null) {
            return "";
        }
        EnumMap<Granularity, Integer> decomposed = duration.decompose();

        String result = decomposed.get(Granularity.HOURS) + ":";
        result += decomposed.get(Granularity.MINUTES);

        if (decomposed.get(Granularity.SECONDS) > 0) {
            result += " " + decomposed.get(Granularity.SECONDS) + "s";
        }

        return result;
    }

    public abstract void goToList();

    public abstract void save();

    public abstract void saveAndContinue();

    public abstract void cancel();

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        prepareExceptionTypeCombo();
        capacityPicker = addEffortDurationPickerAtWorkableTimeRow(comp);
        updateWithCapacityFrom(getSelectedExceptionType());
    }

    private CapacityPicker addEffortDurationPickerAtWorkableTimeRow(Component comp) {
        Component normalEffortRow = comp.getFellow("exceptionDayNormalEffortRow");
        Component extraEffortRow = comp.getFellow("exceptionDayExtraEffortBox");

        EffortDurationPicker normalDuration = findOrCreateDurationPicker(normalEffortRow);
        normalDuration.initializeFor24HoursAnd0Minutes();
        EffortDurationPicker extraDuration = findOrCreateDurationPicker(extraEffortRow);
        Checkbox checkbox = findOrCreateUnlimitedCheckbox(extraEffortRow);

        return CapacityPicker.workWith(checkbox, normalDuration, extraDuration,
                Capacity.create(EffortDuration.zero()));
    }

    private EffortDurationPicker findOrCreateDurationPicker(Component parent) {
        return findOrCreate(parent, EffortDurationPicker.class, parent1 -> {
            EffortDurationPicker normalDuration = new EffortDurationPicker();
            parent1.appendChild(normalDuration);

            return normalDuration;
        });
    }

    private Checkbox findOrCreateUnlimitedCheckbox(Component parent) {
        return findOrCreate(parent, Checkbox.class, parent1 -> {
            Checkbox result = createUnlimitedCheckbox();
            parent1.appendChild(result);

            return result;
        });
    }

    private Checkbox createUnlimitedCheckbox() {
        Checkbox unlimited = new Checkbox();
        unlimited.setLabel(_("Unlimited"));
        unlimited.setTooltiptext(_("Infinitely Over Assignable"));

        return unlimited;
    }

    private void updateWithCapacityFrom(CalendarExceptionType exceptionType) {
        capacityPicker.setValue(
                exceptionType != null ? exceptionType.getCapacity() : Capacity.create(EffortDuration.zero()));
    }

    private CalendarExceptionType getSelectedExceptionType() {
        Comboitem selectedItem = exceptionTypes.getSelectedItem();

        return selectedItem != null ? selectedItem.getValue() : null;
    }

    private void prepareExceptionTypeCombo() {
        exceptionTypes = (Combobox) window.getFellow("exceptionTypes");
        fillExceptionTypesComboAndMarkSelectedItem(exceptionTypes);
        addSelectListener(exceptionTypes);
    }

    private void addSelectListener(final Combobox exceptionTypes) {
        exceptionTypes.addEventListener(Events.ON_SELECT, new EventListener() {

            @Override
            public void onEvent(Event event) {
                Comboitem selectedItem = getSelectedItem((SelectEvent) event);
                if (selectedItem != null) {
                    updateWithCapacityFrom(getValue(selectedItem));
                }
            }

            private Comboitem getSelectedItem(SelectEvent event) {
                return (Comboitem) event.getSelectedItems().iterator().next();
            }

            private CalendarExceptionType getValue(Comboitem item) {
                return (CalendarExceptionType) item.getValue();
            }
        });
    }

    private void fillExceptionTypesComboAndMarkSelectedItem(Combobox exceptionTypes) {
        exceptionTypes.getChildren().clear();
        CalendarExceptionType type = baseCalendarModel.getCalendarExceptionType();

        Comboitem defaultItem = new Comboitem("NO_EXCEPTION");
        exceptionTypes.appendChild(defaultItem);
        if (type == null) {
            exceptionTypes.setSelectedItem(defaultItem);
        }

        for (CalendarExceptionType calendarExceptionType : baseCalendarModel.getCalendarExceptionTypes()) {
            Comboitem item = new Comboitem(calendarExceptionType.getName());
            item.setValue(calendarExceptionType);
            exceptionTypes.appendChild(item);
            if ((type != null) && (type.getName().equals(calendarExceptionType.getName()))) {
                exceptionTypes.setSelectedItem(item);
            }
        }
    }

    public boolean isEditing() {
        return baseCalendarModel.isEditing();
    }

    public BaseCalendar getBaseCalendar() {
        return baseCalendarModel.getBaseCalendar();
    }

    /** Should be public! */
    public String getCalendarType() {
        if (baseCalendarModel.isDerived()) {
            String currentStartDate = this.getCurrentStartDateLabel();
            String currentExpiringDate = this.getCurrentExpiringDateLabel();

            return _("Derived of calendar {0}", getNameParentCalendar()) + currentStartDate + currentExpiringDate;
        }

        return _("Root calendar");
    }

    private String getCurrentExpiringDateLabel() {
        Date date = baseCalendarModel.getCurrentExpiringDate();
        String label = "";

        if (date != null) {
            label = " " + _("to {0}", Util.formatDate(date));
        }

        return label;
    }

    private String getCurrentStartDateLabel() {
        Date date = baseCalendarModel.getCurrentStartDate();
        String label = "";

        if (date != null) {
            label = " " + _("from {0}", Util.formatDate(date));
        }

        return label;
    }

    public boolean isDerived() {
        return baseCalendarModel.isDerived();
    }

    public boolean isNotDerived() {
        return (!isDerived());
    }

    public List<Days> getHoursPerDay() {
        return Arrays.asList(Days.values());
    }

    public HoursPerDayRenderer getHoursPerDayRenderer() {
        return hoursPerDayRenderer;
    }

    public class HoursPerDayRenderer implements ListitemRenderer {

        @Override
        public void render(Listitem item, Object data, int i) {
            final Days day = (Days) data;

            addLabelCell(item, day);

            EffortDurationPicker normalDurationPicker = new EffortDurationPicker();
            normalDurationPicker.initializeFor24HoursAnd0Minutes();
            EffortDurationPicker extraDurationPicker = new EffortDurationPicker();
            Checkbox unlimitedCheckbox = createUnlimitedCheckbox();

            addNormalDurationCell(item, normalDurationPicker);
            addExtraEffortCell(item, extraDurationPicker, unlimitedCheckbox);

            CapacityPicker capacityPicker = CapacityPicker.workWith(unlimitedCheckbox, normalDurationPicker,
                    extraDurationPicker, () -> baseCalendarModel.getCapacityAt(day), value -> {
                        baseCalendarModel.setCapacityAt(day, value);
                        reloadDayInformation();
                    });

            capacityPicker.setDisabled(baseCalendarModel.isDerived() && baseCalendarModel.isDefault(day));

            Listcell inheritedListcell = new Listcell();
            if (baseCalendarModel.isDerived()) {

                Checkbox inheritedCheckbox = Util.bind(new Checkbox(), () -> baseCalendarModel.isDefault(day),
                        value -> {
                            if (value) {
                                baseCalendarModel.setDefault(day);
                            } else {
                                baseCalendarModel.unsetDefault(day);
                            }
                        });

                inheritedCheckbox.addEventListener(Events.ON_CHECK, (EventListener) event -> reloadCurrentWindow());

                inheritedListcell.appendChild(inheritedCheckbox);
            }
            item.appendChild(inheritedListcell);
        }

        private void addLabelCell(Listitem item, final Days day) {
            String days[] = DateFormatSymbols.getInstance(Locales.getCurrent()).getWeekdays();

            Listcell labelListcell = new Listcell();
            labelListcell.appendChild(new Label(days[day.getIndex()]));
            item.appendChild(labelListcell);
        }

        private void addNormalDurationCell(Listitem item, EffortDurationPicker normalDurationPicker) {
            Listcell normalEffortCell = new Listcell();
            normalEffortCell.appendChild(normalDurationPicker);
            item.appendChild(normalEffortCell);
        }

        private void addExtraEffortCell(Listitem item, EffortDurationPicker extraDurationPicker,
                Checkbox checkbox) {
            Listcell extraEffortCell = new Listcell();
            Hbox hbox = new Hbox();
            hbox.setSclass("extra effort cell");
            hbox.appendChild(extraDurationPicker);
            hbox.appendChild(checkbox);
            extraEffortCell.appendChild(hbox);
            item.appendChild(extraEffortCell);
        }
    }

    private void reloadCurrentWindow() {
        Util.reloadBindings(window);
        highlightDaysOnCalendar();
    }

    private void reloadDayInformation() {
        Util.reloadBindings(window.getFellow("dayInformation"));
        Util.reloadBindings(window.getFellow("exceptionInformation"));
        reloadWorkWeeksList();
        reloadTypeDatesAndDuration();
        highlightDaysOnCalendar();
    }

    private void reloadSelectDayInformation() {
        Util.reloadBindings(window.getFellow("dayInformation"));
        Util.reloadBindings(window.getFellow("exceptionInformation"));
        Util.reloadBindings(window.getFellow("hoursPerDay"));
        reloadTypeDatesAndDuration();
        highlightDaysOnCalendar();
    }

    private void reloadWorkWeeksList() {
        Util.reloadBindings(window.getFellow("historyInformation"));
        Util.reloadBindings(window.getFellow("calendarTypeLabel"));
    }

    private void reloadTypeDatesAndDuration() {
        LocalDate selectedDay = baseCalendarModel.getSelectedDay();

        CalendarExceptionType type = baseCalendarModel.getCalendarExceptionType(new LocalDate(selectedDay));
        Combobox exceptionTypes = (Combobox) window.getFellow("exceptionTypes");

        @SuppressWarnings("unchecked")
        List<Comboitem> items = exceptionTypes.getItems();

        for (Comboitem item : items) {
            CalendarExceptionType value = item.getValue();
            if ((value == null) && (type == null)) {
                exceptionTypes.setSelectedItem(item);
                break;
            }

            if ((value != null) && (type != null) && (value.getName().equals(type.getName()))) {
                exceptionTypes.setSelectedItem(item);
                break;
            }
        }

        Datebox dateboxStartDate = (Datebox) window.getFellow("exceptionStartDate");
        dateboxStartDate.setValue(toDate(selectedDay));
        Datebox dateboxEndDate = (Datebox) window.getFellow("exceptionEndDate");
        dateboxEndDate.setValue(toDate(selectedDay));
        capacityPicker.setValue(baseCalendarModel.getWorkableCapacity());
    }

    private Map<String, List<Integer>> getDaysCurrentMonthByColor() {
        LocalDate currentDate = baseCalendarModel.getSelectedDay();

        LocalDate minDate = currentDate.dayOfMonth().withMinimumValue();
        LocalDate maxDate = currentDate.dayOfMonth().withMaximumValue();

        Map<String, List<Integer>> colorsMap = new HashMap<>();

        BaseCalendar calendar = baseCalendarModel.getBaseCalendar();
        if (calendar == null) {
            return colorsMap;
        }

        for (LocalDate date = minDate; date.compareTo(maxDate) <= 0; date = date.plusDays(1)) {
            CalendarExceptionType calendarExceptionType = calendar.getExceptionType(date);

            if (calendarExceptionType != null) {

                if (calendar.getOwnExceptionDay(date) != null) {

                    addDayToColor(colorsMap, calendarExceptionType.getColor().getColorOwnException(),
                            date.getDayOfMonth());
                } else {
                    addDayToColor(colorsMap, calendarExceptionType.getColor().getColorDerivedException(),
                            date.getDayOfMonth());
                }
            } else {
                if (calendar.getCapacityOn(PartialDay.wholeDay(date)).isZero()) {
                    addDayToColor(colorsMap, BACKGROUND_COLOR_ZERO_HOURS_DAY, date.getDayOfMonth());
                }
            }
        }

        return colorsMap;
    }

    private void addDayToColor(Map<String, List<Integer>> colorsMap, String color, int day) {
        if (colorsMap.get(color) == null)
            colorsMap.put(color, new ArrayList<>());

        colorsMap.get(color).add(day);
    }

    public void highlightDaysOnCalendar() {
        Calendar calendar = (Calendar) window.getFellow("calendarWidget");

        Clients.response(new AuInvoke(calendar, "resetHighlightedDates"));

        Map<String, List<Integer>> daysByColor = getDaysCurrentMonthByColor();
        for (String color : daysByColor.keySet()) {

            Clients.response(new AuInvoke(calendar, "highlightDates", daysByColor.get(color).toArray(),
                    TEXT_COLOR_HIGHLIGHTED_DAY, color));
        }
    }

    public Date getSelectedDay() {
        Date selectedDay = toDate(baseCalendarModel.getSelectedDay());

        return selectedDay == null ? new Date() : selectedDay;
    }

    private static Date toDate(LocalDate date) {
        return date == null ? null : date.toDateTimeAtStartOfDay().toDate();
    }

    private static LocalDate toLocalDate(Date date) {
        return date == null ? null : LocalDate.fromDateFields(date);
    }

    public void setSelectedDay(LocalDate date) {
        baseCalendarModel.setSelectedDay(date);
        reloadSelectDayInformation();
    }

    public String getTypeOfDay() {
        BaseCalendar calendar = baseCalendarModel.getBaseCalendar();
        if (calendar == null) {
            return "";
        }

        LocalDate date = baseCalendarModel.getSelectedDay();
        CalendarException exceptionDay = calendar.getExceptionDay(date);
        if (exceptionDay != null) {
            if (calendar.getOwnExceptionDay(date) != null) {
                return _("Exception: {0}", exceptionDay.getType().getName());
            } else {
                return _("Exception: {0} (Inh)", exceptionDay.getType().getName());
            }
        }

        if (calendar.getCapacityOn(PartialDay.wholeDay(date)).isZero()) {
            return _("Not workable day");
        }

        return _("Normal");
    }

    public String getWorkableTime() {
        return asString(baseCalendarModel.getWorkableTime());
    }

    public void createException() {
        Combobox exceptionTypes = (Combobox) window.getFellow("exceptionTypes");
        CalendarExceptionType type = exceptionTypes.getSelectedItem().getValue();

        if (type == null) {
            throw new WrongValueException(exceptionTypes, _("Please, select type of exception"));
        } else {
            Clients.clearWrongValue(exceptionTypes);
        }

        Datebox dateboxStartDate = (Datebox) window.getFellow("exceptionStartDate");
        Date startDate = dateboxStartDate.getValue();

        if (startDate == null) {
            throw new WrongValueException(dateboxStartDate, _("You should select a start date for the exception"));
        } else {
            Clients.clearWrongValue(dateboxStartDate);
        }

        Datebox dateboxEndDate = (Datebox) window.getFellow("exceptionEndDate");
        Date endDate = dateboxEndDate.getValue();

        if (endDate == null) {
            throw new WrongValueException(dateboxEndDate, _("Please, select an End Date for the Exception"));
        } else {
            Clients.clearWrongValue(dateboxEndDate);
        }

        if (new LocalDate(startDate).compareTo(new LocalDate(endDate)) > 0) {
            throw new WrongValueException(dateboxEndDate,
                    _("Exception end date should be greater or equals than start date"));
        } else {
            Clients.clearWrongValue(dateboxEndDate);
        }

        Capacity capacity = capacityPicker.getValue();

        baseCalendarModel.createException(type, LocalDate.fromDateFields(startDate),
                LocalDate.fromDateFields(endDate), capacity);

        reloadDayInformation();
    }

    public boolean isNotExceptional() {
        return !baseCalendarModel.isExceptional();
    }

    public void removeException() {
        baseCalendarModel.removeException();

        reloadDayInformation();
    }

    public List<CalendarData> getHistoryVersions() {
        return baseCalendarModel.getHistoryVersions();
    }

    public HistoryVersionsRenderer getHistoryVersionsRenderer() {
        return historyVersionsRenderer;
    }

    public class HistoryVersionsRenderer implements ListitemRenderer {

        @Override
        public void render(Listitem listitem, Object o, int i) throws Exception {
            CalendarData calendarData = (CalendarData) o;
            listitem.setValue(calendarData);

            if (isDerived()) {
                appendParentCombobox(listitem, calendarData);
            } else {
                appendParentLabel(listitem, calendarData);
            }
            appendStartDatebox(listitem, calendarData);
            appendExpiringDatebox(listitem, calendarData);
            appendSummaryLabel(listitem, calendarData);
            appendOperationsListcell(listitem, calendarData);
            markAsSelected(listitem, calendarData);
            addEventListener(listitem);
        }

        private void appendSummaryLabel(final Listitem listItem, final CalendarData version) {
            BaseCalendar parent = version.getParent();

            List<String> summary = new ArrayList<>();
            for (Days day : Days.values()) {
                if (version.isDefault(day)) {
                    if (parent == null) {
                        summary.add("0");
                    } else {
                        summary.add(_("Inh"));
                    }
                } else {
                    summary.add(asString(version.getCapacityOn(day)));
                }
            }
            Label summaryLabel = new Label(StringUtils.join(summary, " - "));

            Listcell listCell = new Listcell();
            listCell.appendChild(summaryLabel);
            listItem.appendChild(listCell);
        }

        private void appendStartDatebox(final Listitem listItem, final CalendarData version) {
            Datebox datebox = new Datebox();

            final LocalDate dateValidFrom = baseCalendarModel.getValidFrom(version);
            if (dateValidFrom != null) {

                Util.bind(datebox, () -> dateValidFrom.toDateTimeAtStartOfDay().toDate());

                datebox.setDisabled(false);
            } else {
                datebox.setDisabled(true);
            }

            datebox.addEventListener(Events.ON_CHANGE, (EventListener) event -> reloadWorkWeeksList());

            datebox.setConstraint((comp, value) -> {
                try {
                    baseCalendarModel.checkAndChangeStartDate(version, ((Date) value));
                } catch (ValidationException e) {
                    throw new WrongValueException(comp, e.getMessage());
                }
            });

            Listcell listCell = new Listcell();
            listCell.appendChild(datebox);
            listItem.appendChild(listCell);
        }

        private void appendExpiringDatebox(final Listitem listItem, final CalendarData version) {
            Datebox datebox = new Datebox();

            final LocalDate expiringDate = version.getExpiringDate();
            if (expiringDate != null) {
                datebox.setDisabled(false);
            } else {
                datebox.setDisabled(true);
            }

            Util.bind(datebox, () -> {
                LocalDate expiringDate1 = version.getExpiringDate();
                if (expiringDate1 != null) {
                    return expiringDate1.minusDays(1).toDateTimeAtStartOfDay().toDate();
                }

                return null;
            }, value -> {
                LocalDate expiringDate1 = null;
                if (value != null) {
                    expiringDate1 = new LocalDate(value).plusDays(1);
                }
                version.setExpiringDate(expiringDate1);
            });

            datebox.addEventListener(Events.ON_CHANGE, (EventListener) event -> reloadWorkWeeksList());

            datebox.setConstraint((comp, value) -> {
                Date date = ((Date) value);
                try {
                    baseCalendarModel.checkChangeExpiringDate(version, date);
                } catch (ValidationException e) {
                    throw new WrongValueException(comp, e.getMessage());
                }
            });

            Listcell listCell = new Listcell();
            listCell.appendChild(datebox);
            listItem.appendChild(listCell);
        }

        private void appendParentLabel(Listitem listItem, CalendarData version) {
            final Label labelParent = new Label();
            if (version.getParent() != null) {
                labelParent.setValue(version.getParent().getName());
            }

            Listcell listCell = new Listcell();
            listCell.appendChild(labelParent);
            listItem.appendChild(listCell);
        }

        private void appendParentCombobox(final Listitem listItem, final CalendarData version) {
            final Combobox comboParents = new Combobox();
            final List<BaseCalendar> listParents = getParentCalendars();

            for (BaseCalendar parent : listParents) {
                Comboitem comboItem = new Comboitem();
                comboItem.setValue(parent);
                comboItem.setLabel(parent.getName());
                comboItem.setParent(comboParents);

                if ((version.getParent()) != null && (parent.getId().equals(version.getParent().getId()))) {
                    comboParents.setSelectedItem(comboItem);
                }
            }

            comboParents.addEventListener(Events.ON_SELECT, (EventListener) event -> {
                if (comboParents.getSelectedItem() != null) {
                    BaseCalendar parent = comboParents.getSelectedItem().getValue();
                    version.setParent(parent);
                }
            });

            Util.bind(comboParents, () -> comboParents.getSelectedItem(), comboItem -> {
                if ((comboItem != null) && (comboItem.getValue() != null)
                        && (comboItem.getValue() instanceof BaseCalendar)) {

                    BaseCalendar parent = comboItem.getValue();
                    version.setParent(parent);
                }
            });

            Listcell listCell = new Listcell();
            listCell.appendChild(comboParents);
            listItem.appendChild(listCell);
        }

        private void markAsSelected(Listitem item, CalendarData calendarData) {
            CalendarData selected = baseCalendarModel.getCalendarData();
            if ((selected != null) && (calendarData.equals(selected))) {
                item.setSelected(true);
            }
        }

        private void appendOperationsListcell(Listitem item, CalendarData calendarData) {
            Listcell listcell = new Listcell();
            listcell.appendChild(createRemoveButton(calendarData));
            item.appendChild(listcell);
        }

        private Button createRemoveButton(final CalendarData calendarData) {
            Button result = createButton("/common/img/ico_borrar1.png", _("Delete"), "/common/img/ico_borrar.png",
                    "icono",

                    event -> {
                        baseCalendarModel.removeCalendarData(calendarData);
                        reloadWorkWeeksList();
                    });

            if (baseCalendarModel.getBaseCalendar() == null
                    || baseCalendarModel.getBaseCalendar().getCalendarDataVersions().size() == 1) {

                result.setDisabled(true);
            } else {
                result.setDisabled(false);
            }

            return result;
        }

        private Button createButton(String image, String tooltip, String hoverImage, String styleClass,
                EventListener eventListener) {

            Button result = new Button("", image);
            result.setHoverImage(hoverImage);
            result.setSclass(styleClass);
            result.setTooltiptext(tooltip);
            result.addEventListener(Events.ON_CLICK, eventListener);

            return result;
        }

        private void addEventListener(Listitem item) {
            item.addEventListener(Events.ON_CLICK, (EventListener) event -> {
                Listitem item1 = (Listitem) event.getTarget();
                CalendarData calendarData = item1.getValue();

                LocalDate dateValidFrom = baseCalendarModel.getValidFrom(calendarData);
                LocalDate expiringDate = calendarData.getExpiringDate();

                if (dateValidFrom != null) {
                    goToDate(dateValidFrom.toDateTimeAtStartOfDay().toDate());
                } else if (expiringDate != null) {
                    goToDate(expiringDate.minusDays(1).toDateTimeAtStartOfDay().toDate());
                } else {
                    goToDate(new Date());
                }
            });
        }
    }

    public boolean isLastVersion(LocalDate selectedDate) {
        return baseCalendarModel.isLastVersion(selectedDate);
    }

    public boolean isFirstVersion(LocalDate selectedDate) {
        return baseCalendarModel.isFirstVersion(selectedDate);
    }

    public void goToDate(Date date) {
        setSelectedDay(toLocalDate(date));
    }

    public boolean isCreatingNewVersion() {
        return creatingNewVersion;
    }

    public void createNewVersion() {
        creatingNewVersion = true;
        initDatesToCreateNewVersion();
        try {
            Util.reloadBindings(createNewVersionWindow);
            createNewVersionWindow.doModal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void initDatesToCreateNewVersion() {
        this.newWorkWeekStartDate = (new LocalDate()).plusDays(1).toDateTimeAtStartOfDay().toDate();
        this.newWorkWeekExpiringDate = (new LocalDate()).plusDays(2).toDateTimeAtStartOfDay().toDate();
    }

    public void acceptCreateNewVersion() {
        Component compStartDate = createNewVersionWindow.getFellow("startDateValidFromNewVersion");
        LocalDate startDate = getLocalDateFrom((Datebox) compStartDate);

        Component compExpiringDate = createNewVersionWindow.getFellow("expiringDateValidFromNewVersion");
        LocalDate expiringDate = getLocalDateFrom((Datebox) compExpiringDate);

        BaseCalendar selected = null;
        if (isDerived()) {
            Combobox parentCalendars = (Combobox) createNewVersionWindow.getFellow("parentCalendars");
            if (parentCalendars.getSelectedItem() == null) {
                throw new WrongValueException(parentCalendars, _("cannot be empty"));
            }
            selected = parentCalendars.getSelectedItem().getValue();
        }

        try {
            baseCalendarModel.createNewVersion(startDate, expiringDate, selected);
        } catch (IllegalArgumentException e) {
            if (e.getMessage().contains("Wrong expiring date")) {
                throw new WrongValueException(compExpiringDate, _(e.getMessage()));
            } else {
                throw new WrongValueException(compStartDate, _(e.getMessage()));
            }
        }

        Clients.clearWrongValue(compStartDate);
        Clients.clearWrongValue(compExpiringDate);
        creatingNewVersion = false;
        Util.reloadBindings(createNewVersionWindow);

        setSelectedDay(startDate);
        if ((startDate == null) && (expiringDate != null)) {
            setSelectedDay(expiringDate.minusDays(1));
        }

        reloadCurrentWindow();
    }

    private static LocalDate getLocalDateFrom(Datebox datebox) {
        Date value = datebox.getValue();

        return value == null ? null : LocalDate.fromDateFields(value);
    }

    public void cancelNewVersion() {
        creatingNewVersion = false;
        Util.reloadBindings(createNewVersionWindow);
    }

    public Date getDateValidFromNewVersion() {
        return newWorkWeekStartDate;
    }

    public void setDateValidFromNewVersion(Date date) {
        this.newWorkWeekStartDate = date;
    }

    public Date getDateValidToNewVersion() {
        return newWorkWeekExpiringDate;
    }

    public void setDateValidToNewVersion(Date date) {
        this.newWorkWeekExpiringDate = date;
    }

    public List<BaseCalendar> getParentCalendars() {
        return baseCalendarModel.getPossibleParentCalendars();
    }

    public List<CalendarException> getCalendarExceptions() {
        List<CalendarException> calendarExceptions = new ArrayList<>(baseCalendarModel.getCalendarExceptions());
        Collections.sort(calendarExceptions, (o1, o2) -> o1.getDate().compareTo(o2.getDate()));

        return calendarExceptions;
    }

    public ParentCalendarsComboitemRenderer getParentCalendarsComboitemRenderer() {
        return parentCalendarsComboitemRenderer;
    }

    public class ParentCalendarsComboitemRenderer implements ComboitemRenderer {

        @Override
        public void render(Comboitem comboitem, Object o, int i) throws Exception {
            BaseCalendar calendar = (BaseCalendar) o;
            comboitem.setLabel(calendar.getName());
            comboitem.setValue(calendar);

            Combobox combobox = (Combobox) comboitem.getParent();
            if (combobox.getSelectedIndex() != 0) {
                combobox.setSelectedIndex(0);
            }
        }
    }

    public CalendarExceptionRenderer getCalendarExceptionRenderer() {
        return calendarExceptionRenderer;
    }

    public class CalendarExceptionRenderer implements ListitemRenderer {

        @Override
        public void render(Listitem listitem, Object o, int i) throws Exception {
            CalendarException calendarException = (CalendarException) o;
            listitem.setValue(calendarException);

            appendDayListcell(listitem, calendarException);
            appendExceptionTypeListcell(listitem, calendarException);
            appendStandardEffortListcell(listitem, calendarException.getCapacity());
            appendExtraEffortListcell(listitem, calendarException.getCapacity());
            appendCodeListcell(listitem, calendarException);
            appendOriginListcell(listitem, calendarException);
            appendOperationsListcell(listitem, calendarException);

            markAsSelected(listitem, calendarException);

        }

        private void markAsSelected(Listitem item, CalendarException calendarException) {
            LocalDate selectedDay = baseCalendarModel.getSelectedDay();

            if (selectedDay != null && calendarException.getDate().equals(selectedDay))
                item.setSelected(true);
        }
    }

    private void appendDayListcell(Listitem item, CalendarException calendarException) {
        Listcell listcell = new Listcell();
        listcell.appendChild(new Label(calendarException.getDate().toString()));
        item.appendChild(listcell);
    }

    private void appendStandardEffortListcell(Listitem item, Capacity capacity) {
        Listcell listcell = new Listcell();
        listcell.appendChild(new Label(_(capacity.getStandardEffortString())));
        item.appendChild(listcell);
    }

    private void appendExtraEffortListcell(Listitem item, Capacity capacity) {
        Listcell listcell = new Listcell();
        listcell.appendChild(new Label(_(capacity.getExtraEffortString())));
        item.appendChild(listcell);
    }

    private void appendExceptionTypeListcell(Listitem item, CalendarException calendarException) {
        Listcell listcell = new Listcell();
        String type = "";
        if (calendarException.getType() != null) {
            type = calendarException.getType().getName();
        }
        listcell.appendChild(new Label(type));
        item.appendChild(listcell);
    }

    private void appendCodeListcell(final Listitem item, final CalendarException calendarException) {
        item.setValue(calendarException);
        Listcell listcell = new Listcell();
        final Textbox code = new Textbox();

        if (getBaseCalendar() != null) {
            code.setDisabled(getBaseCalendar().isCodeAutogenerated());
        }

        Util.bind(code, () -> calendarException.getCode(), value -> {
            try {
                calendarException.setCode(value);
            } catch (IllegalArgumentException e) {
                throw new WrongValueException(code, e.getMessage());
            }
        });

        code.setConstraint("no empty:" + _("cannot be empty"));

        listcell.appendChild(code);
        item.appendChild(listcell);
    }

    private void appendOriginListcell(Listitem item, CalendarException calendarException) {
        Listcell listcell = new Listcell();
        String origin = _("Inherited");

        if (baseCalendarModel.isOwnException(calendarException))
            origin = _("Direct");

        listcell.appendChild(new Label(origin));
        item.appendChild(listcell);
    }

    private void appendOperationsListcell(Listitem item, CalendarException calendarException) {
        Listcell listcell = new Listcell();
        listcell.appendChild(createRemoveButton(calendarException));
        item.appendChild(listcell);
    }

    private Button createRemoveButton(final CalendarException calendarException) {
        Button result = createButton("/common/img/ico_borrar1.png", _("Delete"), "/common/img/ico_borrar.png",
                "icono", event -> {
                    baseCalendarModel.removeException(calendarException.getDate());
                    reloadDayInformation();
                });

        if (!baseCalendarModel.isOwnException(calendarException)) {
            result.setDisabled(true);
            result.setTooltiptext(_("inherited exception can not be removed"));
        }

        return result;
    }

    private Button createButton(String image, String tooltip, String hoverImage, String styleClass,
            EventListener eventListener) {

        Button result = new Button("", image);
        result.setHoverImage(hoverImage);
        result.setSclass(styleClass);
        result.setTooltiptext(tooltip);
        result.addEventListener(Events.ON_CLICK, eventListener);

        return result;
    }

    public boolean isOwnExceptionDay() {
        return baseCalendarModel.isOwnExceptionDay();
    }

    public boolean isNotOwnExceptionDay() {
        return !isOwnExceptionDay();
    }

    public void updateException() {
        Combobox exceptionTypes = (Combobox) window.getFellow("exceptionTypes");
        CalendarExceptionType type = exceptionTypes.getSelectedItem().getValue();

        Datebox dateboxStartDate = (Datebox) window.getFellow("exceptionStartDate");
        Date startDate = dateboxStartDate.getValue();

        if (startDate == null) {
            throw new WrongValueException(dateboxStartDate, _("You should select a start date for the exception"));
        } else {
            Clients.clearWrongValue(dateboxStartDate);
        }

        Datebox dateboxEndDate = (Datebox) window.getFellow("exceptionEndDate");
        Date endDate = dateboxEndDate.getValue();

        if (endDate == null) {
            throw new WrongValueException(dateboxEndDate, _("Please, select an End Date for the Exception"));
        } else {
            Clients.clearWrongValue(dateboxEndDate);
        }

        if (startDate.compareTo(endDate) > 0) {
            throw new WrongValueException(dateboxEndDate,
                    _("Exception end date should be greater or equals than start date"));
        } else {
            Clients.clearWrongValue(dateboxEndDate);
        }

        Capacity capacity = capacityPicker.getValue();

        baseCalendarModel.updateException(type, LocalDate.fromDateFields(startDate),
                LocalDate.fromDateFields(endDate), capacity);

        reloadDayInformation();
    }

    public String getNameParentCalendar() {
        BaseCalendar parent = baseCalendarModel.getCurrentParent();
        return parent != null ? parent.getName() : "";
    }

    public boolean isResourceCalendar() {
        return baseCalendarModel.isResourceCalendar();
    }

    public List<CalendarAvailability> getCalendarAvailabilities() {
        return baseCalendarModel.getCalendarAvailabilities();
    }

    public CalendarAvailabilityRenderer getCalendarAvailabilityRenderer() {
        return calendarAvailabilityRenderer;
    }

    public class CalendarAvailabilityRenderer implements ListitemRenderer {

        @Override
        public void render(Listitem listitem, Object o, int i) throws Exception {
            CalendarAvailability calendarAvailability = (CalendarAvailability) o;
            listitem.setValue(calendarAvailability);

            appendValidFromListcell(listitem, calendarAvailability);
            appendExpirationDateListcell(listitem, calendarAvailability);
            appendAvailabilityCodeListcell(listitem, calendarAvailability);
            appendOperationsListcell(listitem, calendarAvailability);
        }

        private void appendValidFromListcell(Listitem item, final CalendarAvailability calendarAvailability) {
            Listcell listcell = new Listcell();

            final Datebox datebox = new Datebox();

            Datebox dateboxValidFrom = Util.bind(datebox, () -> {
                LocalDate startDate = calendarAvailability.getStartDate();

                return startDate != null ? startDate.toDateTimeAtStartOfDay().toDate() : null;
            }, value -> {
                LocalDate startDate = new LocalDate(value);
                try {
                    baseCalendarModel.setStartDate(calendarAvailability, startDate);
                } catch (IllegalArgumentException e) {
                    throw new WrongValueException(datebox, e.getMessage());
                }
            });

            listcell.appendChild(dateboxValidFrom);
            item.appendChild(listcell);
        }

        private void appendExpirationDateListcell(Listitem item, final CalendarAvailability calendarAvailability) {
            Listcell listcell = new Listcell();

            final Datebox datebox = new Datebox();

            Datebox dateboxExpirationDate = Util.bind(datebox, () -> {
                LocalDate endDate = calendarAvailability.getEndDate();

                return endDate != null ? endDate.toDateTimeAtStartOfDay().toDate() : null;
            }, value -> {
                try {
                    LocalDate endDate = getAppropiateEndDate(calendarAvailability, value);
                    baseCalendarModel.setEndDate(calendarAvailability, endDate);
                } catch (IllegalArgumentException e) {
                    throw new WrongValueException(datebox, e.getMessage());
                }
            });

            listcell.appendChild(dateboxExpirationDate);
            item.appendChild(listcell);
        }

        private LocalDate getAppropiateEndDate(CalendarAvailability calendarAvailability, Date endDate) {
            if (endDate == null) {
                if (baseCalendarModel.isLastActivationPeriod(calendarAvailability)) {
                    return null;
                } else {
                    throw new IllegalArgumentException(_("End date can only be deleted in the last activation"));
                }
            }

            return new LocalDate(endDate);
        }

        private void appendAvailabilityCodeListcell(Listitem item, final CalendarAvailability availability) {
            item.setValue(availability);
            Listcell listcell = new Listcell();
            final Textbox code = new Textbox();

            if (getBaseCalendar() != null) {
                code.setDisabled(getBaseCalendar().isCodeAutogenerated());
            }

            Util.bind(code, () -> availability.getCode(), value -> {
                try {
                    availability.setCode(value);
                } catch (IllegalArgumentException e) {
                    throw new WrongValueException(code, e.getMessage());
                }
            });

            code.setConstraint("no empty:" + _("cannot be empty"));

            listcell.appendChild(code);
            item.appendChild(listcell);
        }

        private void appendOperationsListcell(Listitem item, CalendarAvailability calendarAvailability) {
            Listcell listcell = new Listcell();
            listcell.appendChild(createRemoveButton(calendarAvailability));
            item.appendChild(listcell);
        }

        private Button createRemoveButton(final CalendarAvailability calendarAvailability) {
            return createButton("/common/img/ico_borrar1.png", _("Delete"), "/common/img/ico_borrar.png", "icono",
                    event -> {
                        baseCalendarModel.removeCalendarAvailability(calendarAvailability);
                        reloadDayInformation();
                        reloadActivationPeriods();
                    });
        }

        private Button createButton(String image, String tooltip, String hoverImage, String styleClass,
                EventListener eventListener) {

            Button result = new Button("", image);
            result.setHoverImage(hoverImage);
            result.setSclass(styleClass);
            result.setTooltiptext(tooltip);
            result.addEventListener(Events.ON_CLICK, eventListener);

            return result;
        }
    }

    public void createCalendarAvailability() {
        baseCalendarModel.createCalendarAvailability();
        reloadDayInformation();
        reloadActivationPeriods();
    }

    private void reloadActivationPeriods() {
        Util.reloadBindings(window.getFellow("calendarAvailabilities"));
    }

    public void reloadExceptionsList() {
        Util.reloadBindings(window.getFellow("exceptionsList"));
    }

    public boolean isNotResourceCalendar() {
        BaseCalendar baseCalendar = baseCalendarModel.getBaseCalendar();
        return !((baseCalendar == null) || (baseCalendar instanceof ResourceCalendar));
    }

    public void validateCalendarExceptionCodes() {
        Listbox listbox = (Listbox) window.getFellow("exceptionsList");
        if (listbox != null) {

            for (int i = 0; i < listbox.getItemCount(); i++) {
                Listitem item = listbox.getItems().get(i);

                if (item.getChildren().size() == 5) {
                    Textbox code = (Textbox) (item.getChildren().get(3)).getFirstChild();

                    if (code != null && !code.isDisabled() && code.getValue().isEmpty()) {
                        throw new WrongValueException(code, _("It cannot be empty"));
                    }
                }
            }
        }
    }

    public void onCheckGenerateCode(Event e) {
        CheckEvent ce = (CheckEvent) e;
        if (ce.isChecked()) {
            try {
                baseCalendarModel.setCodeAutogenerated(ce.isChecked());
            } catch (ConcurrentModificationException err) {
                messagesForUser.showMessage(Level.ERROR, err.getMessage());
            }
        }
        reloadCodeInformation();
    }

    private void reloadCodeInformation() {
        Util.reloadBindings(window.getFellow("txtCode"));
        reloadExceptionsList();
        reloadActivationPeriods();
    }

    public void onSelectException(Event event) {
        Listbox listBox = (Listbox) event.getTarget();
        Listitem item = listBox.getSelectedItem();

        if (item != null) {
            CalendarException calendarException = item.getValue();
            setSelectedDay(calendarException.getDate());
            reloadDayInformation();
        }
    }

    public boolean isVirtualWorker() {
        return baseCalendarModel.isVirtualWorker();
    }

    public Integer getCapacity() {
        return baseCalendarModel.getCapacity();
    }

    public void setCapacity(Integer capacity) {
        baseCalendarModel.setCapacity(capacity);
    }

}