
package de.ivu.elect.jee.presentation.dynamicform;

import static org.apache.commons.lang3.StringUtils.endsWith;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.annotation.Nullable;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import de.ivu.elect.jee.faces.FacesComponentUtil;
import de.ivu.elect.jee.faces.FacesMessageUtil;

/**
 * ELECTMUC-2753 von-bis-Datumsfelder nebeneinander
 */
public abstract class AbstractTemporalBetweenProperty extends AbstractTemporalProperty {

  private final PropertyType propertyType;

  private final AbstractTemporalProperty template;

  private final String defaultLabel;

  private final boolean valueFromRequired;

  private final Consumer<Date> setterFrom;

  private final Supplier<Date> getterFrom;

  private final boolean valueToRequired;

  private final Consumer<Date> setterTo;

  private final Supplier<Date> getterTo;

  private final boolean propertyCrossFieldValidationEnabled;

  protected AbstractTemporalBetweenProperty(PropertyType propertyType, AbstractTemporalProperty template, String defaultLabel, boolean valueFromRequired, Consumer<Date> setterFrom,
      Supplier<Date> getterFrom, boolean valueToRequired, Consumer<Date> setterTo, Supplier<Date> getterTo, boolean propertyCrossFieldValidationEnabled) {
    super();

    this.propertyType = propertyType;

    this.template = template;

    this.defaultLabel = defaultLabel;

    this.valueFromRequired = valueFromRequired;
    this.setterFrom = setterFrom;
    this.getterFrom = getterFrom;

    this.valueToRequired = valueToRequired;
    this.setterTo = setterTo;
    this.getterTo = getterTo;

    this.propertyCrossFieldValidationEnabled = propertyCrossFieldValidationEnabled;
  }

  @Override
  public PropertyType getDefaultType() {
    return propertyType;
  }

  @Override
  public boolean getDefaultRequired() {
    return valueFromRequired || valueToRequired;
  }

  @Override
  public String getDefaultLabel() {
    return defaultLabel;
  }

  public boolean isValueRequired() {
    return valueFromRequired;
  }

  @Override
  public Date getValue() {
    return getterFrom.get();
  }

  @Override
  public void setValue(Date value) {
    setterFrom.accept(value);
  }

  public boolean isValue2Required() {
    return valueToRequired;
  }

  public Date getValue2() {
    return getterTo.get();
  }

  public void setValue2(Date value) {
    setterTo.accept(value);
  }

  @Override
  public boolean isShowTime() {
    return template.isShowTime();
  }

  @Override
  protected DateFormat createDateFormat(Locale locale) {
    return template.createDateFormat(locale);
  }

  @Override
  public Collection<Validator> getValidators() {
    List<Validator> result = new ArrayList<>(template.getValidators());

    if (propertyCrossFieldValidationEnabled) {
      result.add(new TemporalBetweenPropertyCrossFieldValidator());
    }

    return result;
  }

  class TemporalBetweenPropertyCrossFieldValidator implements Validator {

    private Date valueFromRaw;

    private Date valueToRaw;

    public TemporalBetweenPropertyCrossFieldValidator() {
      super();
    }

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
      if (component != null && value instanceof Date) {
        Map<String, Object> attributes = component.getAttributes();

        if (attributes != null) {
          Object componentId = component.getClientId(context);

          // Cf. /elect-base-jar/src/main/resources/META-INF/contracts/elect/WEB-INF/tags/inputCalendarBetween.xhtml!!!
          Object componentIdFrom = attributes.get("componentIdFrom");
          Object componentIdTo = attributes.get("componentIdTo");

          if (componentId != null && componentIdFrom != null && componentIdTo != null) {
            if (endsWith(componentId.toString(), componentIdFrom.toString())) {
              valueFromRaw = (Date) value;
            } else if (endsWith(componentId.toString(), componentIdTo.toString())) {
              valueToRaw = (Date) value;

              validate(valueFromRaw, valueToRaw, (valueFromCompared, valueToCompared) -> {
                DateFormat dateFormat = getDateFormat();

                throw new ValidatorException(FacesMessageUtil.createErrorMessage(context, "component.inputCalendarBetween.TO_BEFORE_FROM", "component.inputCalendarBetween.TO_BEFORE_FROM_detail",
                    FacesComponentUtil.getLocalizedLabelOrId(component), dateFormat.format(valueFromCompared.getTime()), dateFormat.format(valueToCompared.getTime())));
              });
            }
          }
        }
      }
    }

    public void validate(@Nullable Date valueFromRaw, @Nullable Date valueToRaw, BiConsumer<Calendar, Calendar> valueToBeforeValueFromHandler) {
      if (valueFromRaw != null && valueToRaw != null && !valueFromRaw.equals(valueToRaw)) {
        Calendar valueFromCompared = Calendar.getInstance(getTimeZone(), getLocale());
        valueFromCompared.setTime(valueFromRaw);
        valueFromCompared.set(Calendar.MILLISECOND, 0);

        Calendar valueToCompared = Calendar.getInstance(getTimeZone(), getLocale());
        valueToCompared.setTime(valueToRaw);
        valueToCompared.set(Calendar.MILLISECOND, 0);

        if (PropertyType.INPUT_CALENDAR_BETWEEN.equals(propertyType)) {
          valueFromCompared.set(Calendar.HOUR_OF_DAY, 0);
          valueFromCompared.set(Calendar.MINUTE, 0);
          valueFromCompared.set(Calendar.SECOND, 0);

          valueToCompared.set(Calendar.HOUR_OF_DAY, 0);
          valueToCompared.set(Calendar.MINUTE, 0);
          valueToCompared.set(Calendar.SECOND, 0);
        } else if (PropertyType.INPUT_CALENDAR_TIME_ONLY_BETWEEN.equals(propertyType)) {
          valueFromCompared.set(Calendar.DAY_OF_MONTH, 0);
          valueFromCompared.set(Calendar.MONTH, 0);
          valueFromCompared.set(Calendar.YEAR, 0);

          valueToCompared.set(Calendar.DAY_OF_MONTH, 0);
          valueToCompared.set(Calendar.MONTH, 0);
          valueToCompared.set(Calendar.YEAR, 0);
        }

        if (valueToCompared.before(valueFromCompared)) {
          valueToBeforeValueFromHandler.accept(valueFromCompared, valueToCompared);
        }
      }
    }
  }
}
