/*-
 * ~~~~~~licensing~~~~~~
 * test-producers
 * ==========
 * 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.core.producer.wrapper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.platform.runtime.base.connecting.generator.Generated;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryException;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryUtils;
import ru.entaxy.platform.core.producer.api.EntaxyWrappedFactory;

public class DefaultFactoryWrapper extends AbstractFactoryWrapper implements EntaxyWrappedFactory {

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

    public static final String ANY_OUTPUT_TYPE = "__all";

    protected EntaxyFactory origin;

    // outputType -> extrafields map
    protected Map<String, Map<String, FieldInfo>> extraFields = new HashMap<>();

    protected Map<String, List<FieldInfo>> wrappedFieldsCache = new HashMap<>();

    public DefaultFactoryWrapper(EntaxyFactory origin) {
        super(origin);
        this.origin = origin;
    }

    @Override
    public void addField(String outputType, FieldInfo fieldInfo) {
        if (!extraFields.containsKey(outputType))
            extraFields.put(outputType, new HashMap<>());
        this.extraFields.get(outputType).put(fieldInfo.getName(), fieldInfo);
        clearCache();
    }

    @Override
    public void addField(FieldInfo fieldInfo) {
        this.addField(ANY_OUTPUT_TYPE, fieldInfo);
        clearCache();
    }

    protected void clearCache() {
        synchronized (wrappedFieldsCache) {
            wrappedFieldsCache.clear();
        }
    }

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

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

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

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

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

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

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

    @Override
    public List<OutputInfo> getOutputs() {
        return origin.getOutputs();
    }

    @Override
    public OutputInfo getDefaultOutput() {
        return origin.getDefaultOutput();
    }

    @Override
    public OutputInfo getOutputByType(String outputType) {
        return origin.getOutputByType(outputType);
    }

    @Override
    public List<FieldInfo> getFields() {
        return getFields(getDefaultOutput().getType());
    }

    @Override
    public List<FieldInfo> getFields(String outputType) {
        if (!wrappedFieldsCache.containsKey(outputType))
            wrapFields(outputType);
        return wrappedFieldsCache.get(outputType);
    }

    protected void wrapFields(String outputType) {
        List<FieldInfo> result = new ArrayList<>();
        if (origin.getOutputByType(outputType) != null) {

            Map<String, FieldInfo> extras = new HashMap<>();
            if (extraFields.containsKey(ANY_OUTPUT_TYPE))
                extras.putAll(extraFields.get(ANY_OUTPUT_TYPE));
            if (extraFields.containsKey(outputType))
                extras.putAll(extraFields.get(outputType));
            result.addAll(extras.values());
            result.addAll(origin.getFields(outputType));

        }
        List<FieldInfo> wrappers = new LinkedList<>();
        for (FieldInfo fi : result) {
            if (fi.isRef())
                wrappers.add(new RefFieldWrapper((RefFieldInfo) fi));
            else
                wrappers.add(new FieldWrapper(fi));
        }
        wrappedFieldsCache.put(outputType, wrappers);
    }

    protected Map<String, Object> wrapParameters(Map<String, Object> parameters) {
        Map<String, Object> result = new HashMap<>(parameters);
        Map<String, Object> properties = new HashMap<>(parameters);
        result.put("allProperties", properties);
        return result;
    }

    @Override
    public Generated generate(Map<String, Object> parameters) throws EntaxyFactoryException {
        /*		if (generationProcessor != null)
        			generationProcessor.preGenerate(this, null, null, getFactoryType(), parameters);
        		Generated g = origin.generate(wrapParameters(parameters));
        		if (generationProcessor != null)
        			generationProcessor.postGenerate(this, null, null, getFactoryType(), parameters, g);
        		return g;
        */
        return generate(null, parameters);
    }

    @Override
    public Generated generate(String outputType, Map<String, Object> parameters) throws EntaxyFactoryException {
        /*		if (generationProcessor != null)
        			generationProcessor.preGenerate(this, outputType, null, getFactoryType(), parameters);
        		Generated g = origin.generate(outputType, wrapParameters(parameters));
        		if (generationProcessor != null)
        			generationProcessor.postGenerate(this, outputType, null, getFactoryType(), parameters, g);
        		return g;
        */
        return generate(outputType, null, parameters);
    }

    @Override
    public Generated generate(String outputType, String scope, Map<String, Object> parameters)
            throws EntaxyFactoryException {
        log.debug("generate :: processor is [{}]", (generationProcessor == null ? "" : "NOT ") + "null");
        if (generationProcessor != null)
            generationProcessor.preGenerate(this, outputType, scope, getFactoryType(), parameters);
        preGenerateWithPlugins(outputType, scope, parameters);
        Generated g = origin.generate(outputType, scope, wrapParameters(parameters));
        postGenerateWithPlugins(outputType, scope, parameters, g);
        if (generationProcessor != null)
            generationProcessor.postGenerate(this, outputType, scope, getFactoryType(), parameters, g);
        return g;
    }

    protected void preGenerateWithPlugins(String outputType, String scope, Map<String, Object> parameters) {

        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
        try {
            List<ServiceReference<GenerationProcessor>> refs = /*bundleContext.getAllServiceReferences(
                                                               GenerationProcessor.class.getName()
                                                               , "(|(generation.factory.type~=*)(generation.factory.type~=" + getFactoryType() + "))");*/
                    GenerationProcessorService.getInstance().getProcessorsRefs(this);

            log.debug("FOUND SERVICES: " + (refs == null ? "0" : refs.size()));
            if (refs == null)
                return;
            for (ServiceReference<GenerationProcessor> r : refs) {
                try {
                    GenerationProcessor processor = (GenerationProcessor) bundleContext.getService(r);
                    processor.preGenerate(this, outputType, scope, getFactoryType(), parameters);
                } catch (Exception e) {

                } finally {
                    try {
                        bundleContext.ungetService(r);
                    } catch (Exception e) {
                        log.warn("Error ungetting service", e);
                    }
                }
            }

        } catch (Exception e) {
            log.error("Error getting services", e);
        }

    }

    protected void postGenerateWithPlugins(String outputType, String scope, Map<String, Object> parameters,
            Generated generated) {
        BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
        try {
            List<ServiceReference<GenerationProcessor>> refs = /* bundleContext.getAllServiceReferences(
                                                               GenerationProcessor.class.getName()
                                                               , "(|(generation.factory.type~=*)(generation.factory.type~=" + getFactoryType() + "))"); */
                    GenerationProcessorService.getInstance().getProcessorsRefs(this);

            log.debug("FOUND SERVICES: " + (refs == null ? "0" : refs.size()));
            if (refs == null)
                return;
            for (ServiceReference<GenerationProcessor> r : refs) {
                try {
                    GenerationProcessor processor = (GenerationProcessor) bundleContext.getService(r);
                    processor.postGenerate(this, outputType, scope, getFactoryType(), parameters, generated);
                } catch (Exception e) {

                } finally {
                    try {
                        bundleContext.ungetService(r);
                    } catch (Exception e) {
                        log.warn("Error ungetting service", e);
                    }
                }
            }

        } catch (Exception e) {
            log.error("Error getting services", e);
        }
    }

    @Override
    public EntaxyFactory getOrigin() {
        return this.origin;
    }

    @Override
    public Map<String, Object> getTypeInfo() {
        return this.origin.getTypeInfo();
    }

    @Override
    public String getJsonConfiguration() {
        return EntaxyFactoryUtils.getEffectiveJson(this);
    }

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

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

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

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

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