/*-
 * ~~~~~~licensing~~~~~~
 * search-service
 * ==========
 * 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.search.provider;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.MapUtils;
import org.apache.karaf.bundle.core.BundleInfo;
import org.apache.karaf.bundle.core.BundleService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
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.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;

import ru.entaxy.platform.search.AbstractSearchProvider;
import ru.entaxy.platform.search.SearchProvider;
import ru.entaxy.platform.search.SearchProviderInfo;

@Component(service = SearchProvider.class, immediate = true)
@SearchProviderInfo(id = "osgi", targetTypes = {OsgiSearchProvider.TYPE_BUNDLE, OsgiSearchProvider.TYPE_SERVICE})
public class OsgiSearchProvider extends AbstractSearchProvider {

    public static final String TYPE_BUNDLE = "bundle";

    public static final String TYPE_SERVICE = "service";

    public interface BundleSearch {

        String ATTR_ID = "id";
        String ATTR_LOCATION = "location";
        String ATTR_SYMBOLIC_NAME = "symbolicName";
        String ATTR_LAST_MODIFIED = "lastModified";

        String ATTR_HEADER_PREFIX = "header.";
        String ATTR_HEADER = ATTR_HEADER_PREFIX + "*";

        List<String> STANDARD_ATTRIBUTES_LIST = Arrays.asList(new String[] {
            ATTR_ID, ATTR_SYMBOLIC_NAME, ATTR_LOCATION, ATTR_LAST_MODIFIED
        });

        Map<String, String> STANDARD_ATTRIBUTES = MapUtils.putAll(new LinkedHashMap<>(), new String[][] {
            {ATTR_ID, "Bundle Id"},
            {ATTR_LOCATION, "Bundle location"},
            {ATTR_SYMBOLIC_NAME, "Bundle symbolic name"},
            {ATTR_LAST_MODIFIED, "Bundle last modified timestamp"},
            {ATTR_HEADER, "Bundle header value"}
        });
    }

    public interface ServiceSearch {

        String ATTR_ID = "service.id";
        String ATTR_OBJECT_CLASS = "objectClass";
        String ATTR_BUNDLE_ID = "service.bundleId";


        List<String> STANDARD_ATTRIBUTES_LIST = Arrays.asList(new String[] {
            ATTR_ID, ATTR_OBJECT_CLASS, ATTR_BUNDLE_ID
        });

        Map<String, String> STANDARD_ATTRIBUTES = MapUtils.putAll(new LinkedHashMap<>(), new String[][] {
            {ATTR_ID, "Service Id"},
            {ATTR_OBJECT_CLASS, "Service object class"},
            {ATTR_BUNDLE_ID, "Service bundle id"}
        });
    }

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    BundleService bundleService;

    protected BundleContext bundleContext;

    @Activate
    public void activate(ComponentContext componentContext) {
        this.bundleContext = componentContext.getBundleContext();
    }

    @Override
    public List<Map<String, Object>> search(String targetType, Filter filter) throws IllegalArgumentException {
        if (TYPE_BUNDLE.equalsIgnoreCase(targetType))
            return searchBundles(filter);
        if (TYPE_SERVICE.equalsIgnoreCase(targetType))
            try {
                return searchServices(filter);
            } catch (InvalidSyntaxException e) {
                throw new IllegalArgumentException(String.format("Invalid filter: [%s]", filter.toString()));
            }


        throw new IllegalArgumentException(
                String.format("Provider [%s]: target type [%s] not supported", getProviderId(), targetType));


    }

    @Override
    public Map<String, String> getStandardAttributes(String targetType) throws IllegalArgumentException {
        if (TYPE_BUNDLE.equalsIgnoreCase(targetType))
            return BundleSearch.STANDARD_ATTRIBUTES;

        if (TYPE_SERVICE.equalsIgnoreCase(targetType))
            return ServiceSearch.STANDARD_ATTRIBUTES;

        throw new IllegalArgumentException(
                String.format("Provider [%s]: target type [%s] not supported", getProviderId(), targetType));

    }

    protected List<Map<String, Object>> searchBundles(Filter filter) {
        List<Map<String, Object>> result = new ArrayList<>();
        for (Bundle b : bundleContext.getBundles()) {
            Map<String, Object> essence = getBundleEssence(b);
            if (filter.matches(essence))
                result.add(essence);
        }

        return result;
    }

    protected Map<String, Object> getBundleEssence(Bundle bundle) {
        Map<String, Object> result = new LinkedHashMap<>();

        BundleInfo bi = bundleService.getInfo(bundle);

        result.put(BundleSearch.ATTR_ID, bundle.getBundleId());
        result.put(BundleSearch.ATTR_SYMBOLIC_NAME, bundle.getSymbolicName());
        result.put(BundleSearch.ATTR_LOCATION, bi.getUpdateLocation());
        result.put(BundleSearch.ATTR_LAST_MODIFIED, bundle.getLastModified());


        Dictionary<String, String> headers = bundle.getHeaders();
        Enumeration<String> keys = headers.keys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            result.put(BundleSearch.ATTR_HEADER_PREFIX + key, headers.get(key));
        }

        return result;
    }

    protected List<Map<String, Object>> searchServices(Filter filter) throws InvalidSyntaxException {

        ServiceReference<?>[] refs = bundleContext.getAllServiceReferences(null, filter.toString());

        List<Map<String, Object>> result = new ArrayList<>();
        for (ServiceReference<?> ref : refs) {
            result.add(getServiceEssence(ref));
        }

        return result;
    }

    protected Map<String, Object> getServiceEssence(ServiceReference<?> ref) {
        Map<String, Object> result = new LinkedHashMap<>();

        result.put(ServiceSearch.ATTR_ID, ref.getProperty(Constants.SERVICE_ID));
        Object refObjectClass = ref.getProperty(Constants.OBJECTCLASS);
        if (refObjectClass == null || !(refObjectClass instanceof String[]))
            result.put(ServiceSearch.ATTR_OBJECT_CLASS, "");
        else
            result.put(ServiceSearch.ATTR_OBJECT_CLASS, Arrays.asList((String[]) refObjectClass));

        result.put(ServiceSearch.ATTR_BUNDLE_ID, ref.getBundle().getBundleId());

        String[] keys = ref.getPropertyKeys();
        for (String key : keys)
            if (!ServiceSearch.STANDARD_ATTRIBUTES_LIST.contains(key))
                result.put(key, ref.getProperty(key));

        return result;
    }

}
