package sk.shanki.dbsuite.mapper.evolutionAlgorithm;

import sk.shanki.dbsuite.mapper.BiMap;
import sk.shanki.dbsuite.mapper.TableRowsMapping;
import sk.shanki.dbsuite.mapper.db.*;
import java.util.*;
import java.util.random.RandomGenerator;

public class Individual {

    private List<TableRowsMapping> individual;
    private double fitness;
    private RandomGenerator randomGenerator;

    public Individual(List<TableRowsMapping> individual){
        this.individual = individual;
        this.fitness = 0;
        this.randomGenerator = RandomGenerator.getDefault();

    }

    public double getFitness() {
        return this.fitness;
    }

    public double fitness(Database left, Database right){
        applyMapping(left, right);
        double result = 0;

        result += left.computeDatabasePenalisation(AlphaFormulas.numberOfDataColumns(), RowDistanceFormulas.getCUSTOM());
        result += right.computeDatabasePenalisation(AlphaFormulas.numberOfDataColumns(), RowDistanceFormulas.getCUSTOM());

        this.fitness = result;
        return result;
    }

    public void applyMapping(Database left, Database right) {
        //najskor naplnime lavu tabulku ak je mensia ako individual -> v klucoch je studentove
        for (int tableIndex = 0; tableIndex < left.size(); tableIndex++) {
            Table leftTable = left.get(tableIndex);
            TableRowsMapping individualTable = individual.get(tableIndex);
            if (leftTable.size() < individualTable.keys.size()){
                this.applyTableMappingRight(leftTable, individualTable.map);
            }
            else {
                this.applyTableMappingLeft(leftTable, individualTable.map);
            }
        }
        //potom pravu podla nullov
        for(int tableIndex = 0; tableIndex < right.size(); tableIndex++){
            Table rightTable = right.get(tableIndex);
            BiMap individualTable = individual.get(tableIndex).map;
            Table leftTable = null ;
            //ak pri vytvarani individualu bolo studentove riesenie vacsie tak islo na stranu klucov

            //ak je este taka tabulka v spravnom rieseni tak ju vyberieme
            if (tableIndex < left.size()){
                leftTable = left.get(tableIndex);
            }
            //ak nie tak vytvorime prazdnu tabulku
            else{
                leftTable = new Table(rightTable.getName());
            }

            if (rightTable.size() > individualTable.size()) {
                this.applyTableMappingLeft(rightTable, individualTable);
            }
            else{
                this.applyTableMappingRight(rightTable, individualTable);
            }
        }
    }

    private void applyTableMappingRight(Table table, BiMap individualTable){
        for (Row row : table) {
            Row mappedRow = individualTable.getBackwardMap().get(row);
            row.mapTo(mappedRow);
        }
    }

    private void applyTableMappingLeft(Table table, BiMap individualTable){
        for (Row row : table) {
            row.mapTo(individualTable.get(row));
        }
    }

    public Individual cross(Individual individual2){
        List<TableRowsMapping> newIndividual = new ArrayList<>();
        for (int tableIndex = 0; tableIndex < this.individual.size(); tableIndex++) {
            newIndividual.add(crossTwoTables(this.individual.get(tableIndex), individual2.individual.get(tableIndex),
                              this.randomGenerator.nextInt(this.individual.get(tableIndex).map.size())));
        }
        return new Individual(newIndividual);
    }

    public TableRowsMapping crossTwoTables(TableRowsMapping table1, TableRowsMapping table2, int point){
        TableRowsMapping result = new TableRowsMapping(new ArrayList<>());
        for (int index = 0; index < table1.keys.size(); index++) {
            Row key = table1.keys.get(index);
            Row table1Row = table1.map.get(key);
            Row table2Row = table2.map.get(key);

            if (index < point) {
                if(result.map.containsValue(table1Row)){
                    result.put(key, null);
                }
                else {
                    result.put(key, table1Row);
                }
            }
            else{
                if(result.map.containsValue(table2Row)){
                    result.put(key, null);
                }
                else {
                    result.put(key, table2Row);
                }
            }
        }
        return this.correction(result, table1);
    }

    public TableRowsMapping correction(TableRowsMapping individual, TableRowsMapping parent){
        if(!individual.map.containsValue(null)){
            return individual;
        }
        for (Row row:parent.map.values()){
//            boolean flag = false;
            if(individual.map.containsValue(row)){
                continue;
            }
//            for(Row value: individual.map.values()){
//                if(row != null && value == row){
//                    flag = true;
//                    break;
//                }
//            }
//            if(flag){
//                continue;
//            }
            for(Row key: individual.keys){
                if(individual.map.get(key) == null){
                    individual.put(key, row);
                    key.mapTo(row);
                    if(row != null){
                        row.mapTo(key);
                    }
                    break;
                }
            }

        }
        return individual;
    }

    public void mutate(Row row1, Row row2, BiMap table){
        Objects.requireNonNull(row1, "row1 cannot be null");
        Objects.requireNonNull(row2, "row2 cannot be null");

        Row mapRow1 = table.get(row1);
        Row mapRow2 = table.get(row2);

        table.put(row1, mapRow2);
        row1.mapTo(mapRow2);
        if(mapRow2 != null) {
            mapRow2.mapTo(row1);
        }

        table.put(row2, mapRow1);
        row2.mapTo(mapRow1);
        if(mapRow1 != null) {
            mapRow1.mapTo(row2);
        }
    }

    public void mutation(Float prob){
        for (TableRowsMapping table : this.individual) {
            ArrayList<Row> keyRows = new ArrayList<>(table.keys);
            int keyRowsSize = keyRows.size();

            for (Row row: keyRows) {
                if (randomGenerator.nextDouble() < prob) {
                    Row row2 = keyRows.get(randomGenerator.nextInt(keyRowsSize - 1));
                    if (row == row2) {
                        continue;
                    }
                    mutate(row, row2, table.map);
                }
            }
        }
    }

    public int size() {
        return this.individual.size();
    }

    public TableRowsMapping get(int index) {
        return this.individual.get(index);
    }

}
