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

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import javax.enterprise.context.RequestScoped;
import javax.faces.component.UIComponent;
import javax.faces.event.AjaxBehaviorEvent;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.primefaces.component.datatable.DataTable;

import de.ivu.elect.jee.entity.EagerLoadingCollector;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.jee.presentation.Icons;

@RequestScoped
public abstract class AbstractColumnBuilder<E> implements ColumnBuilder<E> {

  private static final long serialVersionUID = 1L;

  @Inject
  protected transient Messages messages;

  @Inject
  protected transient Icons icons;

  @Inject
  protected transient UserLocale userLocale;

  @Inject
  private transient ColumnFilter columnFilter;

  @Inject
  private transient ColumnBuilderReorderEventListener columnBuilderReoderEventListener;

  private transient List<ColumnEntry< ? super E>> columns;

  private boolean initialized;

  private transient Boolean iconPresent;

  private transient Map<ColumnType, Boolean> columnTypeMap;

  public AbstractColumnBuilder() {
    super();
  }

  protected void resetState() {
    columns = null;
    iconPresent = null;
    columnTypeMap = null;
  }

  @Override
  public List<ColumnEntry< ? super E>> getColumns() {
    if (columns == null) {
      columns = getRenderedColumns();
    }

    return columns;
  }

  private List<ColumnEntry< ? super E>> getRenderedColumns() {
    initIfNotInitialized();

    List<ColumnEntry< ? super E>> result = new ArrayList<>();
    List<ColumnEntry< ? super E>> availableColumns = columnFilter.filter(getColumnEntriesSorted(), this);

    for (ColumnEntry< ? super E> columnEntry : availableColumns) {
      if (columnEntry == null) {
        throw new IllegalStateException("ColumnEntry ist null " + this.getClass() + ", " + availableColumns);
      }

      if (columnEntry.isRendered()) {
        result.add(columnEntry);
      }
    }
    return result;
  }

  @Override
  public void initIfNotInitialized() {
    if (!isInitialized()) {
      init();
      setInitialized(true);
    }
  }

  @Override
  public void forceInit() {
    setInitialized(false);
    resetState();
    init();
    setInitialized(true);
  }

  protected void init() {
    initColumns();
  }

  protected abstract void initColumns();

  protected abstract List<ColumnEntry< ? super E>> getColumnEntriesSorted();

  @Override
  public List<ColumnEntry< ? super E>> getAllColumns() {
    return getColumnEntriesSorted();
  }

  @Override
  public boolean isColumnsSortableByValue() {
    return true;
  }

  @Override
  public List<ColumnGroup< ? super E>> getColumnGroups() {
    return new ArrayList<>();
  }

  private boolean isInitialized() {
    return initialized;
  }

  private void setInitialized(boolean initialized) {
    this.initialized = initialized;
  }

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

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

  protected Messages getMessages() {
    return messages;
  }

  protected Icons getIcons() {
    return icons;
  }

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

    return getUserLocale().getLocale();
  }

  protected UserLocale getUserLocale() {
    return userLocale;
  }

  public boolean isColumnTypePresent(ColumnType columnType) {
    if (columnTypeMap == null) {
      columnTypeMap = initColumnTypeMap();
    }

    return columnTypeMap.get(columnType);
  }

  private Map<ColumnType, Boolean> initColumnTypeMap() {
    Map<ColumnType, Boolean> result = new EnumMap<>(ColumnType.class);
    for (ColumnType columnType : ColumnType.values()) {
      result.put(columnType, false);
    }
    for (ColumnEntry< ? super E> columnEntry : getColumns()) {
      result.put(columnEntry.getType(), true);
    }
    return result;
  }

  @Override
  public String getStyleClass(E entity) {
    return "";
  }

  @Override
  public boolean isTypePresentString() {
    return isColumnTypePresent(ColumnType.STRING);
  }

  @Override
  public boolean isTypePresentLong() {
    return isColumnTypePresent(ColumnType.LONG);
  }

  @Override
  public boolean isTypePresentDecimalOneFractionDigit() {
    return isColumnTypePresent(ColumnType.DECIMAL_ONE_FRACTION_DIGIT);
  }

  @Override
  public boolean isTypePresentDecimalOneFractionDigitPercentage() {
    return isColumnTypePresent(ColumnType.DECIMAL_ONE_FRACTION_DIGIT_PERCENTAGE);
  }

  @Override
  public boolean isTypePresentDecimalOneFractionDigitPercentagePoints() {
    return isColumnTypePresent(ColumnType.DECIMAL_ONE_FRACTION_DIGIT_PERCENTAGE_POINTS);
  }

  @Override
  public boolean isTypePresentDecimalTwoFractionDigits() {
    return isColumnTypePresent(ColumnType.DECIMAL_TWO_FRACTION_DIGITS);
  }

  @Override
  public boolean isTypePresentDecimalTwoFractionDigitsPercentage() {
    return isColumnTypePresent(ColumnType.DECIMAL_TWO_FRACTION_DIGITS_PERCENTAGE);
  }

  @Override
  public boolean isTypePresentDecimalTwoFractionDigitsPercentagePoints() {
    return isColumnTypePresent(ColumnType.DECIMAL_TWO_FRACTION_DIGITS_PERCENTAGE_POINTS);
  }

  @Override
  public boolean isTypePresentDecimalThreeFractionDigits() {
    return isColumnTypePresent(ColumnType.DECIMAL_THREE_FRACTION_DIGITS);
  }

  @Override
  public boolean isTypePresentDecimalThreeFractionDigitsPercentage() {
    return isColumnTypePresent(ColumnType.DECIMAL_THREE_FRACTION_DIGITS_PERCENTAGE);
  }

  @Override
  public boolean isTypePresentDecimalThreeFractionDigitsPercentagePoints() {
    return isColumnTypePresent(ColumnType.DECIMAL_THREE_FRACTION_DIGITS_PERCENTAGE_POINTS);
  }

  @Override
  public boolean isTypePresentBoolean() {
    return isColumnTypePresent(ColumnType.BOOLEAN);
  }

  @Override
  public boolean isTypePresentDate() {
    return isColumnTypePresent(ColumnType.DATE);
  }

  @Override
  public boolean isTypePresentTimestamp() {
    return isColumnTypePresent(ColumnType.TIMESTAMP);
  }

  @Override
  public boolean isTypePresentTime() {
    return isColumnTypePresent(ColumnType.TIME);
  }

  @Override
  public boolean isTypePresentEnum() {
    return isColumnTypePresent(ColumnType.ENUM);
  }

  @Override
  public boolean isTypePresentCollection() {
    return isColumnTypePresent(ColumnType.COLLECTION);
  }

  @Override
  public boolean isTypePresentColor() {
    return isColumnTypePresent(ColumnType.COLOR);
  }

  @Override
  public boolean isTypePresentBild() {
    return isColumnTypePresent(ColumnType.BILD);
  }

  @Override
  public boolean isIconPresent() {
    if (iconPresent == null) {
      iconPresent = bestimmeIconPresent();
    }

    return iconPresent;
  }

  protected boolean bestimmeIconPresent() {
    return getColumns().stream().anyMatch(ColumnTypePresent::isIconPresent);
  }

  public void collectEagerLoadings(EagerLoadingCollector<E> eagerLoadingCollector) {
    for (ColumnEntry columnEntry : getRenderedColumns()) {
      eagerLoadingCollector.add(columnEntry.getEagerLoading());
    }
  }

  @Override
  public String getName() {
    return this.getClass().getName();
  }

  @Override
  public boolean isColumnFilterSupported() {
    return true;
  }

  @Override
  public void onRowReorder(AjaxBehaviorEvent ajaxBehaviorEvent) {

    if (ajaxBehaviorEvent == null || ajaxBehaviorEvent.getSource() == null || !(ajaxBehaviorEvent.getSource() instanceof DataTable)) {
      return;
    }

    DataTable table = (DataTable) ajaxBehaviorEvent.getSource();

    List<String> columns = table.getColumns().stream().map(column -> (UIComponent) column).map(colComponent -> (String) colComponent.getAttributes().get("columnName")).filter(StringUtils::isNotBlank)
        .collect(Collectors.toList());

    if (columns.isEmpty()) {
      return;
    }

    columnBuilderReoderEventListener.onRowReorder(columns, this);
  }

  @Override
  public Class< ? > getColumnBuilderClass() {
    return getClass();
  }

  public String getHeaderForColumnInColumnGroup(ColumnEntry< ? super E> columnEntry) {
    initIfNotInitialized();

    String prefix = getColumnGroups().stream().filter(columnGroup -> columnGroup.getColumns().contains(columnEntry)).findFirst().map(columnGroup -> columnGroup.getHeader() + " ").orElse("");

    return prefix + columnEntry.getHeader();
  }

  @Override
  public boolean isSummenZeileBeiExport() {
    return false; // ELECTMUC-1652
  }
}
