package service.loading;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import component.field.dataconvert.DataConverter;
import component.field.dataconvert.DataConverterProvider;
import component.field.loading.FieldValueByContainer;
import component.field.loading.LoadType;
import model.FieldData;

import ru.cg.webbpm.modules.database.api.bean.TableRow;
import ru.cg.webbpm.modules.database.api.dao.LoadDao;
import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions;
import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumn;
import ru.cg.webbpm.modules.database.bean.filter.EntityFilter;
import ru.cg.webbpm.modules.webkit.beans.Behavior;
import ru.cg.webbpm.modules.webkit.beans.PageObjectNotFoundException;

/**
 * @author r.latypov
 */
public abstract class ContainerValueLoaderServiceBase extends Behavior
    implements ContainerValueLoaderService {

  public LoadDao loadDao;

  @Override
  public List<FieldData> loadOnEventData(Object[] params, List<String> guids) {
    if (loadDao == null || params == null || params.length == 0) {
      return Collections.emptyList();
    }
    validateParametersNumber(params.length);
    return new ArrayList<>(loadData(params, idToColumnMapping(guids)));
  }

  protected abstract void validateParametersNumber(int parametersLength);

  protected abstract boolean loadByPkCondition();

  protected abstract List<EntityFilter> createEntityFilters(Object[] params);

  private Map<String, EntityColumn> idToColumnMapping(List<String> guids) {
    Map<String, FieldValueByContainer<?>> byColumnMapping = getScriptsMapping(guids,
        fieldDefaultValue -> fieldDefaultValue.loadType == LoadType.BY_COLUMN
    );
    if (byColumnMapping.isEmpty()) {
      throw new IllegalArgumentException("No control data to load");
    }
    Map<String, EntityColumn> idToColumnMapping = byColumnMapping.entrySet().stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().getValueColumn()
            )
        );
    return idToColumnMapping;
  }

  private List<FieldData> loadData(Object[] params, Map<String, EntityColumn> idToColumnMapping) {
    Set<EntityColumn> columns = new HashSet<>(idToColumnMapping.values());
    Map<EntityColumn, Object> columnToDataMapping;
    if (loadByPkCondition()) {
      columnToDataMapping = loadDao.loadByPK(columns, params[0].toString()).getColumnToDataMap();
    }
    else {
      LoadOptions loadOptions = new LoadOptions();
      loadOptions.setEntityFilters(createEntityFilters(params));
      List<TableRow> tableRows = loadDao.load(columns, loadOptions);
      columnToDataMapping = tableRows.size() > 0
                            ? tableRows.get(0).getColumnToDataMap()
                            : Collections.emptyMap();
    }

    return idToColumnMapping.entrySet().stream()
        .map(entry -> {
          DataConverter dataConverter = DataConverterProvider.getDataConverter(
              entry.getValue().getType());
          Object convertedValue = dataConverter.convertValueForLoad(
              columnToDataMapping.get(entry.getValue()));
          return new FieldData(entry.getKey(), convertedValue);
        })
        .collect(Collectors.toList());
  }

  private Map<String, FieldValueByContainer<?>> getScriptsMapping(List<String> guids,
      final Predicate<FieldValueByContainer<?>> predicate) {
    return guids.stream()
        .map(guid -> new HashMap.SimpleEntry<>(guid, getFieldOnEventValueScript(guid)))
        .filter(entry -> entry.getValue() != null)
        .filter(entry -> predicate.test(entry.getValue()))
        .collect(Collectors.toMap(HashMap.SimpleEntry::getKey, HashMap.SimpleEntry::getValue));
  }

  private FieldValueByContainer<?> getFieldOnEventValueScript(String guid) {
    try {
      return getScriptInObject(guid, FieldValueByContainer.class);
    }
    catch (PageObjectNotFoundException e) {
      return null;
    }
  }
}
