/*-
 * ~~~~~~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.Collections;
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.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.platform.base.objects.EntaxyObject;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.osgi.bundle.BundleCapabilityHelper;
import ru.entaxy.platform.base.support.osgi.bundle.CapabilityDescriptor;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectContainer;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectResource;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeRelation;

public class EntaxyRuntimeObjectImpl implements EntaxyRuntimeObject {

    protected static final String CAPABILITY_PLATFORM = "platform";

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

    private static final BundleInfo nullBundleInfo = new BundleInfo() {
        public long getBundleId() {
            return -1;
        };
    };

    protected static EntaxyObject nullWrappedObject = new EntaxyObject() {

        @Override
        public BundleInfo getBundleInfo() {
            return nullBundleInfo;
        }

        @Override
        public String getConfiguration() {
            return "";
        }

        @Override
        public String getFactoryId() {
            return "";
        }

        @Override
        public String getId() {
            return "NULL-object";
        }

        @Override
        public String getDisplayName() {
            return getId();
        }

        @Override
        public List<Relation> getRelations() {
            return Collections.emptyList();
        }

        @Override
        public List<Relation> getIncomingRelations() {
            return Collections.emptyList();
        }

        @Override
        public String getScope() {
            return "";
        }

        @Override
        public String getOwner() {
            return "";
        }

        @Override
        public String getType() {
            return "";
        }

        @Override
        public boolean isComplete() {
            return false;
        }

        @Override
        public boolean isProvided() {
            return false;
        }

        @Override
        public boolean isGhost() {
            return true;
        }
    };

    // TODO find out the cause and remove this workaround
    static {
        try {
            EntaxyRuntimeObjectImpl.class.getClassLoader().loadClass(EntaxyRuntimeObjectResourceImpl.class.getName());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    EntaxyRuntimeObjectContainerImpl containerImpl;

    protected EntaxyObject wrappedObject = nullWrappedObject;

    List<EntaxyRuntimeObjectResourceImpl> resources = new ArrayList<>();

    Map<String, Object> allAttributes = new HashMap<>();

    protected boolean loadedSuccessfully = false;

    public EntaxyRuntimeObjectImpl() {
        super();
    }

    public EntaxyRuntimeObjectImpl(EntaxyObject object) {
        super();
        setWrappedObject(object);
    }

    protected void reload(EntaxyObject object) {
        this.resources.clear();
        this.allAttributes.clear();
        loadedSuccessfully = false;
        try {

            // we assume that the object can be a ghost with no bundle info yet
            if (object.getBundleInfo() == null)
                return;

            Bundle bundle = FrameworkUtil.getBundle(getClass()).getBundleContext()
                    .getBundle(object.getBundleInfo().getBundleId());
            BundleCapabilityHelper helper = new BundleCapabilityHelper(bundle);
            if (!helper.isLoaded())
                return;
            CapabilityDescriptor capability = helper.findObjectDeclaration(object.getId(), object.getType());
            checkIsPlatform(helper);
            if (capability != null) {
                allAttributes.putAll(capability.getAttributes());
                
                Object resources = capability.getAttributes().get(CAPABILITIES_RUNTIME.ATTR_RESOURCES);
                if (resources != null) {
                    if (resources instanceof List) {
                        List<?> resourceList = (List) resources;
                        for (Object resourceDesc : resourceList)
                            if (resourceDesc == null)
                                continue;
                            else
                                try {
                                    this.resources.add(EntaxyRuntimeObjectResourceImpl.create(resourceDesc.toString()));
                                } catch (IllegalArgumentException iae) {
                                    log.warn("Error wrapping reeource ", iae);
                                    continue;
                                }
                    }
                }
            }
            loadedSuccessfully = true;
        } catch (Exception e) {
            log.warn("Reloading object [" + object.getObjectFullId() + "] failed", e);
            loadedSuccessfully = false;
        }
    }

    protected void checkIsPlatform(BundleCapabilityHelper helper) {
        List<CapabilityDescriptor> capabilities = helper.getEntaxyObjectsProvidedCapabilities();
        if (capabilities != null && capabilities.size() > 0) {
            Map<String, Object> attributes;
            for (CapabilityDescriptor capability : capabilities) {
                attributes = capability.getAttributes();
                if (attributes.containsKey(CAPABILITY_PLATFORM)) {
                    allAttributes.put(CAPABILITY_PLATFORM, attributes.get(CAPABILITY_PLATFORM));
                    break;
                }
            }
        }
    }

    @Override
    public BundleInfo getBundleInfo() {
        return wrappedObject.getBundleInfo();
    }

    @Override
    public String getConfiguration() {
        return wrappedObject.getConfiguration();
    }

    @Override
    public String getFactoryId() {
        return wrappedObject.getFactoryId();
    }

    @Override
    public String getId() {
        return wrappedObject.getId();
    }

    @Override
    public String getDisplayName() {
        return wrappedObject.getDisplayName();
    }

    @Override
    public List<Relation> getRelations() {
        return wrappedObject.getRelations();
    }

    @Override
    public List<Relation> getIncomingRelations() {
        return wrappedObject.getIncomingRelations();
    }

    @Override
    public String getScope() {
        return wrappedObject.getScope();
    }

    @Override
    public String getOwner() {
        String owner = wrappedObject.getOwner();
        if (!CommonUtils.isValid(owner) && EntaxyObject.OBJECT_SCOPE.PRIVATE.equals(getScope())) {
            // private objects can't exists without owner
            // so we'll suppose owner as first composite
            // and if no, then first aggregate

            List<EntaxyRuntimeRelation> relations = getIncomiingRelations(OBJECT_RELATION.COMPOSITION);
            if (!relations.isEmpty())
                return relations.get(0).getMain().getObjectFullId();

            relations = getIncomiingRelations(OBJECT_RELATION.AGGREGATION);
            if (!relations.isEmpty())
                return relations.get(0).getMain().getObjectFullId();

        }
        return owner;
    }

    @Override
    public String getType() {
        return wrappedObject.getType();
    }

    @Override
    public boolean isComplete() {
        return wrappedObject.isComplete();
    }

    @Override
    public boolean isProvided() {
        return wrappedObject.isProvided();
    }

    @Override
    public boolean isGhost() {
        return wrappedObject.isGhost();
    }

    public boolean isLoaded() {
        return (this.wrappedObject != null)
                && (this.wrappedObject != nullWrappedObject);
    }

    public EntaxyObject getWrappedObject() {
        return wrappedObject;
    }

    public void setWrappedObject(EntaxyObject wrappedObject) {
        reload(wrappedObject);
        this.wrappedObject = wrappedObject;
    }

    EntaxyRuntimeObjectContainerImpl getContainerImpl() {
        return containerImpl;
    }

    void setContainerImpl(EntaxyRuntimeObjectContainerImpl containerImpl) {
        this.containerImpl = containerImpl;
    }

    @Override
    public boolean isColocatedWith(EntaxyRuntimeObject object) {
        EntaxyRuntimeObjectContainer objectContainer = object.getContainer();
        if ((containerImpl == null) || (objectContainer == null))
            return false;
        return containerImpl.getId().equals(objectContainer.getId());
    }

    @Override
    public List<EntaxyRuntimeRelation> getRelations(OBJECT_RELATION relationType) {
        return getRelations().stream()
                .filter(r -> relationType.equals(r.getType()))
                .map(r -> new EntaxyRuntimeRelationImpl(r))
                .collect(Collectors.toList());
    }

    @Override
    public List<EntaxyRuntimeRelation> getSubordRelations() {
        return getRelations().stream()
                .filter(r -> r.isSubordinating())
                .map(r -> new EntaxyRuntimeRelationImpl(r))
                .collect(Collectors.toList());
    }

    @Override
    public List<EntaxyRuntimeRelation> getIncomiingRelations(OBJECT_RELATION relationType) {
        return getIncomingRelations().stream()
                .filter(r -> relationType.equals(r.getType()))
                .map(r -> new EntaxyRuntimeRelationImpl(r))
                .collect(Collectors.toList());
    }

    @Override
    public List<EntaxyRuntimeRelation> getIncomiingSubordRelations() {
        return getIncomingRelations().stream()
                .filter(r -> r.isSubordinating())
                .map(r -> new EntaxyRuntimeRelationImpl(r))
                .collect(Collectors.toList());
    }

    @Override
    public List<EntaxyRuntimeRelation> getDependsOn() {
        List<EntaxyRuntimeRelation> result = getIncomiingSubordRelations();
        result.addAll(getRelations(OBJECT_RELATION.DEPENDENCY));
        result.addAll(getRelations(OBJECT_RELATION.USE));
        return result;
    }

    @Override
    public List<EntaxyRuntimeRelation> getDependents() {
        List<EntaxyRuntimeRelation> result = getSubordRelations();
        result.addAll(getIncomiingRelations(OBJECT_RELATION.DEPENDENCY));
        result.addAll(getIncomiingRelations(OBJECT_RELATION.USE));
        return result;
    }

    @Override
    public EntaxyRuntimeObjectContainer getContainer() {
        return (EntaxyRuntimeObjectContainer) containerImpl;
    }

    @Override
    public List<EntaxyRuntimeObject> getColocatedObjects() {
        if (containerImpl == null)
            return Collections.emptyList();
        return containerImpl.getObjects().stream()
                .filter(obj -> (obj != this))
                .map(obj -> (EntaxyRuntimeObject) obj)
                .collect(Collectors.toList());
    }

    @Override
    public List<EntaxyRuntimeObjectResource> getResources() {
        return internalGetResources().stream().map(res -> (EntaxyRuntimeObjectResource) res)
                .collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getAllAttributes() {
        return internalGetAllAttributes();
    }

    @Override
    public boolean hasAttribute(String name) {
        return internalGetAllAttributes().containsKey(name);
    }

    @Override
    public Object getAttribute(String name) {
        return internalGetAllAttributes().get(name);
    }

    @Override
    public Object getAttributeOrDefault(String name, Object defaultValue) {
        return internalGetAllAttributes().getOrDefault(name, defaultValue);
    }

    protected Map<String, Object> internalGetAllAttributes() {
        if (!loadedSuccessfully)
            reload(wrappedObject);
        return allAttributes;
    }

    protected List<EntaxyRuntimeObjectResourceImpl> internalGetResources() {
        if (!loadedSuccessfully)
            reload(wrappedObject);
        return this.resources;
    }

}
