package com.knecon.fforesight.service.layoutparser.processor.utils;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;

import lombok.experimental.UtilityClass;

@UtilityClass
public class TableMergingUtility {

    private static final double TABLE_ALIGNMENT_THRESHOLD = 2d;


    public List<TablePageBlock> findConsecutiveTablesWithSameColCountAndSameHeaders(TablePageBlock originalTablePageBlock, List<AbstractPageBlock> pageBlocks) {

        List<TablePageBlock> consecutiveTables = pageBlocks.stream()
                .map(abstractPageBlock -> (TablePageBlock) abstractPageBlock)
                .filter(tablePageBlock -> !tablePageBlock.equals(originalTablePageBlock))
                .sorted(Comparator.comparingInt(TablePageBlock::getPage).thenComparing(TablePageBlock::getY).thenComparing(TablePageBlock::getX))
                .toList();

        assert consecutiveTables.size() == pageBlocks.size() - 1;
        var currentTable = originalTablePageBlock;
        int currentTableIndex = 0;

        List<TablePageBlock> consecutiveTablesWithSameColCountAndHeaders = new LinkedList<>();
        consecutiveTablesWithSameColCountAndHeaders.add(originalTablePageBlock);
        for (int i = 0; i < consecutiveTables.size(); i++) {
            TablePageBlock consecutiveTable = consecutiveTables.get(i);

            if (consecutiveTable.getColCount() == originalTablePageBlock.getColCount() //
                && getHeaders(consecutiveTable).isEmpty() //
                && outerBoundaryAlignsX(originalTablePageBlock, consecutiveTable) //
                && consecutiveOrSamePage(currentTable, consecutiveTable) //
                && !tableBetween(currentTable, consecutiveTable, findTablesBetween(consecutiveTables, currentTableIndex, i))) {

                currentTable = consecutiveTable;
                currentTableIndex = i;
                consecutiveTablesWithSameColCountAndHeaders.add(consecutiveTable);
            }
        }
        return consecutiveTablesWithSameColCountAndHeaders;
    }


    private static List<TablePageBlock> findTablesBetween(List<TablePageBlock> consecutiveTables, int currentTableIndex, int i) {

        if (currentTableIndex + 1 == consecutiveTables.size() || currentTableIndex + 1 >= i) {
            return Collections.emptyList();
        }
        return consecutiveTables.subList(currentTableIndex + 1, i);
    }


    private static boolean consecutiveOrSamePage(TablePageBlock currentTable, TablePageBlock consecutiveTable) {

        return currentTable.getPage() == consecutiveTable.getPage() || currentTable.getPage() + 1 == consecutiveTable.getPage();
    }


    private static boolean tableBetween(TablePageBlock currentTable, TablePageBlock consecutiveTable, List<TablePageBlock> tablesBetween) {

        if (tablesBetween.isEmpty()) {
            return false;
        }
        // assumes the tables are on the same page or on consecutive pages, all tables on pages in between are ignored.
        return tablesBetween.stream()
                       .filter(tableBetween -> tableBetween.getPage() == currentTable.getPage())
                       .anyMatch(tableBetween -> tableBetween.isBelow(currentTable)) //
               || tablesBetween.stream()
                       .filter(tableBetween -> tableBetween.getPage() == consecutiveTable.getPage())
                       .anyMatch(tableBetween -> tableBetween.isAbove(consecutiveTable));
    }


    private static boolean outerBoundaryAlignsX(TablePageBlock originalTablePageBlock, TablePageBlock consecutiveTable) {

        return Math.abs(consecutiveTable.getMinX() - originalTablePageBlock.getMinX()) < TABLE_ALIGNMENT_THRESHOLD
               && Math.abs(consecutiveTable.getMaxX() - originalTablePageBlock.getMaxX()) < TABLE_ALIGNMENT_THRESHOLD;
    }


    private List<Cell> getHeaders(TablePageBlock table) {

        return table.getRows()
                .stream()
                .flatMap(Collection::stream)
                .filter(Cell::isHeaderCell)
                .toList();
    }

}
