/*
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * MyCoRe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MyCoRe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mycore.pi.urn;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mycore.access.MCRAccessException;
import org.mycore.backend.jpa.MCREntityManagerProvider;
import org.mycore.datamodel.metadata.MCRBase;
import org.mycore.datamodel.metadata.MCRDerivate;
import org.mycore.datamodel.metadata.MCRMetadataManager;
import org.mycore.datamodel.metadata.MCRObjectDerivate;
import org.mycore.datamodel.niofs.MCRPath;
import org.mycore.datamodel.niofs.utils.MCRFileCollectingFileVisitor;
import org.mycore.pi.MCRPIManager;
import org.mycore.pi.MCRPIService;
import org.mycore.pi.MCRPIServiceDates;
import org.mycore.pi.backend.MCRPI;
import org.mycore.pi.exceptions.MCRPersistentIdentifierException;

import jakarta.persistence.EntityManager;

/**
 * Service for assigning granular URNs to Derivate. You can call it with a Derivate-ID and it will assign a Base-URN for
 * the Derivate and granular URNs for every file in the Derivate (except IgnoreFileNames). If you then add a file to
 * Derivate you can call with Derivate-ID and additional path of the file. E.g. mir_derivate_00000060 and /image1.jpg
 * <p> <b>Inscriber is ignored with this {@link MCRPIService}</b> </p> Configuration Parameter(s): <dl>
 * <dt>IgnoreFileNames</dt> <dd>Comma seperated list of regex file which should not have a urn assigned. Default:
 * mets\\.xml</dd> </dl>
 */
public class MCRURNGranularOAIService extends MCRPIService<MCRDNBURN> {

    private static final Logger LOGGER = LogManager.getLogger();

    public MCRURNGranularOAIService() {
        super(MCRDNBURN.TYPE);
    }

    @Override
    public MCRDNBURN register(MCRBase obj, String additional, boolean updateObject)
        throws MCRAccessException, MCRPersistentIdentifierException {
        this.validateRegistration(obj, additional);

        MCRObjectDerivate derivate = ((MCRDerivate) obj).getDerivate();
        MCRDNBURN newURN;

        if (additional.equals("")) {
            /* Multiple URN for entire Derivate...  */
            newURN = registerURNsDerivate(obj, additional, derivate);
        } else {
            /* Single URN to one File... */
            newURN = registerSingleURN(obj, additional, derivate);
        }

        try {
            MCRMetadataManager.update(obj);
        } catch (Exception e) {
            throw new MCRPersistentIdentifierException("Error while updating derivate " + obj.getId(), e);
        }

        return newURN;

    }

    private MCRDNBURN registerSingleURN(MCRBase obj, String additional, MCRObjectDerivate derivate)
        throws MCRPersistentIdentifierException {
        LOGGER.info("Add single urn to {} / {}", obj.getId(), additional);

        EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
        MCRPath filePath = MCRPath.getPath(obj.getId().toString(), additional);
        if (!Files.exists(filePath)) {
            throw new MCRPersistentIdentifierException("Invalid path : " + additional);
        }

        int count = Math.toIntExact(derivate.getFileMetadata().stream().filter(file -> file.getUrn() != null).count());
        MCRDNBURN newURN = (MCRDNBURN) MCRPIManager.getInstance().get(derivate.getURN())
            .findFirst().get();

        String setID = obj.getId().getNumberAsString();
        MCRDNBURN urntoAssign = newURN.toGranular(setID, count + 1, count + 1);
        derivate.getOrCreateFileMetadata(filePath, urntoAssign.asString()).setUrn(urntoAssign.asString());
        MCRPI databaseEntry = new MCRPI(urntoAssign.asString(), getType(), obj.getId().toString(), additional,
            this.getServiceID(), new MCRPIServiceDates(new Date(), null));
        em.persist(databaseEntry);
        return newURN;
    }

    private MCRDNBURN registerURNsDerivate(MCRBase obj, String additional, MCRObjectDerivate derivate)
        throws MCRPersistentIdentifierException {
        LOGGER.info("Add URNs to all files of {}", obj.getId());

        EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();

        Path path = MCRPath.getPath(obj.getId().toString(), "/");
        MCRFileCollectingFileVisitor<Path> collectingFileVisitor = new MCRFileCollectingFileVisitor<>();

        try {
            Files.walkFileTree(path, collectingFileVisitor);
        } catch (IOException e) {
            throw new MCRPersistentIdentifierException("Could not walk derivate file tree!", e);
        }

        List<String> ignoreFileNamesList = getIgnoreFileList();

        List<Predicate<String>> predicateList = ignoreFileNamesList
            .stream()
            .map(Pattern::compile)
            .map(Pattern::asPredicate)
            .collect(Collectors.toList());

        List<MCRPath> pathList = collectingFileVisitor
            .getPaths()
            .stream()
            .filter(file -> predicateList.stream()
                .noneMatch(p -> p.test(file.toString().split(":")[1])))
            .map(p -> (MCRPath) p)
            .sorted()
            .collect(Collectors.toList());

        MCRDNBURN newURN = getNewIdentifier(obj, additional);
        String setID = obj.getId().getNumberAsString();

        for (int pathListIndex = 0; pathListIndex < pathList.size(); pathListIndex++) {
            MCRDNBURN subURN = newURN.toGranular(setID, pathListIndex + 1, pathList.size());
            derivate.getOrCreateFileMetadata(pathList.get(pathListIndex), subURN.asString()).setUrn(subURN.asString());
            MCRPI databaseEntry = new MCRPI(subURN.asString(), getType(), obj.getId().toString(),
                pathList.get(pathListIndex).getOwnerRelativePath(),
                this.getServiceID(), new MCRPIServiceDates(null, null));
            em.persist(databaseEntry);
        }

        derivate.setURN(newURN.asString());
        MCRPI databaseEntry = new MCRPI(newURN.asString(), getType(), obj.getId().toString(), "",
            this.getServiceID(), new MCRPIServiceDates(new Date(), null));
        em.persist(databaseEntry);
        return newURN;
    }

    private List<String> getIgnoreFileList() {
        List<String> ignoreFileNamesList = new ArrayList<>();
        String ignoreFileNames = getProperties().get("IgnoreFileNames");
        if (ignoreFileNames != null) {
            ignoreFileNamesList.addAll(Arrays.asList(ignoreFileNames.split(",")));
        } else {
            ignoreFileNamesList.add("mets\\.xml"); // default value
        }
        return ignoreFileNamesList;
    }

    @Override
    protected MCRPIServiceDates registerIdentifier(MCRBase obj, String additional, MCRDNBURN urn) {
        return new MCRPIServiceDates(null, null);
    }

    @Override
    protected void delete(MCRDNBURN identifier, MCRBase obj, String additional)
        throws MCRPersistentIdentifierException {
        throw new MCRPersistentIdentifierException("Delete is not supported for " + getType());
    }

    @Override
    protected void update(MCRDNBURN identifier, MCRBase obj, String additional) {
        //TODO: improve API, don't override method to do nothing
        LOGGER.info("No update in this implementation");
    }
}
