package com.knecon.fforesight.service.layoutparser.processor.model.graph.nodes;

import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeTypeProto.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.NodeVisitor;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.textblock.TextBlock;
import com.knecon.fforesight.service.layoutparser.processor.visualization.LayoutDebugLayer;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;

@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Document extends AbstractSemanticNode {

    Set<Page> pages;
    Integer numberOfPages;

    LayoutDebugLayer layoutDebugLayer;


    @Override
    public NodeType getType() {

        return NodeType.DOCUMENT;
    }


    /**
     * Gets the sections of the document as a list.
     *
     * @return A list of all sections within the document.
     */
    public List<Section> getAllSections() {

        return streamAllSubNodesOfType(NodeType.SECTION).map(node -> (Section) node)
                .collect(Collectors.toList());
    }


    /**
     * Gets the main sections of the document as a list.
     *
     * @return A list of main sections within the document
     * @deprecated This method is marked for removal.
     * Use {@link #streamChildrenOfType(NodeType)} instead,
     * or {@link #getChildrenOfTypeSectionOrSuperSection()} which returns children of type SECTION as well as SUPER_SECTION.
     */
    @Deprecated(forRemoval = true)
    public List<Section> getMainSections() {

        return streamChildrenOfType(NodeType.SECTION).map(node -> (Section) node)
                .collect(Collectors.toList());
    }


    /**
     * Gets the direct children of type SECTION or SUPER_SECTION of the document as a list of SemanticNode objects.
     *
     * @return A list of all children of type SECTION or SUPER_SECTION.
     */
    public List<SemanticNode> getChildrenOfTypeSectionOrSuperSection() {

        return streamChildren().filter(semanticNode -> semanticNode.getType().equals(NodeType.SECTION) || semanticNode.getType().equals(NodeType.SUPER_SECTION))
                .toList();
    }


    public List<Header> getHeaders() {

        return streamChildrenOfType(NodeType.HEADER).map(node -> (Header) node)
                .collect(Collectors.toList());
    }


    public List<Footer> getFooters() {

        return streamChildrenOfType(NodeType.FOOTER).map(node -> (Footer) node)
                .collect(Collectors.toList());
    }


    @Override
    public Headline getHeadline() {

        return streamAllSubNodesOfType(NodeType.HEADLINE).map(node -> (Headline) node)
                .findFirst().orElse(Headline.builder().build());
    }


    public Stream<TextBlock> streamTerminalTextBlocksInOrder() {

        return streamAllNodes().filter(SemanticNode::isLeaf)
                .map(SemanticNode::getTextBlock);
    }


    @Override
    public List<Integer> getTreeId() {

        return Collections.emptyList();
    }


    @Override
    public void setTreeId(List<Integer> tocId) {

        throw new UnsupportedOperationException("Document is always the root of the TablePageBlock of Contents");
    }


    private Stream<SemanticNode> streamAllNodes() {

        return getDocumentTree().allEntriesInOrder()
                .map(DocumentTree.Entry::getNode);
    }


    public Stream<Image> streamAllImages() {

        return streamAllSubNodesOfType(NodeType.IMAGE).map(node -> (Image) node);
    }


    public Map<NodeType, Long> buildSemanticNodeCounts() {

        return streamAllSubNodes().collect(Collectors.groupingBy(SemanticNode::getType, Collectors.counting()));
    }


    @Override
    public String toString() {

        return NodeType.DOCUMENT + ": " + this.getTextBlock().buildSummary();
    }


    @Override
    public Map<Page, Rectangle2D> getBBox() {

        Map<Page, Rectangle2D> bBox = new HashMap<>();
        for (Page page : pages) {
            bBox.put(page, new Rectangle2D.Double(0, 0, page.getWidth(), page.getHeight()));
        }
        return bBox;
    }


    @Override
    public void accept(NodeVisitor visitor) {

        visitor.visit(this);
    }

}
