commit f30ebb742c4cc2044f1b2aa6e258aac226a476c7
Author: mlinha <mlinha@students.zcu.cz>
Date:   Wed Aug 5 21:35:15 2020 +0200

    re #8113 re #7885 all functions implemented, contingency table generation improved; re #8154 value filtering added; no comments

diff --git a/src/main/java/vldc/aswi/database/DatabaseInterface.java b/src/main/java/vldc/aswi/database/DatabaseInterface.java
index 631c3185..279dd373 100644
--- a/src/main/java/vldc/aswi/database/DatabaseInterface.java
+++ b/src/main/java/vldc/aswi/database/DatabaseInterface.java
@@ -97,36 +97,33 @@ public class DatabaseInterface {
     public Map<String, TableColumn> executeQueryAndReturnTableColumns(String sqlQuery) {
         return jdbcTemplate.query(
                 sqlQuery,
-                new ResultSetExtractor<Map<String, TableColumn>>() {
-                    @Override
-                    public Map<String, TableColumn> extractData(ResultSet resultSet) throws SQLException, DataAccessException {
-                        List<TableColumn> tableColumnsList = new ArrayList<>();
+                resultSet -> {
+                    List<TableColumn> tableColumnsList = new ArrayList<>();
 
-                        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
-                        int columnCount = resultSetMetaData.getColumnCount();
+                    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+                    int columnCount = resultSetMetaData.getColumnCount();
 
-                        // The column count starts from 1.
-                        for (int i = 1; i <= columnCount; i++ ) {
-                            String columnName = resultSetMetaData.getColumnName(i);
-                            tableColumnsList.add(new TableColumn(columnName));
-                        }
+                    // The column count starts from 1.
+                    for (int i = 1; i <= columnCount; i++ ) {
+                        String columnName = resultSetMetaData.getColumnName(i);
+                        tableColumnsList.add(new TableColumn(columnName));
+                    }
 
-                        // Add values from row to every column.
-                        while(resultSet.next()) {
-                            for (int i = 1; i <= columnCount; i++) {
-                                tableColumnsList.get(i - 1).addValue(resultSet.getString(i));
-                            }
+                    // Add values from row to every column.
+                    while(resultSet.next()) {
+                        for (int i = 1; i <= columnCount; i++) {
+                            tableColumnsList.get(i - 1).addValue(resultSet.getString(i));
                         }
+                    }
 
-                        Map<String, TableColumn> tableColumnMap = new HashMap<>();
-
-                        // Create map, where key is tablecolumn name.
-                        for (TableColumn column : tableColumnsList) {
-                            tableColumnMap.put(column.getName(), column);
-                        }
+                    Map<String, TableColumn> tableColumnMap = new HashMap<>();
 
-                        return tableColumnMap;
+                    // Create map, where key is tablecolumn name.
+                    for (TableColumn column : tableColumnsList) {
+                        tableColumnMap.put(column.getName(), column);
                     }
+
+                    return tableColumnMap;
                 }
         );
     }
diff --git a/src/main/java/vldc/aswi/model/table/BooleanWrapper.java b/src/main/java/vldc/aswi/model/table/BooleanWrapper.java
new file mode 100644
index 00000000..a6563e79
--- /dev/null
+++ b/src/main/java/vldc/aswi/model/table/BooleanWrapper.java
@@ -0,0 +1,18 @@
+package vldc.aswi.model.table;
+
+public class BooleanWrapper {
+
+    private boolean value;
+
+    public BooleanWrapper(boolean value) {
+        this.value = value;
+    }
+
+    public boolean isValue() {
+        return value;
+    }
+
+    public void setValue(boolean value) {
+        this.value = value;
+    }
+}
diff --git a/src/main/java/vldc/aswi/model/table/DoubleWrapper.java b/src/main/java/vldc/aswi/model/table/DoubleWrapper.java
new file mode 100644
index 00000000..09a4b182
--- /dev/null
+++ b/src/main/java/vldc/aswi/model/table/DoubleWrapper.java
@@ -0,0 +1,18 @@
+package vldc.aswi.model.table;
+
+public class DoubleWrapper {
+
+    private double value;
+
+    public DoubleWrapper(double value) {
+        this.value = value;
+    }
+
+    public double getValue() {
+        return value;
+    }
+
+    public void setValue(double value) {
+        this.value = value;
+    }
+}
diff --git a/src/main/java/vldc/aswi/model/table/Node.java b/src/main/java/vldc/aswi/model/table/Node.java
index ca6234eb..3bc64e3e 100644
--- a/src/main/java/vldc/aswi/model/table/Node.java
+++ b/src/main/java/vldc/aswi/model/table/Node.java
@@ -1,7 +1,6 @@
 package vldc.aswi.model.table;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -9,6 +8,22 @@ import java.util.List;
  */
 public class Node {
 
+    private double totalResult;
+
+    private double totalResultAvg;
+
+    private double totalResultAvgSum;
+
+    private double totalResultSum;
+
+    private double totalMin;
+
+    private double totalMax;
+
+    private boolean isFirstMin;
+
+    private boolean isFirstMax;
+
     /**
      * Values of the nodes on a path the the node.
      */
@@ -18,6 +33,8 @@ public class Node {
      * Creates a node.
      */
     public Node() {
+        isFirstMin = true;
+        isFirstMax = true;
         this.values = new ArrayList<>();
     }
 
@@ -26,6 +43,8 @@ public class Node {
      * @param node node to be copied
      */
     public Node(Node node) {
+        isFirstMin = true;
+        isFirstMax = true;
         this.values = new ArrayList<>(node.values);
     }
 
@@ -44,4 +63,68 @@ public class Node {
     public void setValues(List<String> values) {
         this.values = values;
     }
+
+    public double getTotalResult() {
+        return totalResult;
+    }
+
+    public void setTotalResult(double totalResult) {
+        this.totalResult = totalResult;
+    }
+
+    public double getTotalResultAvg() {
+        return totalResultAvg;
+    }
+
+    public void setTotalResultAvg(double totalResultAvg) {
+        this.totalResultAvg = totalResultAvg;
+    }
+
+    public double getTotalResultAvgSum() {
+        return totalResultAvgSum;
+    }
+
+    public void setTotalResultAvgSum(double totalResultAvgSum) {
+        this.totalResultAvgSum = totalResultAvgSum;
+    }
+
+    public double getTotalResultSum() {
+        return totalResultSum;
+    }
+
+    public void setTotalResultSum(double totalResultSum) {
+        this.totalResultSum = totalResultSum;
+    }
+
+    public double getTotalMin() {
+        return totalMin;
+    }
+
+    public void setTotalMin(double totalMin) {
+        this.totalMin = totalMin;
+    }
+
+    public double getTotalMax() {
+        return totalMax;
+    }
+
+    public void setTotalMax(double totalMax) {
+        this.totalMax = totalMax;
+    }
+
+    public boolean isFirstMin() {
+        return isFirstMin;
+    }
+
+    public void setFirstMin(boolean firstMin) {
+        isFirstMin = firstMin;
+    }
+
+    public boolean isFirstMax() {
+        return isFirstMax;
+    }
+
+    public void setFirstMax(boolean firstMax) {
+        isFirstMax = firstMax;
+    }
 }
diff --git a/src/main/java/vldc/aswi/model/table/ValueFunction.java b/src/main/java/vldc/aswi/model/table/ValueFunction.java
index e7bc8593..2c484fc4 100644
--- a/src/main/java/vldc/aswi/model/table/ValueFunction.java
+++ b/src/main/java/vldc/aswi/model/table/ValueFunction.java
@@ -6,39 +6,70 @@ package vldc.aswi.model.table;
  */
 public class ValueFunction {
 
+    private double totalResult = 0.0;
+
+    private double totalResultAvg;
+
+    private double totalResultAvgSum;
+
+    private double totalResultSum;
+
+    private double totalMin;
+
+    private double totalMax;
+
+    private boolean isFirstMin = true;
+
+    private boolean isFirstMax = true;
+
     /**
      * Value
      */
-    private String value;
+    private String nameOfSelect;
+
+    private String name;
+
     /**
      * Function
      */
     private String function;
 
+    private String type;
+
     /**
      * Creates an instance of the class
-     * @param value value
+     * @param nameOfSelect value
      * @param function function
      */
-    public ValueFunction(String value, String function) {
-        this.value = value;
+    public ValueFunction(String nameOfSelect, String name, String function, String type) {
+        this.nameOfSelect = nameOfSelect;
+        this.name = name;
         this.function = function;
+        this.type = type;
     }
 
     /**
      * Gets value.
      * @return value
      */
-    public String getValue() {
-        return value;
+    public String getNameOfSelect() {
+        return nameOfSelect;
     }
 
     /**
      * Sets value.
-     * @param value value
+     * @param nameOfSelect value
      */
-    public void setValue(String value) {
-        this.value = value;
+    public void setNameOfSelect(String nameOfSelect) {
+        this.nameOfSelect = nameOfSelect;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
     }
 
     /**
@@ -56,4 +87,76 @@ public class ValueFunction {
     public void setFunction(String function) {
         this.function = function;
     }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public double getTotalResult() {
+        return totalResult;
+    }
+
+    public void setTotalResult(double totalResult) {
+        this.totalResult = totalResult;
+    }
+
+    public double getTotalResultAvg() {
+        return totalResultAvg;
+    }
+
+    public void setTotalResultAvg(double totalResultAvg) {
+        this.totalResultAvg = totalResultAvg;
+    }
+
+    public double getTotalResultAvgSum() {
+        return totalResultAvgSum;
+    }
+
+    public void setTotalResultAvgSum(double totalResultAvgSum) {
+        this.totalResultAvgSum = totalResultAvgSum;
+    }
+
+    public double getTotalResultSum() {
+        return totalResultSum;
+    }
+
+    public void setTotalResultSum(double totalResultSum) {
+        this.totalResultSum = totalResultSum;
+    }
+
+    public double getTotalMin() {
+        return totalMin;
+    }
+
+    public void setTotalMin(double totalMin) {
+        this.totalMin = totalMin;
+    }
+
+    public double getTotalMax() {
+        return totalMax;
+    }
+
+    public void setTotalMax(double totalMax) {
+        this.totalMax = totalMax;
+    }
+
+    public boolean isFirstMin() {
+        return isFirstMin;
+    }
+
+    public void setFirstMin(boolean firstMin) {
+        isFirstMin = firstMin;
+    }
+
+    public boolean isFirstMax() {
+        return isFirstMax;
+    }
+
+    public void setFirstMax(boolean firstMax) {
+        isFirstMax = firstMax;
+    }
 }
diff --git a/src/main/java/vldc/aswi/model/table/contingencyTable/ContingencyTableRow.java b/src/main/java/vldc/aswi/model/table/contingencyTable/ContingencyTableRow.java
index 2d5458e1..614c230b 100644
--- a/src/main/java/vldc/aswi/model/table/contingencyTable/ContingencyTableRow.java
+++ b/src/main/java/vldc/aswi/model/table/contingencyTable/ContingencyTableRow.java
@@ -37,6 +37,14 @@ public class ContingencyTableRow {
         cells.add(cell);
     }
 
+    /**
+     * Add new cells from the list into current row.
+     * @param cells - List of new cells.
+     */
+    public void addTableRowCells(List<ContingencyTableRowCell> cells) {
+        this.cells.addAll(cells);
+    }
+
     /**
      * Check if current row is header or not.
      * @return True if current row is header, false if not.
diff --git a/src/main/java/vldc/aswi/service/SqlQueryManagerImpl.java b/src/main/java/vldc/aswi/service/SqlQueryManagerImpl.java
index 11d35a49..fb25935f 100644
--- a/src/main/java/vldc/aswi/service/SqlQueryManagerImpl.java
+++ b/src/main/java/vldc/aswi/service/SqlQueryManagerImpl.java
@@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import vldc.aswi.database.DatabaseInterface;
-import vldc.aswi.domain.Assembly;
 import vldc.aswi.domain.Configuration;
 import vldc.aswi.domain.Function;
 import vldc.aswi.domain.parameter.ParameterInConfiguration;
@@ -14,7 +13,6 @@ import vldc.aswi.model.table.ValueFunction;
 import vldc.aswi.model.table.contingencyTable.ContingencyTableRow;
 import vldc.aswi.utils.Converter;
 
-import javax.sql.DataSource;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
@@ -23,22 +21,28 @@ import java.util.concurrent.TimeUnit;
  */
 @Service
 @Slf4j
-public class SqlQueryManagerImpl implements SqlQueryManager{
+public class SqlQueryManagerImpl implements SqlQueryManager {
 
-    /** Autowired databaseInterface component. */
+    /**
+     * Autowired databaseInterface component.
+     */
     @Autowired
     private DatabaseInterface databaseInterface;
-    
+
     /**
      * Get list of  contingencyTableRow.
-     * @param sqlQuery
-     * @param parameterInConfigurations
+     *
+     * @param configuration - configuration from which table will be created.
      * @return List of contingencyTableRow.
      */
     @Override
-    public List<ContingencyTableRow> getContingencyTableRow(String sqlQuery, List<ParameterInConfiguration> parameterInConfigurations) {
+    public List<ContingencyTableRow> getContingencyTableRow(Configuration configuration) {
         long startTime = System.nanoTime();
-        Map<String, TableColumn> tableColumns = this.databaseInterface.executeQueryAndReturnTableColumns(sqlQuery);
+
+        Map<String, TableColumn> tableColumns = this.databaseInterface.executeQueryAndReturnTableColumns(
+                generateFullSQLQuery(configuration.getAssembly().getSQLQuery(), configuration.getParametersInConfiguration())
+        );
+
         long stopTime = System.nanoTime();
         System.out.println("Select:" + TimeUnit.MILLISECONDS.convert((stopTime - startTime), TimeUnit.NANOSECONDS));
         List<NameUserName> rowNames = new ArrayList<>();
@@ -46,31 +50,15 @@ public class SqlQueryManagerImpl implements SqlQueryManager{
         List<ValueFunction> valueFunctions = new ArrayList<>();
 
         // Create a copy of list.
-        List<ParameterInConfiguration> copiedParametersInConfiguration = new ArrayList<>(parameterInConfigurations);
+        List<ParameterInConfiguration> copiedParametersInConfiguration = new ArrayList<>(configuration.getParametersInConfiguration());
 
         // sort parameters in configuration to have them in order set by user
         Collections.sort(copiedParametersInConfiguration);
 
-        // TODO: 28.05.2020 map would be better
-        for(ParameterInConfiguration parameterInConfiguration : copiedParametersInConfiguration) {
-            if (parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Řádek")) {
-                rowNames.add(new NameUserName(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
-                        parameterInConfiguration.getColumnName()));
-            }
-            else if (parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Sloupec")) {
-                columnNames.add(new NameUserName(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
-                        parameterInConfiguration.getColumnName()));
-            }
-            else if(parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Hodnota")) {
-                for (Function function : parameterInConfiguration.getFunctions()) {
-                    valueFunctions.add(new ValueFunction(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
-                            function.getName().toUpperCase()));
-                }
-            }
-        }
+        initializeContingencyTableDataLists(rowNames, columnNames, valueFunctions, configuration.getParametersInConfiguration());
 
-        Converter converter = new Converter();
-        return converter.convertTableColumnsToContingencyTableRows(tableColumns, rowNames, columnNames, valueFunctions);
+        Converter converter = new Converter(tableColumns, rowNames, columnNames, valueFunctions, true);
+        return converter.convertTableColumnsToContingencyTableRows();
     }
 
     /**
@@ -91,4 +79,126 @@ public class SqlQueryManagerImpl implements SqlQueryManager{
     public List<String> getNameOfColumnsFromQuery(String sqlQuery) {
         return this.databaseInterface.getNameOfColumnsFromQuery(sqlQuery);
     }
+
+    private void initializeContingencyTableDataLists(List<NameUserName> rowNames, List<NameUserName> columnNames,
+                                                     List<ValueFunction> valueFunctions, List<ParameterInConfiguration> parametersInConfiguration) {
+        for (ParameterInConfiguration parameterInConfiguration : parametersInConfiguration) {
+            if (parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Řádek")) {
+                rowNames.add(new NameUserName(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
+                        parameterInConfiguration.getColumnName()));
+            } else if (parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Sloupec")) {
+                columnNames.add(new NameUserName(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
+                        parameterInConfiguration.getColumnName()));
+            } else if (parameterInConfiguration.getLocation() != null && parameterInConfiguration.getLocation().getName().equals("Hodnota")) {
+                for (Function function : parameterInConfiguration.getFunctions()) {
+                    valueFunctions.add(new ValueFunction(parameterInConfiguration.getParameter().getNameOfSelect().toUpperCase(),
+                            parameterInConfiguration.getParameter().getName(), function.getName().toUpperCase(),
+                            parameterInConfiguration.getParameter().getParameterType().getName()));
+                }
+            }
+        }
+    }
+
+    private String generateFullSQLQuery(String basicSQLQuery, List<ParameterInConfiguration> parametersInConfiguration) {
+        StringBuilder finalSQLQuery = new StringBuilder("SELECT * FROM (");
+        finalSQLQuery.append(basicSQLQuery);
+        finalSQLQuery.append(")");
+
+        boolean isWhereDefined = false;
+
+        for (ParameterInConfiguration parameterInConfiguration : parametersInConfiguration) {
+            if (parameterInConfiguration.getOperator() != null && !parameterInConfiguration.getOperator().getName().equals("zadny") &&
+                    parameterInConfiguration.getOperatorValue() != null && !parameterInConfiguration.getOperatorValue().equals("")) {
+                String[] values = parameterInConfiguration.getOperatorValue().split(";");
+                if (!isWhereDefined) {
+                    finalSQLQuery.append(" WHERE ");
+                    isWhereDefined = true;
+                } else {
+                    finalSQLQuery.append(" AND ");
+                }
+
+                finalSQLQuery.append(parameterInConfiguration.getParameter().getNameOfSelect());
+                finalSQLQuery.append(" ");
+                finalSQLQuery.append(parameterInConfiguration.getOperator().getName());
+                finalSQLQuery.append(" ");
+                switch (parameterInConfiguration.getOperator().getName()) {
+                    case "BETWEEN":
+                    case "NOT BETWEEN":
+                        finalSQLQuery.append(betweenNotBetween(values, parameterInConfiguration.getParameter().getParameterType().getName()));
+                        break;
+                    case "IN":
+                    case "NOT IN":
+                        finalSQLQuery.append(inNotIn(values, parameterInConfiguration.getParameter().getParameterType().getName()));
+                        break;
+                    case "LIKE":
+                    case "NOT LIKE":
+                        finalSQLQuery.append(likeNotLike(values, parameterInConfiguration.getParameter().getParameterType().getName()));
+                        break;
+                    default:
+                        finalSQLQuery.append(otherOperators(values, parameterInConfiguration.getParameter().getParameterType().getName()));
+                        break;
+                }
+
+            }
+        }
+
+        return finalSQLQuery.toString();
+    }
+
+    private String likeNotLike(String[] values, String type) {
+        StringBuilder SQLQueryPart = new StringBuilder();
+        addQuotes(type, SQLQueryPart);
+        SQLQueryPart.append(values[0]);
+        addQuotes(type, SQLQueryPart);
+
+        return SQLQueryPart.toString();
+    }
+
+    private String inNotIn(String[] values, String type) {
+        StringBuilder SQLQueryPart = new StringBuilder();
+        SQLQueryPart.append("(");
+        for (int i = 0; i < values.length; i++) {
+            addQuotes(type, SQLQueryPart);
+            SQLQueryPart.append(values[i]);
+            addQuotes(type, SQLQueryPart);
+            if (i < values.length - 1) {
+                SQLQueryPart.append(",");
+            }
+        }
+        SQLQueryPart.append(")");
+
+        return SQLQueryPart.toString();
+    }
+
+    private String betweenNotBetween(String[] values, String type) {
+        StringBuilder SQLQueryPart = new StringBuilder();
+
+        addQuotes(type, SQLQueryPart);
+        SQLQueryPart.append(values[0]);
+        addQuotes(type, SQLQueryPart);
+
+        SQLQueryPart.append(" AND ");
+
+        addQuotes(type, SQLQueryPart);
+        SQLQueryPart.append(values[1]);
+        addQuotes(type, SQLQueryPart);
+
+        return SQLQueryPart.toString();
+    }
+
+    private String otherOperators(String[] values, String type) {
+        StringBuilder SQLQueryPart = new StringBuilder();
+
+        addQuotes(type, SQLQueryPart);
+        SQLQueryPart.append(values[0]);
+        addQuotes(type, SQLQueryPart);
+
+        return SQLQueryPart.toString();
+    }
+
+    private void addQuotes(String type, StringBuilder SQLQueryPart) {
+        if (type.equals("Text") || type.equals("Datum")) {
+            SQLQueryPart.append("'");
+        }
+    }
 }
diff --git a/src/main/java/vldc/aswi/utils/Converter.java b/src/main/java/vldc/aswi/utils/Converter.java
index c843d0e9..f5717949 100644
--- a/src/main/java/vldc/aswi/utils/Converter.java
+++ b/src/main/java/vldc/aswi/utils/Converter.java
@@ -1,13 +1,10 @@
 package vldc.aswi.utils;
 
-import vldc.aswi.model.table.NameUserName;
-import vldc.aswi.model.table.Node;
-import vldc.aswi.model.table.TableColumn;
-import vldc.aswi.model.table.ValueFunction;
+import vldc.aswi.model.table.*;
 import vldc.aswi.model.table.contingencyTable.ContingencyTableRow;
 import vldc.aswi.model.table.contingencyTable.ContingencyTableRowCell;
 
-import javax.persistence.criteria.CriteriaBuilder;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -18,28 +15,37 @@ import java.util.concurrent.TimeUnit;
  */
 public class Converter {
 
-    private List<Node> rowNodes;
+    private final Map<String, TableColumn> tableColumns;
+    private final List<NameUserName> rowNames;
+    private final List<NameUserName> columnNames;
+    private final List<ValueFunction> valueFunctions;
+
     private List<Node> columnNodes;
+    private final DecimalFormat decimalFormat = new DecimalFormat("0.###");
+
+    public Converter(Map<String, TableColumn> tableColumns, List<NameUserName> rowNames, List<NameUserName> columnNames,
+                     List<ValueFunction> valueFunctions) {
+        this.tableColumns = tableColumns;
+        this.rowNames = rowNames;
+        this.columnNames = columnNames;
+        this.valueFunctions = valueFunctions;
+    }
 
     /**
      * Convert Map of TableColumns into list of ContingencyTableRow.
-     * @param tableColumns - Map of TableColumn.
      * @return List of ContingencyTableRow.
      */
-    public List<ContingencyTableRow> convertTableColumnsToContingencyTableRows(Map<String, TableColumn> tableColumns,
-                                                                                      List<NameUserName> rowNames,
-                                                                                      List<NameUserName> columnNames,
-                                                                               List<ValueFunction> valueFunctions) {
+    public List<ContingencyTableRow> convertTableColumnsToContingencyTableRows() {
         long startTime = System.nanoTime();
         List<ContingencyTableRow> contingencyTableRows = new ArrayList<>();
 
         long startTime2 = System.nanoTime();
-        List<ContingencyTableRow> headerRows = createHeaderOnlyRows(tableColumns, columnNames, valueFunctions);
+        List<ContingencyTableRow> headerRows = createHeaderOnlyRows();
         long stopTime2 = System.nanoTime();
         System.out.println("Headers:" + TimeUnit.MILLISECONDS.convert((stopTime2 - startTime2), TimeUnit.NANOSECONDS));
 
         long startTime3 = System.nanoTime();
-        List<ContingencyTableRow> dataRows = createDataRows(tableColumns, columnNames, rowNames, valueFunctions);
+        List<ContingencyTableRow> dataRows = createDataRows();
         long stopTime3 = System.nanoTime();
         System.out.println("Rows:" + TimeUnit.MILLISECONDS.convert((stopTime3 - startTime3), TimeUnit.NANOSECONDS));
 
@@ -54,20 +60,16 @@ public class Converter {
 
     /**
      * Generates header rows.
-     * @param tableColumns Map of TableColumn
-     * @param columnNames list of column and user column names
-     * @param valueFunctions list of values and their functions
      * @return list of contingency table rows
      */
-    private List<ContingencyTableRow> createHeaderOnlyRows(Map<String, TableColumn> tableColumns,
-                                                           List<NameUserName> columnNames, List<ValueFunction> valueFunctions) {
+    private List<ContingencyTableRow> createHeaderOnlyRows() {
         List<List<String>> values = new ArrayList<>();
         columnNodes = new ArrayList<>();
         List<ContingencyTableRow> headerRows = new ArrayList<>();
 
         List<String> valueOptions = new ArrayList<>();
         for (ValueFunction valueFunction : valueFunctions) {
-            valueOptions.add(valueFunction.getFunction() + " z " + valueFunction.getValue());
+            valueOptions.add(valueFunction.getFunction() + " z " + valueFunction.getName());
         }
         values.add(valueOptions);
         headerRows.add(new ContingencyTableRow(true, 0));
@@ -108,7 +110,7 @@ public class Converter {
             for(ValueFunction valueFunction: valueFunctions) {
                 if (i == 0) {
                     headerRows.get(i).addTableRowCell(new ContingencyTableRowCell("Celkový " + valueFunction.getFunction() +
-                            " z " + valueFunction.getValue(), 1));
+                            " z " + valueFunction.getName(), 1));
                 } else {
                     headerRows.get(i).addTableRowCell(new ContingencyTableRowCell("", 1));
                 }
@@ -144,16 +146,11 @@ public class Converter {
 
     /**
      * Creates data rows.
-     * @param tableColumns Map of TableColumn
-     * @param columnNames list of column and user column names
-     * @param rowNames list of row and user row names
-     * @param valueFunctions list of values and their functions
      * @return list of contingency table rows
      */
-    private List<ContingencyTableRow> createDataRows(Map<String, TableColumn> tableColumns, List<NameUserName> columnNames,
-                                                     List<NameUserName> rowNames, List<ValueFunction> valueFunctions) {
+    private List<ContingencyTableRow> createDataRows() {
         List<ContingencyTableRow> dataRows = new ArrayList<>();
-        rowNodes = new ArrayList<>();
+        List<Node> rowNodes = new ArrayList<>();
         List<List<String>> values = new ArrayList<>();
         for(NameUserName nameUserName : rowNames) {
             TableColumn column = tableColumns.get(nameUserName.getName());
@@ -166,10 +163,51 @@ public class Converter {
         long stopTime = System.nanoTime();
         System.out.println("Rows List:" + TimeUnit.MILLISECONDS.convert((stopTime - startTime), TimeUnit.NANOSECONDS));
 
+        boolean[] usableColumns = new boolean[(valueFunctions.size() * columnNodes.size())];
+
+        List<ContingencyTableRow> rowsToBeRemoved = new ArrayList<>();
         for (int i = 0; i < dataRows.size(); i++) {
-            fillRowWithData(dataRows.get(i), tableColumns, rowNodes.get(i), rowNames, columnNames, valueFunctions);
+            if (!fillRowWithData(dataRows.get(i), rowNodes.get(i), usableColumns)) {
+                rowsToBeRemoved.add(dataRows.get(i));
+            }
+        }
+
+        dataRows.removeAll(rowsToBeRemoved);
+
+//        for (ContingencyTableRow row : dataRows) {
+//            List<ContingencyTableRowCell> cellsToRemove = new ArrayList<>();
+//            for (int i = 1; i < usableColumns.length; i++) {
+//                if (!usableColumns[i - 1]) {
+//                    cellsToRemove.add(row.getCells().get(i));
+//                }
+//            }
+//            row.getCells().removeAll(cellsToRemove);
+//            cellsToRemove.clear();
+//        }
+
+        ContingencyTableRow finalRow = new ContingencyTableRow(false, 0);
+        finalRow.addTableRowCell(new ContingencyTableRowCell("Celkem", 1));
+
+        int columnIndex = 0;
+        List<ContingencyTableRowCell> valueCells = new ArrayList<>();
+        for (ValueFunction valueFunction : valueFunctions) {
+            for (Node columnNode : columnNodes) {
+                if (true) {
+                    finalRow.addTableRowCell(generateFilledCell(valueFunction.getFunction(), columnNode.getTotalResult(), columnNode.getTotalResultSum(),
+                            columnNode.getTotalMin(), columnNode.getTotalMax(), columnNode.getTotalResultAvgSum(),
+                            columnNode.getTotalResultAvg()));
+                    columnIndex++;
+                }
+            }
+            valueCells.add(generateFilledCell(valueFunction.getFunction(), valueFunction.getTotalResult(), valueFunction.getTotalResultSum(),
+                    valueFunction.getTotalMin(), valueFunction.getTotalMax(), valueFunction.getTotalResultAvgSum(),
+                    valueFunction.getTotalResultAvg()));
         }
 
+        finalRow.addTableRowCells(valueCells);
+
+        dataRows.add(finalRow);
+
         return dataRows;
     }
 
@@ -226,32 +264,51 @@ public class Converter {
     /**
      * Fills each data row with data.
      * @param row row to ne filled with data
-     * @param tableColumns Map of TableColumn
      * @param node node of the row
-     * @param rowNames list of row names and user names
-     * @param columnNames list of column names and user names
-     * @param valueFunctions list of values and their functions
      */
-    private void fillRowWithData(ContingencyTableRow row, Map<String, TableColumn> tableColumns, Node node,
-                                 List<NameUserName> rowNames, List<NameUserName> columnNames, List<ValueFunction> valueFunctions) {
+    private boolean fillRowWithData(ContingencyTableRow row, Node node, boolean[] usableColumns) {
+        if (valueFunctions.size() == 0) {
+            return true;
+        }
+
+        boolean isSingleValueRow = false;
+
+        if (node.getValues().size() <= 1) {
+            isSingleValueRow = true;
+        }
+
+        boolean isRowUsable = false;
         if (columnNodes.size() == 0) {
             columnNodes.add(new Node());
         }
 
-        List<Integer> usableIDs = getUsableIDs(tableColumns, node, rowNames);
+        List<Integer> usableIDs = getUsableIDs(node);
 
-        int[] totals = new int[valueFunctions.size()];
+        DoubleWrapper[] totals = new DoubleWrapper[valueFunctions.size()];
+        initializeWrapperField(totals);
+
+        DoubleWrapper[] totalsAvgSum = new DoubleWrapper[valueFunctions.size()];
+        initializeWrapperField(totalsAvgSum);
+
+        boolean[] isFilledValueCells = new boolean[valueFunctions.size()];
+        int columnIndex = 0;
         //hodnota
         for (int v = 0; v < valueFunctions.size(); v++) {
+            BooleanWrapper isFirstMinMaxTotal = new BooleanWrapper(true);
             ValueFunction valueFunction = valueFunctions.get(v);
             //sloupec v kont. tabulce
             for (Node columnNode : columnNodes) {
-                int result = 0;
+                DoubleWrapper result = new DoubleWrapper(0);
+                DoubleWrapper resultAvgSum = new DoubleWrapper(0);
+                BooleanWrapper isFirstMinMax = new BooleanWrapper(true);
+                boolean isFilledCell = false;
                 // radek v orig. tabulce
                 for (Integer usableID : usableIDs) {
                     boolean isUsable = true;
                     int j;
-                    // zvoleny sloupec ze sloupcu
+                    // zvoleny sloupec ze sloupcu, ziska hodnoty z pouzitelne radky danych sloupcu originalni tabulky a
+                    // pokud se vsechny rovnaji hodnotam, ktere jsou ulozeny v hodnotach uzlu na pozici vybraneho
+                    // sloupce, je kommbinace pouzitelna k vypoctum
                     for (j = 0; j < columnNode.getValues().size(); j++) {
                         if (!tableColumns.get(columnNames.get(j).getName()).getValues().get(usableID).equals(columnNode.getValues().get(j))) {
                             isUsable = false;
@@ -259,51 +316,222 @@ public class Converter {
                         }
                     }
                     // zvoleny sloupec z hodnot
-                    if (tableColumns.get(valueFunction.getValue()).getValues().get(usableID) == null) {
+                    if (tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID) == null) {
                         isUsable = false;
                     }
 
-                    if (isUsable) {
-                        if (valueFunction.getFunction().equals("COUNT")) {
-                            result++;
-                        } else if (valueFunction.getFunction().equals("SUM")) {
-                            result += Integer.parseInt(tableColumns.get(valueFunction.getValue()).getValues().get(usableID));
-                        }
-                        if (j - 1 == 0 || j == 0) {
-                            if (valueFunction.getFunction().equals("COUNT")) {
-                                totals[v]++;
-                            } else if (valueFunction.getFunction().equals("SUM")) {
-                                totals[v] += Integer.parseInt(tableColumns.get(valueFunction.getValue()).getValues().get(usableID));
-                            }
+                    usableColumns[columnIndex] = isUsable;
+
+                    if (isUsable) { // pokud lze hodnotu pouzit, prunik vsech hodnot sedi (ma nejakou hodnotu)
+                        isRowUsable = true;
+                        isFilledCell = true;
+                        computeValues(valueFunction, result, resultAvgSum, columnNode, isFirstMinMax,
+                                usableID, false, isSingleValueRow);
+                        if (j <= 1) {
+                            isFilledValueCells[v] = true;
+                            computeValues(valueFunction, totals[v], totalsAvgSum[v], null, isFirstMinMaxTotal,
+                                    usableID, true, isSingleValueRow);
                         }
                     }
                 }
-                row.addTableRowCell(new ContingencyTableRowCell(Integer.toString(result), 0));
+                if (!isFilledCell) {
+                    row.addTableRowCell(new ContingencyTableRowCell("", 0));
+                }
+                else {
+                    row.addTableRowCell(generateFilledCell(valueFunction.getFunction(), result.getValue(), result.getValue(), result.getValue(),
+                            result.getValue(), resultAvgSum.getValue(), result.getValue()));
+                }
+                columnIndex++;
             }
         }
-        for (int total : totals) {
-            row.addTableRowCell(new ContingencyTableRowCell(Integer.toString(total), 0));
+        for (int i = 0; i < valueFunctions.size(); i++) {
+            if (!isFilledValueCells[i]) {
+                row.addTableRowCell(new ContingencyTableRowCell("", 0));
+            }
+            else {
+                row.addTableRowCell(generateFilledCell(valueFunctions.get(i).getFunction(), totals[i].getValue(), totals[i].getValue(),
+                        totals[i].getValue(), totals[i].getValue(), totalsAvgSum[i].getValue(), totals[i].getValue()));
+            }
         }
+
+        return isRowUsable;
     }
 
     /**
      * Gets a list of IDs of the query rows that fit the contingency table row settings.
-     * @param tableColumns Map of TableColumn
      * @param node node of the row
-     * @param rowNames list of row names and user names
      * @return list of usable IDs
      */
-    private List<Integer> getUsableIDs(Map<String, TableColumn> tableColumns, Node node, List<NameUserName> rowNames) {
+    private List<Integer> getUsableIDs(Node node) {
         List<Integer> ids = new ArrayList<>();
 
         for (int i = 0; i < tableColumns.get(rowNames.get(0).getName()).getValues().size(); i++) {
+            boolean isUsable = true;
             for (int j = 0; j < node.getValues().size(); j++) {
-                if (tableColumns.get(rowNames.get(j).getName()).getValues().get(i).equals(node.getValues().get(j))) {
-                    ids.add(i);
+                if (!tableColumns.get(rowNames.get(j).getName()).getValues().get(i).equals(node.getValues().get(j))) {
+                    isUsable = false;
+                    break;
                 }
             }
+            if (isUsable) {
+                ids.add(i);
+            }
         }
 
         return ids;
     }
+
+    private void computeValues(ValueFunction valueFunction, DoubleWrapper result, DoubleWrapper resultAvgSum, Node columnNode,
+                               BooleanWrapper isFirstMinMax, int usableID, boolean isValueTotal, boolean isSingleValueRow) {
+        switch (valueFunction.getFunction()) {
+            case "COUNT":
+                count(valueFunction, result, columnNode, isValueTotal, isSingleValueRow);
+                break;
+            case "SUM":
+                sum(valueFunction, result, columnNode, usableID, isValueTotal, isSingleValueRow);
+                break;
+            case "MIN":
+                min(valueFunction, result, columnNode, isFirstMinMax, usableID, isValueTotal, isSingleValueRow);
+                break;
+            case "MAX":
+                max(valueFunction, result, columnNode, isFirstMinMax, usableID, isValueTotal, isSingleValueRow);
+                break;
+            case "AVG":
+                average(valueFunction, result, resultAvgSum, columnNode, usableID, isValueTotal, isSingleValueRow);
+                break;
+        }
+    }
+
+    private ContingencyTableRowCell generateFilledCell(String function, double totalResult, double totalResultSum,
+                                                       double totalMin, double totalMax, double totalResultAvgSum,
+                                                       double totalResultAvg) {
+        ContingencyTableRowCell cell = null;
+        switch (function) {
+            case "COUNT":
+                cell = new ContingencyTableRowCell(decimalFormat.format(totalResult), 1);
+                break;
+            case "SUM":
+                cell = new ContingencyTableRowCell(decimalFormat.format(totalResultSum), 1);
+                break;
+            case "MIN":
+                cell = new ContingencyTableRowCell(decimalFormat.format(totalMin), 1);
+                break;
+            case "MAX":
+                cell = new ContingencyTableRowCell(decimalFormat.format(totalMax), 1);
+                break;
+            case "AVG":
+                if (totalResultAvg == 0) {
+                    cell = new ContingencyTableRowCell("!!Dělení nulou!!", 0);
+                } else {
+                    cell = new ContingencyTableRowCell(decimalFormat.format(totalResultAvgSum / totalResultAvg), 0);
+                }
+                break;
+        }
+
+        return cell;
+    }
+
+    private void initializeWrapperField(DoubleWrapper[] field) {
+        for (int i = 0; i < field.length; i++) {
+            field[i] = new DoubleWrapper(0);
+        }
+    }
+
+    private void count(ValueFunction valueFunction, DoubleWrapper result, Node columnNode, boolean isValueTotal,
+                       boolean isSingleValueRow) {
+        result.setValue(result.getValue() + 1);
+        if (columnNode != null && isSingleValueRow) {
+            columnNode.setTotalResult(columnNode.getTotalResult() + 1);
+        }
+        if (isValueTotal && isSingleValueRow) {
+            valueFunction.setTotalResult(valueFunction.getTotalResult() + 1);
+        }
+    }
+
+    private void sum(ValueFunction valueFunction, DoubleWrapper result, Node columnNode, int usableID, boolean isValueTotal,
+                     boolean isSingleValueRow) {
+        if (valueFunction.getType().equals("Číslo")) {
+            result.setValue(result.getValue() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            if (columnNode != null && isSingleValueRow) {
+                columnNode.setTotalResultSum(columnNode.getTotalResultSum() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+            if (isValueTotal && isSingleValueRow) {
+                valueFunction.setTotalResultSum(valueFunction.getTotalResultSum() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+        }
+    }
+
+    private void min(ValueFunction valueFunction, DoubleWrapper result, Node columnNode, BooleanWrapper isFirstMinMax,
+                     int usableID, boolean isValueTotal, boolean isSingleValueRow) {
+        if (valueFunction.getType().equals("Číslo")) {
+            if (isFirstMinMax.isValue()) {
+                isFirstMinMax.setValue(false);
+                result.setValue(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+            else {
+                result.setValue(Math.min(result.getValue(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+            }
+            if (columnNode != null && isSingleValueRow) {
+                if (columnNode.isFirstMin()) {
+                    columnNode.setFirstMin(false);
+                    columnNode.setTotalMin(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+                } else {
+                    columnNode.setTotalMin(Math.min(columnNode.getTotalMin(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+                }
+            }
+            if (isValueTotal && isSingleValueRow) {
+                if (valueFunction.isFirstMin()) {
+                    valueFunction.setFirstMin(false);
+                    valueFunction.setTotalMin(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+                } else {
+                    valueFunction.setTotalMin(Math.min(valueFunction.getTotalMin(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+                }
+            }
+        }
+    }
+
+    private void max(ValueFunction valueFunction, DoubleWrapper result, Node columnNode, BooleanWrapper isFirstMinMax,
+                     int usableID, boolean isValueTotal, boolean isSingleValueRow) {
+        if (valueFunction.getType().equals("Číslo")) {
+            if (isFirstMinMax.isValue()) {
+                isFirstMinMax.setValue(false);
+                result.setValue(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+            else {
+                result.setValue(Math.max(result.getValue(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+            }
+            if (columnNode != null && isSingleValueRow) {
+                if (columnNode.isFirstMax()) {
+                    columnNode.setFirstMax(false);
+                    columnNode.setTotalMax(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+                } else {
+                    columnNode.setTotalMax(Math.max(columnNode.getTotalMax(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+                }
+            }
+            if (isValueTotal && isSingleValueRow) {
+                if (valueFunction.isFirstMax()) {
+                    valueFunction.setFirstMax(false);
+                    valueFunction.setTotalMax(Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+                } else {
+                    valueFunction.setTotalMax(Math.max(valueFunction.getTotalMax(), Integer.parseInt(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID))));
+                }
+            }
+        }
+    }
+
+    private void average(ValueFunction valueFunction, DoubleWrapper result, DoubleWrapper resultAvgSum, Node columnNode,
+                         int usableID, boolean isValueTotal, boolean isSingleValueRow) {
+        if (valueFunction.getType().equals("Číslo")) {
+            result.setValue(result.getValue() + 1);
+            resultAvgSum.setValue(resultAvgSum.getValue() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            if (columnNode != null && isSingleValueRow) {
+                columnNode.setTotalResultAvg(columnNode.getTotalResultAvg() + 1);
+                columnNode.setTotalResultAvgSum(columnNode.getTotalResultAvgSum() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+            if (isValueTotal && isSingleValueRow) {
+                valueFunction.setTotalResultAvg(valueFunction.getTotalResultAvg() + 1);
+                valueFunction.setTotalResultAvgSum(valueFunction.getTotalResultAvgSum() + Double.parseDouble(tableColumns.get(valueFunction.getNameOfSelect()).getValues().get(usableID)));
+            }
+        }
+    }
 }
