/* FichothequeLib_Import - Copyright (c) 2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.fichotheque.tools.from.html;

import java.util.ArrayList;
import java.util.List;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;


/**
 *
 * @author Vincent Calame
 */
public final class SoupReorganizeEngine {

    private SoupReorganizeEngine() {

    }

    public static void run(Element root) {
        Block block = new Block(root);
        block.reorganize();
    }


    private static class Block {

        private Element root;
        private int nodeSize;
        private int currentIndex = 0;

        private Block(Element root) {
            this.root = root;
            this.nodeSize = root.childNodeSize();
        }

        private void reorganize() {
            while (currentIndex < nodeSize) {
                Node node = root.childNode(currentIndex);
                if (node instanceof Element) {
                    Element element = (Element) node;
                    if (isToBeScanned(element)) {
                        Inline inline = new Inline(element);
                        ScanResult scanResult = inline.scan();
                        if (scanResult != null) {
                            List<Node> candidateToMoveList = scanResult.candidateToMoveList;
                            int size = candidateToMoveList.size();
                            if (size > 0) {
                                root.insertChildren(currentIndex + 1, candidateToMoveList);
                                currentIndex += size;
                                nodeSize += size;
                            }
                            Element splittedIline = scanResult.splittedInline;
                            if (splittedIline != null) {
                                root.insertChildren(currentIndex + 1, splittedIline);
                                nodeSize += 1;
                            }
                        }
                    }
                }
                currentIndex++;
            }
            for (Element child : root.children()) {
                if (isBlockCandidate(child)) {
                    Block childBlock = new Block(child);
                    childBlock.reorganize();
                }
            }
        }

    }


    private static class Inline {

        private final Element root;
        private final int nodeSize;
        private int currentIndex = 0;


        private Inline(Element inlineElement) {
            this.root = inlineElement;
            this.nodeSize = root.childNodeSize();
        }

        private ScanResult scan() {
            while (currentIndex < nodeSize) {
                Node node = root.childNode(currentIndex);
                if (node instanceof Element) {
                    Element child = (Element) node;
                    if (isMoveCandidate(child)) {
                        ScanResult scanResult = new ScanResult(child);
                        moveFollowers(scanResult);
                        return scanResult;
                    } else {
                        Inline childInline = new Inline(child);
                        ScanResult childScanResult = childInline.scan();
                        if (childScanResult != null) {
                            completeChildScanResult(childScanResult);
                            return childScanResult;
                        }
                    }
                }
                currentIndex++;
            }
            return null;
        }

        private void completeChildScanResult(ScanResult childScanResult) {
            Element copy = root.shallowClone();
            if (childScanResult.splittedInline != null) {
                copy.appendChild(childScanResult.splittedInline);
            }
            if (currentIndex < (nodeSize - 1)) {
                List<Node> remainingNodeList = new ArrayList<Node>();
                for (int i = currentIndex + 1; i < nodeSize; i++) {
                    remainingNodeList.add(root.childNode(i));
                }
                for (Node node : remainingNodeList) {
                    copy.appendChild(node);
                }
            }
            if (copy.childNodeSize() > 0) {
                childScanResult.splittedInline = copy;
            }
        }

        private void moveFollowers(ScanResult scanResult) {
            int startIndex = currentIndex + 1;
            if (startIndex == nodeSize) {
                return;
            }
            List<Node> remainingNodeList = new ArrayList<Node>();
            for (int i = startIndex; i < nodeSize; i++) {
                remainingNodeList.add(root.childNode(i));
            }
            Element copy = (Element) root.shallowClone();
            boolean CandidateTest = true;
            Node precedingBlankNode = null;
            for (Node remainingNode : remainingNodeList) {
                if (CandidateTest) {
                    if (isMoveCandidate(remainingNode)) {
                        if (precedingBlankNode != null) {
                            scanResult.addToMove(precedingBlankNode);
                        }
                        scanResult.addToMove((Element) remainingNode);
                        precedingBlankNode = null;
                    } else if (isBlank(remainingNode)) {
                        if (precedingBlankNode != null) {
                            scanResult.addToMove(precedingBlankNode);
                        }
                        precedingBlankNode = remainingNode;
                    } else {
                        CandidateTest = false;
                    }
                }
                if (!CandidateTest) {
                    if (precedingBlankNode != null) {
                        copy.appendChild(precedingBlankNode);
                        precedingBlankNode = null;
                    }
                    copy.appendChild(remainingNode);
                }
            }
            if (copy.childNodeSize() > 0) {
                scanResult.splittedInline = copy;
            }
        }

    }


    private static class ScanResult {

        private List<Node> candidateToMoveList = new ArrayList<Node>();
        private Element splittedInline = null;

        private ScanResult(Element firstMove) {
            candidateToMoveList.add(firstMove);
        }

        private void addToMove(Node candidate) {
            candidateToMoveList.add(candidate);
        }

    }

    private static boolean isToBeScanned(Element element) {
        short tagType = TagUtils.getTagType(element);
        if (tagType != TagUtils.INLINE_TYPE) {
            return false;
        }
        switch (element.tagName()) {
            case "a":
                if (imgIncludeInLinkTest(element)) {
                    return false;
                }
                break;
        }
        return true;
    }

    private static boolean isMoveCandidate(Node node) {
        if (!(node instanceof Element)) {
            return false;
        }
        Element element = (Element) node;
        short tagType = TagUtils.getTagType(element);
        if (tagType != TagUtils.INLINE_TYPE) {
            return true;
        }
        if (element.childNodeSize() == 0) {
            return true;
        }
        switch (element.tagName()) {
            case "a":
                if (imgIncludeInLinkTest(element)) {
                    return true;
                }
                break;
        }
        if (element.dataset().containsKey("bdf-type")) {
            return true;
        }
        return false;
    }

    private static boolean imgIncludeInLinkTest(Element linkElement) {
        Element loneChild = TagUtils.getLoneChild(linkElement);
        if ((loneChild != null) && (loneChild.tagName().equals("img"))) {
            return true;
        } else {
            return false;
        }
    }

    private static boolean isBlockCandidate(Node node) {
        if (!(node instanceof Element)) {
            return false;
        }
        return (TagUtils.getTagType((Element) node) == TagUtils.BLOCK_TYPE);
    }

    private static boolean isBlank(Node node) {
        return ((node instanceof TextNode) && (((TextNode) node).isBlank()));
    }

}
