/*-
 * ~~~~~~licensing~~~~~~
 * base-extensions-support
 * ==========
 * 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.esb.platform.runtime.base.extensions.impl;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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.service.component.annotations.ReferencePolicyOption;
import org.osgi.util.tracker.BundleTracker;

import ru.entaxy.esb.platform.runtime.base.extensions.api.EntaxyExtendable;
import ru.entaxy.esb.platform.runtime.base.extensions.api.EntaxyExtension;
import ru.entaxy.esb.platform.runtime.base.extensions.api.EntaxyExtensionService;
import ru.entaxy.platform.base.support.CommonUtils;
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;

@Component(service = EntaxyExtensionService.class, immediate = true)
public class EntaxyExtensionServiceImpl
        extends UniformBundleTrackerCustomizer<List<EntaxyExtension>>
        implements EntaxyExtensionService,
        BundleTrackerCustomizerListener<List<EntaxyExtension>> {

    protected BundleContext bundleContext;

    protected Map<String, EntaxyExtendable> extendables = new HashMap<>();

    protected Object extensionsLock = new Object();

    protected final Map<String, List<EntaxyExtension>> extensions = new HashMap<>();

    protected BundleTracker<List<EntaxyExtension>> extensionTracker;

    @Activate
    public void activate(ComponentContext componentContext) {
        this.bundleContext = componentContext.getBundleContext();
        extensionTracker = BundleTrackerUtils.<List<EntaxyExtension>>createBuilder()
                .bundleContext(this.bundleContext)
                .addFilter(
                        (new BundleHeaderFilter()).header(HEADER_EXTENSIONS_PROVIDER))
                .customizer(this.listener(this))
                .bundleState(Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE)
                .get();
        extensionTracker.open();

    }

    @Deactivate
    public void deactivate() {
        extensionTracker.close();
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policyOption = ReferencePolicyOption.GREEDY,
            unbind = "removeExtendable")
    public void addExtendable(EntaxyExtendable entaxyExtendable) {
        synchronized (extensionsLock) {
            String extendableId = entaxyExtendable.getExtendableId();
            extendables.put(extendableId, entaxyExtendable);
            if (extensions.containsKey(extendableId))
                for (EntaxyExtension extension : extensions.get(extendableId))
                    entaxyExtendable.addExtension(extension);
        }
    }

    public void removeExtendable(EntaxyExtendable entaxyExtendable) {
        extendables.remove(entaxyExtendable.getExtendableId());
    }

    @Override
    public void added(List<EntaxyExtension> managedObject) {
        synchronized (extensionsLock) {
            for (EntaxyExtension extension : managedObject) {
                String targetId = extension.getTargetExtendableId();
                extensions.putIfAbsent(targetId, new ArrayList<>());

                extensions.get(targetId).add(extension);
                if (extendables.containsKey(targetId))
                    extendables.get(targetId).addExtension(extension);
            }
        }
    }

    @Override
    public void modified(List<EntaxyExtension> ignore) {}

    @Override
    public void removed(List<EntaxyExtension> managedObject) {
        synchronized (extensionsLock) {
            for (EntaxyExtension extension : managedObject) {
                String targetId = extension.getTargetExtendableId();
                if (extendables.containsKey(targetId))
                    extendables.get(targetId).removeExtension(extension);

                if (extensions.containsKey(targetId))
                    extensions.get(targetId).remove(extension);
            }
        }
    }

    @Override
    protected List<EntaxyExtension> createManagedObject(Bundle bundle, BundleEvent event,
            Map<String, List<?>> filterResults) {
        String resourceLocation =
                CommonUtils.getValid(bundle.getHeaders().get(HEADER_EXTENSIONS_LOCATION), DEFAULT_EXTENSIONS_LOCATION);
        Enumeration<URL> entries = bundle.findEntries(resourceLocation, "*.*", true);

        Map<String, EntaxyExtensionImpl> localExtensions = new HashMap<>();

        if (entries != null)
            while (entries.hasMoreElements()) {
            URL entry = entries.nextElement();
            if (entry.toString().endsWith("/"))
                continue;

            String localPath = entry.toString();
            localPath = localPath.substring(localPath.indexOf(resourceLocation) + resourceLocation.length() + 1);

            String resourceName = localPath.substring(localPath.lastIndexOf("/") + 1);

            String path = localPath.lastIndexOf("/") >= 0
                    ? localPath.substring(0, localPath.lastIndexOf("/"))
                    : "";
            int splitIndex = path.indexOf("/");
            String target = splitIndex > 0
                    ? path.substring(0, splitIndex)
                    : "";

            if (splitIndex > 0)
                path = path.substring(splitIndex + 1);

            if (!CommonUtils.isValid(target) || !CommonUtils.isValid(resourceName))
                continue;

            if (!localExtensions.containsKey(target))
                localExtensions.put(target, new EntaxyExtensionImpl(bundle, target));

            localExtensions.get(target).getResources().put(path + "/" + resourceName, entry);

        }

        return localExtensions.values().stream().collect(Collectors.toList());
    }

    @Override
    public List<EntaxyExtension> getExtensions() {
        final List<EntaxyExtension> result = new ArrayList<>();
        extensions.values().forEach(arr -> result.addAll(arr));
        Collections.sort(result, new Comparator<EntaxyExtension>() {

            @Override
            public int compare(EntaxyExtension o1, EntaxyExtension o2) {
                int res = o1.getTargetExtendableId().compareTo(o2.getTargetExtendableId());
                if (res != 0)
                    return res;
                if (o1.getBundleId() < o2.getBundleId())
                    return -1;
                if (o1.getBundleId() > o2.getBundleId())
                    return 1;
                return 0;
            }
        });
        return result;
    }

    @Override
    public List<EntaxyExtendable> getExtendables() {
        return extendables.values().stream().collect(Collectors.toList());
    }


}
