/*-
 * ~~~~~~licensing~~~~~~
 * object-runtime-core
 * ==========
 * 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.objects.runtime.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.karaf.bundle.core.BundleService;
import org.osgi.framework.Filter;
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.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.resources.EntaxyResourceService;
import ru.entaxy.platform.base.objects.EntaxyObject;
import ru.entaxy.platform.base.objects.EntaxyObjectService;
import ru.entaxy.platform.base.objects.EntaxyObjectServiceListener;
import ru.entaxy.platform.core.artifact.service.ArtifactService;
import ru.entaxy.platform.objects.runtime.EntaxyObjectCustomizer;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject.ENTAXY_RUNTIME_OBJECT_STATE;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectContainer;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectContainer.ENTAXY_RUNTIME_OBJECT_CONTAINER_STATE;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectExtendedDataProvider;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectService;

@Component(service = EntaxyRuntimeObjectService.class, immediate = true)
public class EntaxyRuntimeObjectServiceImpl implements EntaxyRuntimeObjectService, EntaxyObjectServiceListener {

    private static final Logger log = LoggerFactory.getLogger(EntaxyRuntimeObjectServiceImpl.class);

    private static EntaxyRuntimeObjectServiceImpl INSTANCE = null;

    public static EntaxyRuntimeObjectServiceImpl getInstance() {
        return INSTANCE;
    }

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policyOption = ReferencePolicyOption.GREEDY,
            policy = ReferencePolicy.DYNAMIC)
    volatile protected EntaxyObjectService objectService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policyOption = ReferencePolicyOption.GREEDY,
            policy = ReferencePolicy.DYNAMIC)
    volatile protected EntaxyResourceService resourceService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policyOption = ReferencePolicyOption.GREEDY,
            policy = ReferencePolicy.DYNAMIC)
    volatile protected BundleService bundleService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policyOption = ReferencePolicyOption.GREEDY,
            policy = ReferencePolicy.DYNAMIC)
    volatile ArtifactService artifactService;

    protected List<Listener> listeners = new ArrayList<>();

    protected Object storageLock = new Object();

    protected ObjectRuntimeStorage storage = new ObjectRuntimeStorage();

    protected List<EntaxyObjectCustomizer> customizers = new ArrayList<>();

    protected Object customizersLock = new Object();

    protected Map<String, EntaxyRuntimeObjectExtendedDataProvider> extendedDataProviders = new HashMap<>();

    protected Object extendedDataProvidersLock = new Object();

    @Activate
    public void activate(ComponentContext componentContext) {
        INSTANCE = this;
        storage.setBundleContext(componentContext.getBundleContext());
        objectService.addListener(this);
        synchronized (storageLock) {
            fillCurrentObjects();
        }
    }

    @Deactivate
    public void deactivate() {
        storage.setBundleContext(null);
        try {
            objectService.removeListener(this);
        } catch (Exception ex) {
            log.trace(ex.getMessage());
        }

    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY, unbind = "removeCustomizer")
    public void addCustomizer(EntaxyObjectCustomizer customizer) {
        synchronized (customizersLock) {
            customizers.add(customizer);
            synchronized (storageLock) {
                for (EntaxyRuntimeObjectImpl object : storage.objects.values()) {
                    if (customizer.isAccepted(object.getWrappedObject())) {
                        storage.update(customizer.applyCustomization(object.getWrappedObject()));
                    }
                }
            }
        }
    }

    public void removeCustomizer(EntaxyObjectCustomizer customizer) {
        customizers.add(customizer);
        synchronized (storageLock) {
            for (EntaxyRuntimeObjectImpl object : storage.objects.values()) {
                if (customizer.isAccepted(object.getWrappedObject())) {
                    storage.update(customizer.unapplyCustomization(object.getWrappedObject()));
                }
            }
        }
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY, unbind = "removeExtendedDataProvider")
    public void addExtendedDataProvider(EntaxyRuntimeObjectExtendedDataProvider dataProvider) {
        synchronized (extendedDataProvidersLock) {
            this.extendedDataProviders.put(dataProvider.getProviderId(), dataProvider);
        }
    }

    public void removeExtendedDataProvider(EntaxyRuntimeObjectExtendedDataProvider dataProvider) {
        synchronized (extendedDataProvidersLock) {
            this.extendedDataProviders.remove(dataProvider.getProviderId());
        }
    }

    protected void fillCurrentObjects() {
        // make a copy
        List<EntaxyObject> objectsList = new ArrayList<>(objectService.getObjects());
        for (EntaxyObject obj : objectsList)
            storage.add(customize(obj));
    }

    protected EntaxyObject customize(EntaxyObject object) {
        synchronized (customizersLock) {
            for (EntaxyObjectCustomizer customizer : customizers) {
                if (customizer.isAccepted(object))
                    return customizer.applyCustomization(object);
            }
        }
        return object;
    }

    // EntaxyObjectServiceListener

    @Override
    public void objectAdded(EntaxyObject entaxyObject) {
        synchronized (storageLock) {
            storage.add(customize(entaxyObject));
        }
        EntaxyRuntimeObject runtimeObject = getRuntimeObject(entaxyObject);
        if (runtimeObject == null)
            return;
        for (Listener l : listeners) {
            l.objectAdded(runtimeObject);
        }
    }

    @Override
    public void objectModified(EntaxyObject entaxyObject) {
        synchronized (storageLock) {
            storage.update(customize(entaxyObject));
        }
        EntaxyRuntimeObject runtimeObject = getRuntimeObject(entaxyObject);
        if (runtimeObject == null)
            return;
        for (Listener l : listeners) {
            l.objectModified(runtimeObject);
        }
    }

    @Override
    public void objectRemoved(EntaxyObject entaxyObject) {
        EntaxyRuntimeObject runtimeObject = getRuntimeObject(entaxyObject);
        synchronized (storageLock) {
            storage.remove(entaxyObject);
        }
        if (runtimeObject == null)
            return;
        for (Listener l : listeners) {
            l.objectRemoved(runtimeObject);
        }
    }

    // EntaxyRuntimeObjectService

    @Override
    public void addListener(Listener listener) {
        removeListener(listener);
        this.listeners.add(listener);
    }

    @Override
    public void removeListener(Listener listener) {
        this.listeners.remove(listener);
    }

    @Override
    public EntaxyRuntimeObject getRuntimeObject(EntaxyObject origin) {
        return this.storage.objects.get(origin.getObjectFullId());
    }

    @Override
    public EntaxyRuntimeObject getRuntimeObject(String runtimeObjectId) {
        return this.storage.objects.get(runtimeObjectId);
    }

    @Override
    public EntaxyRuntimeObjectContainer getContainer(String containerName) {
        Optional<EntaxyRuntimeObjectContainer> container = storage.containers.values().stream()
                .filter(c -> containerName.equalsIgnoreCase(c.getName()))
                .map(c -> (EntaxyRuntimeObjectContainer) c)
                .findFirst();
        return container.isPresent() ? container.get() : null;
    }

    @Override
    public List<EntaxyRuntimeObject> getObjects() {
        return this.storage.objectsCache;
    }

    @Override
    public List<EntaxyRuntimeObject> findObjects(final Filter filter) {

        return getObjects().stream()
                .filter(obj -> filter.matches(getObjectEssense(obj)))
                .collect(Collectors.toList());

    }


    @Override
    public Map<String, Object> getObjectEssense(EntaxyRuntimeObject object) {
        Map<String, Object> result = new HashMap<>(object.getAllAttributes());
        result.put("type", object.getType());

        EntaxyRuntimeObjectContainer container = object.getContainer();
        if (container != null) {
            result.put("container.id", container.getId());
            result.put("container.name", container.getName());
            result.put("container.type", container.getType());
            result.put("container.state", container.getState().name());
        }

        return result;
    }

    @Override
    public List<EntaxyRuntimeObject> getObjectsForContainer(EntaxyRuntimeObjectContainer container) {
        return container.getObjects();
    }

    @Override
    public List<EntaxyRuntimeObjectContainer> getContainers() {
        return storage.containers.values().stream().map(c -> (EntaxyRuntimeObjectContainer) c)
                .collect(Collectors.toList());
    }

    @Override
    public EntaxyRuntimeObjectContainer getContainerForObject(EntaxyRuntimeObject runtimeObject) {
        return runtimeObject.getContainer();
    }

    @Override
    public EntaxyRuntimeObjectContainer getContainerForObject(String runtimeObjectId) {
        EntaxyRuntimeObjectImpl impl = storage.objects.get(runtimeObjectId);
        if (impl == null)
            return null;
        return impl.getContainer();
    }

    @Override
    public Map<String, Map<String, Object>> getExtendedObjectInfo(EntaxyRuntimeObject runtimeObject) {
        Map<String, Map<String, Object>> result = new HashMap<>();
        synchronized (extendedDataProvidersLock) {
            for (Map.Entry<String, EntaxyRuntimeObjectExtendedDataProvider> entry : extendedDataProviders.entrySet()) {
                Map<String, Object> providerResult = entry.getValue().getExtendedData(runtimeObject);
                if (providerResult != null)
                    result.put(entry.getKey(), providerResult);
            }
        }
        return result;
    }

    @Override
    public ENTAXY_RUNTIME_OBJECT_STATE getObjectState(String runtimeObjectId) {
        return getObjectState(getRuntimeObject(runtimeObjectId));
    }

    @Override
    public ENTAXY_RUNTIME_OBJECT_STATE getObjectState(EntaxyRuntimeObject runtimeObject) {
        if (runtimeObject == null)
            return ENTAXY_RUNTIME_OBJECT_STATE.UNKNOWN;
        EntaxyRuntimeObjectContainer container = runtimeObject.getContainer();
        try {
            ENTAXY_RUNTIME_OBJECT_CONTAINER_STATE containerState = container.getState();
            if (ENTAXY_RUNTIME_OBJECT_CONTAINER_STATE.Active.equals(containerState))
                return ENTAXY_RUNTIME_OBJECT_STATE.ACTIVE;
            return ENTAXY_RUNTIME_OBJECT_STATE.INACTIVE;
        } catch (Exception e) {
            return ENTAXY_RUNTIME_OBJECT_STATE.UNKNOWN;
        }
    }

    // others

    public EntaxyResourceService getResourceService() {
        return resourceService;
    }

    public BundleService getBundleService() {
        return bundleService;
    }

    public ArtifactService getArtifactService() {
        return artifactService;
    }
}
