/*-
 * ~~~~~~licensing~~~~~~
 * route-producing
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.routes.design.library.tracker;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.util.tracker.BundleTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonObject;

import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;
import ru.entaxy.platform.base.support.osgi.tracker.BundleTrackerCustomizerListener;
import ru.entaxy.platform.base.support.osgi.tracker.BundleTrackerUtils;
import ru.entaxy.platform.base.support.osgi.tracker.UniformBundleTrackerCustomizer;
import ru.entaxy.platform.base.support.osgi.tracker.filter.BundleHeaderFilter;
import ru.entaxy.platform.routes.design.library.ComponentLibrary;
import ru.entaxy.platform.routes.design.library.storage.ComponentLibraryStorage;
import ru.entaxy.platform.routes.design.library.storage.ComponentLibraryStorage.Library;
import ru.entaxy.platform.routes.design.library.storage.ProvidedLibraryDescriptor;

@Component(service = ComponentLibraryTracker.class, immediate = true)
public class ComponentLibraryTracker {

    private static final Logger LOG = LoggerFactory.getLogger(ComponentLibraryTracker.class);

    public static ComponentLibraryTracker create(ComponentLibraryStorage storage, BundleContext bundleContext) {
        return new ComponentLibraryTracker(storage, bundleContext);
    }

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected ComponentLibraryStorage storage;

    protected BundleContext bundleContext;

    protected BundleTracker<List<ProvidedLibraryDescriptor>> tracker;

    public ComponentLibraryTracker() {
        super();
    }

    @Activate
    public void activate(ComponentContext componentContext) {
        this.bundleContext = componentContext.getBundleContext();
        tracker = BundleTrackerUtils.<List<ProvidedLibraryDescriptor>>createBuilder()
                .addFilter(
                        (new BundleHeaderFilter()).header(ComponentLibrary.COMPONENT_LIBRARY_PROVIDER_HEADER))
                .customizer(
                        (new LibraryTrackerCusomizer())
                                .listener(new LibraryTrackerCusomizerListener()))
                .bundleState(Bundle.ACTIVE | Bundle.INSTALLED | Bundle.RESOLVED)
                .get();
        tracker.open();
    }

    @Deactivate
    public void deactivate() {
        if (tracker != null)
            tracker.close();
    }

    protected ComponentLibraryTracker(ComponentLibraryStorage storage, BundleContext bundleContext) {

        this.storage = storage;
        this.bundleContext = bundleContext;



    }

    public void open() {
        if (tracker != null)
            tracker.open();
    }

    public void close() {
        if (tracker != null)
            tracker.close();
    }

    protected class LibraryTrackerCusomizer extends UniformBundleTrackerCustomizer<List<ProvidedLibraryDescriptor>> {

        @Override
        protected List<ProvidedLibraryDescriptor> createManagedObject(Bundle bundle, BundleEvent event,
                Map<String, List<?>> filterResults) {

            Enumeration<URL> foundEntries = bundle.findEntries(ProvidedLibraryDescriptor.LIBRARY_PATH, "*", false);
            if (foundEntries == null) {
                LOG.warn("Bundle [{}] marked as library provider but contains no libraries", bundle.getSymbolicName());
                return null;
            }

            List<ProvidedLibraryDescriptor> result = new ArrayList<>();

            while (foundEntries.hasMoreElements()) {

                URL entry = foundEntries.nextElement();
                String entryUrl = entry.toString();
                if (!entryUrl.endsWith("/"))
                    continue;

                int index = entryUrl.indexOf(ProvidedLibraryDescriptor.LIBRARY_PATH);
                String libraryPath = entryUrl.substring(index);
                String libraryName = libraryPath.substring(ProvidedLibraryDescriptor.LIBRARY_PATH.length() + 1,
                        libraryPath.length() - 1);
                LOG.info("Found library [{}] in bundle [{}]", libraryName, bundle.getSymbolicName());
                result.add(new ProvidedLibraryDescriptor(bundle, libraryPath, libraryName));
            }

            return result;
        }

    }

    protected class LibraryTrackerCusomizerListener
            implements BundleTrackerCustomizerListener<List<ProvidedLibraryDescriptor>> {

        @Override
        public void added(List<ProvidedLibraryDescriptor> managedObject) {
            for (ProvidedLibraryDescriptor descriptor : managedObject) {

                URL entry = descriptor.bundle
                        .getEntry(descriptor.libraryPath + ComponentLibraryStorage.LIBRARY_DESCRIPTOR_NAME);
                if (entry == null) {
                    LOG.warn("Descrptor not found for library [{}] in bundle [{}]", descriptor.libraryName,
                            descriptor.bundle.getSymbolicName());
                    continue;
                }

                String content = null;

                try (InputStream is = entry.openStream()) {
                    content = new String(is.readAllBytes());
                } catch (IOException e) {
                    LOG.error(String.format("Error reading library descriptor [%s] in bundle [%s]",
                            descriptor.libraryPath + ComponentLibraryStorage.LIBRARY_DESCRIPTOR_NAME,
                            descriptor.bundle.getSymbolicName()), e);
                    continue;
                }

                if (!CommonUtils.isValid(content)) {
                    LOG.error(String.format("Library descriptor is empty: [%s] in bundle [%s]",
                            descriptor.libraryPath + ComponentLibraryStorage.LIBRARY_DESCRIPTOR_NAME,
                            descriptor.bundle.getSymbolicName()));
                    continue;
                }

                JsonObject jo = JSONUtils.getJsonRootObject(content);
                if ((jo == null) || jo.keySet().isEmpty()) {
                    LOG.error(String.format("Invalid library descriptor: [%s] in bundle [%s]",
                            descriptor.libraryPath + ComponentLibraryStorage.LIBRARY_DESCRIPTOR_NAME,
                            descriptor.bundle.getSymbolicName()));
                    continue;
                }

                Library library = JSONUtils.GSON.fromJson(jo, Library.class);
                if ((library == null) || !CommonUtils.isValid(library.library)) {
                    LOG.error(String.format("Invalid library descriptor content: [%s] in bundle [%s]",
                            descriptor.libraryPath + ComponentLibraryStorage.LIBRARY_DESCRIPTOR_NAME,
                            descriptor.bundle.getSymbolicName()));
                    continue;
                }

                LOG.info("Recognized library [{}] having [{}] schemas", library.library,
                        library.schemas == null ? -1 : library.schemas.size());

                storage.addLibrary(descriptor, library);

            }
        }

        @Override
        public void modified(List<ProvidedLibraryDescriptor> managedObject) {
            // ignore
        }

        @Override
        public void removed(List<ProvidedLibraryDescriptor> managedObject) {
            // ignore
        }

    }
}
