/*-
 * ~~~~~~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.impl;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.resources.EntaxyResourceURLFactory;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.routes.design.library.ComponentLibrary;
import ru.entaxy.platform.routes.design.library.ComponentLibrary.Schema;
import ru.entaxy.platform.routes.design.library.ComponentLibrary.Transformation;
import ru.entaxy.platform.routes.design.library.ComponentLibraryManager;
import ru.entaxy.platform.routes.design.library.storage.ComponentLibraryStorage;

@Component(service = ComponentLibraryManager.class, immediate = true)
public class ComponentLibraryManagerImpl implements ComponentLibraryManager, ComponentLibraryStorage.StorageListener {

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

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    ComponentLibraryStorage libraryStorage;

    protected List<ComponentLibrary> libraries = new ArrayList<>();
    protected List<ComponentLibrary.Schema> schemas = new ArrayList<>();
    protected List<ComponentLibrary.Transformation> transformations = new ArrayList<>();

    protected Object librariesLock = new Object();

    @Activate
    public void activate(ComponentContext componentContext) {
        initialize();
        this.libraryStorage.registerListener(this);
    }

    @Deactivate
    public void deactivate(ComponentContext componentContext) {
        this.libraryStorage.unregisterListener(this);
    }

    protected void initialize() {
        synchronized (librariesLock) {
            this.libraries = wrap(libraryStorage.getLibraries());
            for (ComponentLibrary lib : libraries)
                for (ComponentLibrary.Schema schema : lib.getSchemas()) {
                    schemas.add(schema);
                    for (ComponentLibrary.Transformation transformation : schema.getTransformations()) {
                        transformations.add(transformation);
                    }
                }
        }
    }

    protected void reload() {
        synchronized (librariesLock) {
            libraries.clear();
            schemas.clear();
            transformations.clear();
        }
        initialize();
    }

    @Override
    public List<ComponentLibrary> getLibraries() {
        return this.libraries;
    }

    @Override
    public List<ComponentLibrary> getLibrariesForContext(Map<String, Object> context) {
        return this.libraries.stream().filter(l -> l.getObjectFilter() == null || l.getObjectFilter().matches(context))
                .collect(Collectors.toList());
    }

    @Override
    public List<Schema> getSchemasForNamespace(String namespace) {
        return this.schemas.stream().filter(s -> s.getNamespace().equals(namespace))
                .collect(Collectors.toList());
    }

    @Override
    public List<Schema> getSchemasForPrefix(String prefix) {
        return this.schemas.stream().filter(s -> s.getPrefix().equals(prefix))
                .collect(Collectors.toList());
    }

    @Override
    public List<Schema> getSchemasForContext(Map<String, Object> context) {
        return this.schemas.stream().filter(s -> s.getObjectFilter() == null || s.getObjectFilter().matches(context))
                .collect(Collectors.toList());
    }

    @Override
    public List<Schema> getAllSchemas() {
        return this.schemas;
    }

    @Override
    public List<Schema> getAllSchemasWithUiDescriptor() {
        return this.schemas.stream().filter(s -> CommonUtils.isValid(s.getUiDescriptor())).collect(Collectors.toList());
    }

    @Override
    public List<Transformation> getTransformationsForContext(Map<String, Object> context) {
        return this.transformations.stream()
                .filter(t -> t.getObjectFilter() == null || t.getObjectFilter().matches(context))
                .collect(Collectors.toList());
    }

    /* ComponentLibraryStorage.StorageListener  */
    @Override
    public void storageUpdated() {
        reload();
    }

    protected URL resolve(ComponentLibrary library, String relative) {
        try {
            URL libraryUrl = new URL(library.getUrl() + "/");
            URI uri = libraryUrl.toURI().resolve(relative);
            return uri.toURL();
        } catch (URISyntaxException | MalformedURLException e) {
            LOG.error(String.format("Error resolving url [%s] for library [%s]", relative, library.getName()), e);
            return null;
        }
    }

    /* wrappers */

    protected List<ComponentLibrary> wrap(List<ComponentLibrary> originList) {
        return originList.stream().map(l -> new LibraryWrapper(l)).collect(Collectors.toList());
    }

    protected class LibraryWrapper implements ComponentLibrary {

        ComponentLibrary origin;

        List<ComponentLibrary.Schema> wrappedSchemas = null;

        public LibraryWrapper(ComponentLibrary origin) {
            this.origin = origin;
        }

        @Override
        public String getName() {
            return origin.getName();
        }

        @Override
        public String getUrl() {
            try {
                return EntaxyResourceURLFactory.getGlobalUrl(origin.getUrl()).toString();
            } catch (MalformedURLException e) {
                LOG.error("Error getting global URL for [{}]", origin.getUrl());
                return origin.getUrl();
            }
        }

        @Override
        public List<Schema> getSchemas() {
            if (wrappedSchemas == null) {
                wrappedSchemas = new ArrayList<>();

                for (ComponentLibrary.Schema schema : origin.getSchemas())
                    wrappedSchemas.add(new SchemaWrapper(schema, this));

            }
            return wrappedSchemas;
        }

        @Override
        public Filter getObjectFilter() {
            return origin.getObjectFilter();
        }

    }

    protected class SchemaWrapper implements ComponentLibrary.Schema {

        ComponentLibrary library;
        ComponentLibrary.Schema origin;

        List<ComponentLibrary.Transformation> wrappedTransformations = null;

        public SchemaWrapper(ComponentLibrary.Schema origin, ComponentLibrary library) {
            this.library = library;
            this.origin = origin;
        }

        @Override
        public String getRoot() {
            return origin.getRoot();
        }

        @Override
        public String getRootUrl() {
            URL resolved = resolve(library, getRoot());
            if (resolved != null)
                return resolved.toString();
            return getRoot();
        }

        @Override
        public String getNamespace() {
            return origin.getNamespace();
        }

        @Override
        public String getPrefix() {
            return origin.getPrefix();
        }

        @Override
        public boolean isPublic() {
            return origin.isPublic();
        }

        @Override
        public List<Transformation> getTransformations() {
            if (wrappedTransformations == null) {
                wrappedTransformations = new ArrayList<>();

                for (ComponentLibrary.Transformation transformation : origin.getTransformations())
                    wrappedTransformations.add(new TransformationWrapper(transformation, this));

            }
            return wrappedTransformations;
        }

        @Override
        public ComponentLibrary getLibrary() {
            return this.library;
        }

        @Override
        public Filter getObjectFilter() {
            Filter originFilter = origin.getObjectFilter();
            return originFilter == null ? library.getObjectFilter() : originFilter;
        }

        @Override
        public String getUiDescriptor() {
            return origin.getUiDescriptor();
        }

    }

    protected class TransformationWrapper implements ComponentLibrary.Transformation {

        ComponentLibrary.Transformation origin;
        ComponentLibrary.Schema schema;

        public TransformationWrapper(ComponentLibrary.Transformation origin, ComponentLibrary.Schema schema) {
            this.origin = origin;
            this.schema = schema;
        }

        @Override
        public String getProcessor() {
            return origin.getProcessor();
        }

        @Override
        public String getSource() {
            return origin.getSource();
        }

        @Override
        public String getSourceUrl() {
            if (!CommonUtils.isValid(getSource()))
                return null;
            URL resolved = resolve(schema.getLibrary(), getSource());
            if (resolved != null)
                return resolved.toString();
            return getSource();
        }

        @Override
        public Schema getSchema() {
            return this.schema;
        }

        @Override
        public Filter getObjectFilter() {
            Filter originFilter = origin.getObjectFilter();
            return originFilter == null ? schema.getObjectFilter() : originFilter;
        }
    }
}
