/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.causeway.core.runtimeservices.menubars;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.layout.menubars.MenuBars;
import org.apache.causeway.applib.services.menu.MenuBarsLoaderService;
import org.apache.causeway.applib.services.menu.MenuBarsMarshallerService;
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.core.config.viewer.web.WebAppContextPath;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;

import org.jspecify.annotations.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * Default implementation of {@link MenuBarsLoaderService}.
 *
 * @since 2.0 {@index}
 */
@Service
@Named(CausewayModuleCoreRuntimeServices.NAMESPACE + ".MenuBarsLoaderServiceDefault")
@Priority(PriorityPrecedence.MIDPOINT)
@Qualifier("Default")
@Slf4j
public class MenuBarsLoaderServiceDefault
implements MenuBarsLoaderService {

    private final boolean supportsReloading;
    private final AtomicReference<AbstractResource> menubarsLayoutResourceRef;
    private final CommonMimeType menubarsLayoutMimeType;

    @Inject
    public MenuBarsLoaderServiceDefault(final MetaModelContext mmc) {
        this.supportsReloading = mmc.getSystemEnvironment().isPrototyping();

        var menubarsLayoutFile = mmc.getConfiguration().viewer().common().application()
                .menubarsLayoutFile();
        var menubarsLayoutResource = new ClassPathResource(menubarsLayoutFile);
        if(!menubarsLayoutResource.exists()) {
            log.warn("menubarsLayoutFile {} (as configured for Apache Causeway) not found",
                    menubarsLayoutFile);
        }
        this.menubarsLayoutResourceRef = new AtomicReference<>(menubarsLayoutResource);
        this.menubarsLayoutMimeType = CommonMimeType.valueOfFileName(menubarsLayoutFile)
                .orElse(CommonMimeType.XML); // fallback default
    }

    // JUnit support
    public MenuBarsLoaderServiceDefault(
            final AtomicReference<AbstractResource> menubarsLayoutResourceRef,
            final CommonMimeType formatUnderTest) {
        this.supportsReloading = true;
        menubarsLayoutResourceRef.getAndUpdate(r->r!=null
                ? r
                : new AbstractResource() {
                    @Override public String getDescription() { return "Empty Resource"; }
                    @Override public InputStream getInputStream() throws IOException { return null; }}
                );
        this.menubarsLayoutResourceRef = menubarsLayoutResourceRef;
        this.menubarsLayoutMimeType = formatUnderTest;
    }

    @Override
    public boolean supportsReloading() {
        return supportsReloading;
    }

    @Override
    public <T extends MenuBars> Optional<T> menuBars(final @NonNull MenuBarsMarshallerService<T> marshaller) {
        return marshaller.unmarshal(loadMenubarsLayoutResource(), menubarsLayoutMimeType)
            .ifFailure(failure->severeCannotLoad(menubarsLayoutResourceRef.get(), failure))
            .getValue();
    }

    // -- HELPER

    private String loadMenubarsLayoutResource() {

        var menubarsLayoutResource = menubarsLayoutResourceRef.get();
        try {

            if(!menubarsLayoutResource.exists()) {
                return null;
            }

            var source = menubarsLayoutResource.getInputStream(); // throws if not found
            final String layoutFileContent =
                    _Strings.read(source, StandardCharsets.UTF_8);

            if(layoutFileContent == null) {
                warnNotFound(menubarsLayoutResource);
            }
            return layoutFileContent;
        } catch (Exception e) {
            severeCannotLoad(menubarsLayoutResource, e);
            return null;
        }

    }

    private boolean warnedOnce = false;

    private void warnNotFound(final AbstractResource menubarsLayoutResource) {
        if(warnedOnce) {
            return;
        }
        log.warn(
                "{}: could not find readable resource {} for the Menubars-Layout.",
                        WebAppContextPath.class.getName(),
                        menubarsLayoutResource);
        warnedOnce = true;
    }

    private void severeCannotLoad(final AbstractResource menubarsLayoutResource, final Throwable cause) {
        log.error("{}: could not find readable resource {} for the Menubars-Layout.",
                        WebAppContextPath.class.getName(),
                        menubarsLayoutResource,
                cause);
    }

}
