package com.knecon.fforesight.service.viewerdoc.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.knecon.fforesight.service.viewerdoc.LayerIdentifier;
import com.knecon.fforesight.service.viewerdoc.layers.LayerGroup;
import com.knecon.fforesight.service.viewerdoc.model.Visualizations;
import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.PDFDoc;
import com.pdftron.pdf.ocg.Config;
import com.pdftron.pdf.ocg.Group;
import com.pdftron.sdf.Obj;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class PdftronLayerUtility {

    @SneakyThrows
    public Map<LayerIdentifier, Group> addLayersToDocument(List<LayerGroup> layerGroups, PDFDoc pdfDoc) {

        Map<LayerIdentifier, Group> optionalContentGroupMap = new HashMap<>();

        for (var layerGroup : layerGroups) {

            if (!layerGroup.subLayersAreOptionalContent() || layerGroup.isEmpty()) {
                continue;
            }

            if (layerGroup.isOptionalContent()) {
                Group group = addLayerToDocument(pdfDoc, layerGroup.getGroupIdentifier().name(), layerGroup.isVisibleByDefault());
                optionalContentGroupMap.put(layerGroup.getGroupIdentifier(), group);
            }

            if (layerGroup.subLayersAreOptionalContent()) {
                for (Visualizations subLayer : layerGroup.getVisualizations()) {
                    Group subGroup = addLayerToDocument(pdfDoc, subLayer.getLayer().name(), layerGroup.isVisibleByDefault());
                    optionalContentGroupMap.put(subLayer.getLayer(), subGroup);
                }
            }
        }

        setOrderArrayForPresentGroups(pdfDoc, PDFTronViewerDocumentService.ALL_LAYERS_WITH_OPTIONAL_CONTENT);

        return optionalContentGroupMap;
    }


    @SneakyThrows
    public void setOrderArrayForPresentGroups(PDFDoc pdfDoc, List<LayerGroup> layerGroups) {

        Config cfg = getConfig(pdfDoc);
        Obj orderArray = pdfDoc.createIndirectArray();

        Map<String, Group> groupMap = findAllGroupsInDocAsMap(pdfDoc);

        for (var layerGroup : layerGroups) {

            Obj childOrderArray;

            if (!layerGroup.subLayersAreOptionalContent()) {
                continue;
            }

            if (layerGroup.isOptionalContent() && groupMap.containsKey(layerGroup.getGroupIdentifier().name())) {
                Group group = groupMap.remove(layerGroup.getGroupIdentifier().name());
                group.setInitialState(cfg, layerGroup.isVisibleByDefault());
                orderArray.pushBack(group.getSDFObj());
                childOrderArray = pdfDoc.createIndirectArray();
                orderArray.pushBack(childOrderArray);
            } else {
                childOrderArray = orderArray;
            }

            for (Visualizations subLayer : layerGroup.getVisualizations()) {
                if (groupMap.containsKey(subLayer.getLayer().name())) {
                    Group group = groupMap.remove(subLayer.getLayer().name());
                    group.setInitialState(cfg, subLayer.isVisibleByDefault());
                    childOrderArray.pushBack(group.getSDFObj());
                }
            }
        }

        if (!groupMap.isEmpty()) {
            for (Group group : groupMap.values()) {
                orderArray.pushBack(group.getSDFObj());
            }
        }

        cfg.setOrder(orderArray);
        cfg.getSDFObj().putText("ListMode", "VisiblePages");
    }


    private static Map<String, Group> findAllGroupsInDocAsMap(PDFDoc pdfDoc) throws PDFNetException {

        Map<String, Group> groupMap = new LinkedHashMap<>();

        List<Group> presentGroups = findAllGroupsInDoc(pdfDoc);

        for (Group group : presentGroups) {
            groupMap.put(group.getName(), group);
        }
        return groupMap;
    }


    private static Config getConfig(PDFDoc pdfDoc) throws PDFNetException {

        Config cfg = pdfDoc.getOCGConfig();
        if (cfg == null) {
            cfg = Config.create(pdfDoc, true);
        }
        cfg.setName("knecon debug layer order");
        cfg.setCreator("Knecon Technology GmbH");
        return cfg;
    }


    @SneakyThrows
    private Group addLayerToDocument(PDFDoc doc, String layerName, boolean layerVisibilityDefaultValue) {

        Optional<Group> existingGroup = findGroupInDoc(doc, layerName);

        if (existingGroup.isPresent()) {
            return existingGroup.get();
        }

        return addNewLayer(doc, layerName, layerVisibilityDefaultValue, false);
    }


    private Group addNewLayer(PDFDoc doc, String layerName, boolean layerVisibilityDefaultValue, boolean containsAll) throws PDFNetException {

        Config cfg = getConfig(doc);
        Group grp = Group.create(doc, layerName);
        grp.setInitialState(cfg, layerVisibilityDefaultValue);

        return grp;
    }


    @SneakyThrows
    private Optional<Group> findGroupInDoc(PDFDoc doc, String layerName) {

        Obj ocgs = doc.getOCGs();
        if (ocgs != null) {
            int i;
            int sz = (int) ocgs.size();
            for (i = 0; i < sz; ++i) {
                Group ocg = new Group(ocgs.getAt(i));
                if (ocg.getName().equals(layerName)) {
                    return Optional.of(ocg);
                }
            }
        }
        return Optional.empty();
    }


    @SneakyThrows
    private List<Group> findAllGroupsInDoc(PDFDoc doc) {

        Obj ocgs = doc.getOCGs();

        if (ocgs == null) {
            return Collections.emptyList();
        }

        List<Group> groups = new ArrayList<>(Math.toIntExact(ocgs.size()));
        int i;
        int sz = (int) ocgs.size();
        for (i = 0; i < sz; ++i) {
            Group ocg = new Group(ocgs.getAt(i));
            groups.add(ocg);
        }
        return groups;
    }

}
