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

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Consumer;

import javax.annotation.Nullable;
import javax.faces.convert.Converter;
import javax.faces.validator.Validator;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.jee.cdi.BeanManagerUtil;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.jee.faces.UserTimeZone;
import de.ivu.elect.jee.presentation.Icons;

public abstract class AbstractProperty implements Property {

  private final Set<Property> observingProperties;

  private final Set<Property> observedProperties;

  private PropertyType type;

  private String label;

  private String tooltip;

  private String styleClass;

  private Boolean required;

  private boolean readonly;

  private boolean visibleOnPageLoad;

  private boolean visible;

  private Converter converter;

  private final Collection<Validator> validators = new ArrayList<>();

  private final List<Consumer<Property>> observedPropertyValueChangedConsumers = new ArrayList<>();

  private Messages messages;

  private Icons icons;

  private UserLocale userLocale;

  private UserTimeZone userTimeZone;

  private boolean messageProperty = false;

  private boolean rtl = false;

  private boolean autocomplete = false;

  protected AbstractProperty() {
    super();

    this.observingProperties = new HashSet<>();
    this.observedProperties = new HashSet<>();

    this.type = null;

    this.label = null;
    this.tooltip = null;
    this.styleClass = null;

    this.required = null;
    this.readonly = false;

    this.visibleOnPageLoad = false; // Cf. FormBuilderConfig#isPropertiesHiddenOnPageLoad!
    this.visible = true;
  }

  public abstract PropertyType getDefaultType();

  @Override
  public PropertyType getType() {
    if (type == null) {
      return getDefaultType();
    }

    return type;
  }

  @Override
  public void setType(PropertyType type) {
    this.type = type;
  }

  @Override
  public boolean isAccessableOnPageLoad() {
    return isVisibleOnPageLoad() || mustBeAccessableOnPageLoad();
  }

  protected boolean mustBeAccessableOnPageLoad() {
    return mustBeAccessableOnPageLoadByRequired() || mustBeAccessableOnPageLoadByValue();
  }

  protected boolean mustBeAccessableOnPageLoadByRequired() {
    return !isReadonly() && (getDefaultRequired() || isRequired()) && !hasValue();
  }

  protected boolean mustBeAccessableOnPageLoadByValue() {
    return false;
  }

  protected abstract boolean hasDefaultValue();

  public abstract String getDefaultLabel();

  @Override
  public String getLabel() {
    if (label == null) {
      return getDefaultLabel();
    }

    return label;
  }

  @Override
  public void setLabel(String label) {
    this.label = label;
  }

  @Nullable
  public String getDefaultTooltip() {
    return null;
  }

  @Nullable
  @Override
  public String getTooltip() {
    if (tooltip == null) {
      return getDefaultTooltip();
    }

    return tooltip;
  }

  @Override
  public void setTooltip(String tooltip) {
    this.tooltip = tooltip;
  }

  @Nullable
  public String getDefaultStyleClass() {
    return null;
  }

  @Override
  public String getStyleClass() {
    String result = styleClass;
    if (result == null) {
      result = getDefaultStyleClass();
    }

    if (isRtl()) {
      if (result == null) {
        result = "";
      }
      result += " input-rtl ";
    }
    return result;
  }

  @Override
  public void setStyleClass(String styleClass) {
    this.styleClass = styleClass;
  }

  public void appendStyleClass(String styleClass) {
    String finalStyleClass = this.styleClass;
    if (StringUtils.isBlank(finalStyleClass)) {
      this.styleClass = styleClass;
    }
    this.styleClass = finalStyleClass + " " + styleClass;
  }

  public abstract boolean getDefaultRequired();

  @Override
  public boolean isRequired() {
    if (required == null) {
      return getDefaultRequired();
    }

    return required;
  }

  @Override
  public void setRequired(boolean required) {
    this.required = required;
  }

  @Override
  public boolean isReadonly() {
    return readonly;
  }

  @Override
  public void setReadonly(boolean readonly) {
    this.readonly = readonly;
  }

  @Override
  public boolean isVisibleOnPageLoad() {
    return visibleOnPageLoad;
  }

  @Override
  public void setVisibleOnPageLoad(boolean visibleOnPageLoad) {
    this.visibleOnPageLoad = visibleOnPageLoad;
  }

  @Override
  public boolean isVisible() {
    return visible;
  }

  @Override
  public void setVisible(boolean visible) {
    this.visible = visible;
  }

  void addObservingProperty(AbstractProperty observingProperty) {
    registerObservingPropertyForObservedProperty(this, observingProperty);
  }

  @Override
  public Collection<Property> getObservingProperties() {
    return observingProperties;
  }

  void addObservedProperty(AbstractProperty observedProperty) {
    registerObservingPropertyForObservedProperty(observedProperty, this);
  }

  @Override
  public Collection<Property> getObservedProperties() {
    return observedProperties;
  }

  @Override
  @Deprecated // nicht überschreiben => appendObservedPropertyValueChangedConsumer() verwenden
  public void onObservedPropertyValueChanged(Property observedProperty) {
    observedPropertyValueChangedConsumers.forEach(c -> c.accept(observedProperty));
  }

  @Override
  public void appendObservedPropertyValueChangedConsumer(Consumer<Property> observedPropertyConsumer) {
    observedPropertyValueChangedConsumers.add(observedPropertyConsumer);
  }

  private static void registerObservingPropertyForObservedProperty(AbstractProperty observedProperty, AbstractProperty observingProperty) {
    if (observedProperty != null && observingProperty != null) {
      //ELECTMUC-2604
      //      if (observedProperty.equals(observingProperty) || observingProperty.getObservingProperties().contains(observedProperty)) {
      //        throw new IllegalStateException("Infinite loop - cannot register an observed property to be an observing property!");
      //      }

      observedProperty.observingProperties.add(observingProperty);
      observingProperty.observedProperties.add(observedProperty);
    }
  }

  @Override
  public Collection<Validator> getValidators() {
    return validators;
  }

  public abstract Converter getDefaultConverter();

  @Override
  public Converter getConverter() {
    if (converter == null) {
      return getDefaultConverter();
    }

    return converter;
  }

  @Override
  public void setConverter(Converter converter) {
    this.converter = converter;
  }

  protected String getMessage(Enum< ? > enumValue) {
    return getMessages().lokalisiere(enumValue);
  }

  protected String getMessage(String msgKey, Object... params) {
    return getMessages().getMessageForMessageKey(msgKey, params);
  }

  protected Messages getMessages() {
    if (messages == null) {
      messages = BeanManagerUtil.getBeanByClass(Messages.class);
    }

    return messages;
  }

  protected Icons getIcons() {
    if (icons == null) {
      icons = BeanManagerUtil.getBeanByClass(Icons.class);
    }

    return icons;
  }

  protected Locale getLocale() {
    if (getUserLocale() == null) {
      return Locale.getDefault();
    }

    return getUserLocale().getLocale();
  }

  protected UserLocale getUserLocale() {
    if (userLocale == null) {
      userLocale = BeanManagerUtil.getBeanByClass(UserLocale.class);
    }

    return userLocale;
  }

  protected TimeZone getTimeZone() {
    if (getUserTimeZone() == null) {
      return TimeZone.getDefault();
    }

    return getUserTimeZone().getTimeZone();
  }

  protected UserTimeZone getUserTimeZone() {
    if (userTimeZone == null) {
      userTimeZone = BeanManagerUtil.getBeanByClass(UserTimeZone.class);
    }

    return userTimeZone;
  }

  protected boolean notAllEqual(Object[] one, Object[] another) {
    return !allEqual(one, another);
  }

  protected boolean allEqual(Object[] one, Object[] another) {
    return allNullOrEmpty(one, another) || Arrays.equals(one, another);
  }

  protected boolean notAllNullOrEmpty(Object[] one, Object[] another) {
    return !allNullOrEmpty(one, another);
  }

  protected boolean allNullOrEmpty(Object[] one, Object[] another) {
    return isNullOrEmpty(one) && isNullOrEmpty(another);
  }

  protected boolean notNullOrEmpty(Object[] values) {
    return !isNullOrEmpty(values);
  }

  protected boolean isNullOrEmpty(Object[] values) {
    return values == null || values.length == 0;
  }

  protected boolean notAllEqual(Collection< ? > one, Collection< ? > another) {
    return !allEqual(one, another);
  }

  protected boolean allEqual(Collection< ? > one, Collection< ? > another) {
    return allNullOrEmpty(one, another) || one != null && another != null && one.size() == another.size() && one.containsAll(another);
  }

  protected boolean notAllNullOrEmpty(Collection< ? > one, Collection< ? > another) {
    return !allNullOrEmpty(one, another);
  }

  protected boolean allNullOrEmpty(Collection< ? > one, Collection< ? > another) {
    return isNullOrEmpty(one) && isNullOrEmpty(another);
  }

  protected boolean notNullOrEmpty(Collection< ? > values) {
    return !isNullOrEmpty(values);
  }

  protected boolean isNullOrEmpty(Collection< ? > values) {
    return values == null || values.isEmpty();
  }

  protected boolean notAllEqual(Object one, Object another) {
    return !allEqual(one, another);
  }

  protected boolean allEqual(Object one, Object another) {
    return allNullOrBlank(one, another) || Objects.equals(one, another);
  }

  protected boolean notAllNullOrBlank(Object one, Object another) {
    return !allNullOrBlank(one, another);
  }

  protected boolean allNullOrBlank(Object one, Object another) {
    return isNullOrBlank(one) && isNullOrBlank(another);
  }

  protected boolean notNullOrBlank(Object value) {
    return !isNullOrBlank(value);
  }

  protected boolean isNullOrBlank(Object value) {
    return value == null || value instanceof CharSequence && isBlank((CharSequence) value);
  }

  public boolean isMessageProperty() {
    return messageProperty;
  }

  public void setMessageProperty(boolean messageProperty) {
    this.messageProperty = messageProperty;
  }

  public boolean isRtl() {
    return rtl;
  }

  public void setRtl(boolean rtl) {
    this.rtl = rtl;
  }

  public boolean isAutocomplete() {
    return autocomplete;
  }

  public void setAutocomplete(boolean autocomplete) {
    this.autocomplete = autocomplete;
  }
}
