package com.knecon.fforesight.service.layoutparser.processor.model.outline;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import org.springframework.lang.NonNull;

import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;

import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
public class TableOfContents implements Iterable<TableOfContentItem> {

    private List<TableOfContentItem> mainSections = new ArrayList<>();


    public TableOfContents(List<TableOfContentItem> mainSections) {

        this.mainSections = mainSections;
    }


    public List<TextPageBlock> getAllTextPageBlocks() {

        List<TextPageBlock> allTextPageBlocks = new ArrayList<>();
        for (TableOfContentItem item : mainSections) {
            collectTextPageBlocks(item, allTextPageBlocks);
        }
        return allTextPageBlocks;
    }


    private void collectTextPageBlocks(TableOfContentItem item, List<TextPageBlock> textPageBlocks) {

        textPageBlocks.add(item.getHeadline());
        for (TableOfContentItem child : item.getChildren()) {
            collectTextPageBlocks(child, textPageBlocks);
        }
    }


    public List<TableOfContentItem> getAllTableOfContentItems() {

        List<TableOfContentItem> allItems = new ArrayList<>();
        for (TableOfContentItem item : mainSections) {
            collectTableOfContentItems(item, allItems);
        }
        return allItems;
    }


    private void collectTableOfContentItems(TableOfContentItem item, List<TableOfContentItem> allItems) {

        allItems.add(item);
        for (TableOfContentItem child : item.getChildren()) {
            collectTableOfContentItems(child, allItems);
        }
    }


    private boolean containsBlock(TextPageBlock block) {

        for (TableOfContentItem existingItem : this.getMainSections()) {
            if (existingItem.getHeadline().equals(block) || existingItem.contains(block)) {
                return true;
            }
        }
        return false;
    }


    private boolean containsItem(TableOfContentItem tocItem) {

        for (TableOfContentItem existingItem : this.getMainSections()) {
            if (existingItem.equals(tocItem) || existingItem.contains(tocItem)) {
                return true;
            }
        }
        return false;
    }


    @Override
    public @NonNull Iterator<TableOfContentItem> iterator() {

        return new TableOfContentItemIterator(mainSections);
    }


    private static class TableOfContentItemIterator implements Iterator<TableOfContentItem> {

        private final Stack<Iterator<TableOfContentItem>> stack = new Stack<>();


        TableOfContentItemIterator(List<TableOfContentItem> mainSections) {

            stack.push(mainSections.iterator());
        }


        @Override
        public boolean hasNext() {

            ensureStackTopIsCurrent();
            return !stack.isEmpty() && stack.peek().hasNext();
        }


        @Override
        public TableOfContentItem next() {

            ensureStackTopIsCurrent();
            TableOfContentItem currentItem = stack.peek().next();
            if (currentItem.getChildren() != null && !currentItem.getChildren().isEmpty()) {
                stack.push(currentItem.getChildren()
                                   .iterator());
            }
            return currentItem;
        }


        private void ensureStackTopIsCurrent() {

            while (!stack.isEmpty() && !stack.peek().hasNext()) {
                stack.pop();
            }
        }

    }

}
