package ru.taximaxim.treeviewer.filter;

import ru.taximaxim.treeviewer.utils.ColumnType;
import ru.taximaxim.treeviewer.utils.TriFunction;

import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * Enum for types of column filters
 */
public enum FilterOperation {

    NONE("", (o, s, t) -> true),
    EQUALS("=", (o, s, t) -> equalsByType(o, s, t)),
    NOT_EQUALS("!=", (o, s, t) -> !EQUALS.matchesForType(o, s, t)),
    CONTAINS("~", (o, s, t) -> o.toLowerCase().contains(s.toLowerCase())),
    GREATER(">", (o, s, t) -> greater(o, s, t)),
    GREATER_OR_EQUAL(">=", (o, s, t) -> EQUALS.matchesForType(o, s, t) || GREATER.matchesForType(o, s, t)),
    LESS("<", (o, s, t) -> less(o, s, t)),
    LESS_OR_EQUAL("<=", (o, s, t) -> EQUALS.matchesForType(o, s, t) || LESS.matchesForType(o, s, t));

    private final String conditionText;
    private final TriFunction<String, String, ColumnType, Boolean> testFunction;

    FilterOperation(String conditionText, TriFunction<String, String, ColumnType, Boolean> testFunction) {
        this.conditionText = conditionText;
        this.testFunction = testFunction;
    }

    public boolean matchesForType(String objectValue, String searchValue, ColumnType columnType) {
        return testFunction.apply(objectValue, searchValue, columnType);
    }

    @Override
    public String toString() {
        return conditionText;
    }

    public String getConditionText() {
        return conditionText;
    }

    public static FilterOperation find(String text) {
        FilterOperation[] list = FilterOperation.values();
        for (FilterOperation filterOperation : list) {
            if (filterOperation.getConditionText().equals(text)) {
                return filterOperation;
            }
        }
        return FilterOperation.NONE;
    }

    private static boolean equalsByType(String objectValue, String searchValue, ColumnType columnType) {
        switch (columnType) {
            case INTEGER:
                return testMatches(objectValue, searchValue, FilterOperation::getInteger, (i1, i2) -> i1.equals(i2));
            case DOUBLE:
                return testMatches(objectValue, searchValue, FilterOperation::getDouble, (i1, i2) -> i1.equals(i2));
            case DATE:
            case STRING:
                return objectValue.equals(searchValue);
        }
        return false;
    }

    private static boolean greater(String objectValue, String searchValue, ColumnType columnType) {
        switch (columnType) {
            case INTEGER:
                return testMatches(objectValue, searchValue, FilterOperation::getInteger, (i1, i2) -> i1 > i2);
            case DOUBLE:
                return testMatches(objectValue, searchValue, FilterOperation::getDouble, (i1, i2) -> i1 > i2);
            case DATE:
            case STRING:
                return objectValue.compareTo(searchValue) > 0;
        }
        return false;
    }

    private static boolean less(String objectValue, String searchValue, ColumnType columnType) {
        switch (columnType) {
            case INTEGER:
                return testMatches(objectValue, searchValue, FilterOperation::getInteger, (i1, i2) -> i1 < i2);
            case DOUBLE:
                return testMatches(objectValue, searchValue, FilterOperation::getInteger, (i1, i2) -> i1 < i2);
            case DATE:
            case STRING:
                return objectValue.compareTo(searchValue) < 0;
        }
        return false;
    }

    private static <T> boolean testMatches(String objectValue, String searchValue,
                                           Function<String, Optional<T>> converter, BiFunction<T, T, Boolean> matcher) {
        Optional<T> objectOpt = converter.apply(objectValue);
        Optional<T> searchOpt = converter.apply(searchValue);
        return objectOpt.isPresent() && searchOpt.isPresent() && matcher.apply(objectOpt.get(), searchOpt.get());
    }

    private static Optional<Integer> getInteger(String value) {
        try {
            return Optional.of(Integer.parseInt(value));
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    private static Optional<Double> getDouble(String value) {
        try {
            return Optional.of(Double.parseDouble(value));
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }
}

@@ -280,23 +280,16 @@
 ers%0A */%0A
-public 
 enum Fil
@@ -935,36 +935,39 @@
 nType, Boolean%3E 
-test
+matcher
 Function;%0A%0A    F
@@ -1052,20 +1052,23 @@
 oolean%3E 
-test
+matcher
 Function
@@ -1128,20 +1128,23 @@
    this.
-test
+matcher
 Function
@@ -1146,20 +1146,23 @@
 ction = 
-test
+matcher
 Function
@@ -1174,23 +1174,16 @@
  %7D%0A%0A    
-public 
 boolean 
@@ -1277,20 +1277,23 @@
  return 
-test
+matcher
 Function
@@ -2022,37 +2022,33 @@
          return 
-testM
+m
 atches(objectVal
@@ -2097,33 +2097,23 @@
 er, 
-(i1, i2) -%3E i1.
+Integer::
 equals
-(i2)
 );%0A 
@@ -2151,37 +2151,33 @@
          return 
-testM
+m
 atches(objectVal
@@ -2225,33 +2225,22 @@
 le, 
-(i1, i2) -%3E i1.
+Double::
 equals
-(i2)
 );%0A 
@@ -2552,37 +2552,33 @@
          return 
-testM
+m
 atches(objectVal
@@ -2685,37 +2685,33 @@
          return 
-testM
+m
 atches(objectVal
@@ -3095,37 +3095,33 @@
          return 
-testM
+m
 atches(objectVal
@@ -3236,21 +3236,17 @@
  return 
-testM
+m
 atches(o
@@ -3282,39 +3282,38 @@
 erOperation::get
-Integer
+Double
 , (i1, i2) -%3E i1
@@ -3505,13 +3505,9 @@
 ean 
-testM
+m
 atch
@@ -3549,20 +3549,16 @@
 hValue,%0A
-    
         

Fix wrong type, change access level