package com.gentics.mesh.graphql.filter;

import com.gentics.mesh.core.data.GraphFieldContainer;
import com.gentics.mesh.core.data.node.field.BooleanGraphField;
import com.gentics.mesh.core.data.node.field.DateGraphField;
import com.gentics.mesh.core.data.node.field.HtmlGraphField;
import com.gentics.mesh.core.data.node.field.StringGraphField;
import com.gentics.mesh.core.rest.common.FieldTypes;
import com.gentics.mesh.core.rest.schema.FieldSchema;
import com.gentics.mesh.core.rest.schema.SchemaModel;
import com.gentics.mesh.graphql.context.GraphQLContext;
import com.gentics.mesh.graphqlfilter.filter.BooleanFilter;
import com.gentics.mesh.graphqlfilter.filter.DateFilter;
import com.gentics.mesh.graphqlfilter.filter.FilterField;
import com.gentics.mesh.graphqlfilter.filter.MainFilter;
import com.gentics.mesh.graphqlfilter.filter.StringFilter;

import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FieldFilter extends MainFilter<GraphFieldContainer> {
    private static final String NAME_PREFIX = "FieldFilter.";

    // TODO Remove this after all types are supported
    private static final Set<String> availableTypes = Stream.of(
        FieldTypes.STRING, FieldTypes.HTML, FieldTypes.DATE, FieldTypes.BOOLEAN
    ).map(FieldTypes::toString).collect(Collectors.toSet());

    public static FieldFilter filter(GraphQLContext context, SchemaModel container) {
        return context.getOrStore(NAME_PREFIX + container.getName(), () -> new FieldFilter(container));
    }

    private final SchemaModel schema;

    private FieldFilter(SchemaModel container) {
        super(container.getName() + "FieldFilter", "Filters by fields");
        this.schema = container;
    }

    @Override
    protected List<FilterField<GraphFieldContainer, ?>> getFilters() {
        return schema.getFields().stream()
            // filters fields where the Filter is not implemented
            // TODO remove this after all types are supported
            .filter(field -> availableTypes.contains(field.getType()))
            .map(this::createFieldFilter)
            .collect(Collectors.toList());
    }

    private FilterField<GraphFieldContainer, ?> createFieldFilter(FieldSchema fieldSchema) {
        String schemaName = schema.getName();
        String name = fieldSchema.getName();
        String description = "Filters by the field " + name;
        FieldTypes type = FieldTypes.valueByName(fieldSchema.getType());
        switch (type) {
            case STRING:
                return new FieldMappedFilter<>(name, description, StringFilter.filter(), node -> getOrNull(node.getString(name), StringGraphField::getString), schemaName);
            case HTML:
                return new FieldMappedFilter<>(name, description, StringFilter.filter(), node -> getOrNull(node.getHtml(name), HtmlGraphField::getHTML), schemaName);
            case DATE:
                return new FieldMappedFilter<>(name, description, DateFilter.filter(), node -> getOrNull(node.getDate(name), DateGraphField::getDate), schemaName);
            case BOOLEAN:
                return new FieldMappedFilter<>(name, description, BooleanFilter.filter(), node -> getOrNull(node.getBoolean(name), BooleanGraphField::getBoolean), schemaName);
            // TODO correctly implement other types
            case NUMBER:
            case BINARY:
            case LIST:
            case NODE:
            case MICRONODE:
                return new FieldMappedFilter<>(name, description, StringFilter.filter(), node -> "bogus", schemaName);
            default:
                throw new RuntimeException("Unexpected type " + type);
        }
    }

    private static <T, R> R getOrNull(T nullableValue, Function<T, R> mapper) {
        if (nullableValue == null) {
            return null;
        } else {
            return mapper.apply(nullableValue);
        }
    }
}

@@ -837,22 +837,110 @@
 ter.
-StringFilter;%0A
+NumberFilter;%0Aimport com.gentics.mesh.graphqlfilter.filter.StringFilter;%0A%0Aimport java.math.BigDecimal;
 %0Aimp
@@ -1414,16 +1414,35 @@
 .BOOLEAN
+, FieldTypes.NUMBER
 %0A    ).m
@@ -2760,32 +2760,52 @@
 Filter.filter(),
+%0A                   
  node -%3E getOrNu
@@ -2975,32 +2975,52 @@
 Filter.filter(),
+%0A                   
  node -%3E getOrNu
@@ -3182,32 +3182,52 @@
 Filter.filter(),
+%0A                   
  node -%3E getOrNu
@@ -3395,32 +3395,52 @@
 Filter.filter(),
+%0A                   
  node -%3E getOrNu
@@ -3502,32 +3502,271 @@
 ), schemaName);%0A
+            case NUMBER:%0A                return new FieldMappedFilter%3C%3E(name, description, NumberFilter.filter(),%0A                    node -%3E getOrNull(node.getNumber(name), val -%3E new BigDecimal(val.getNumber().toString())), schemaName);%0A
             // T
@@ -3805,33 +3805,8 @@
 pes%0A
-            case NUMBER:%0A
     
@@ -3904,127 +3904,8 @@
 DE:%0A
-                return new FieldMappedFilter%3C%3E(name, description, StringFilter.filter(), node -%3E %22bogus%22, schemaName);%0A
     

Add number fields