/*-
 * ~~~~~~licensing~~~~~~
 * config-runtime
 * ==========
 * 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.config.runtime;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.felix.utils.properties.TypedProperties;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.config.runtime.ObjectConfig.ConfigFieldHint;

public class ObjectConfigEditor {

    public static ObjectConfigEditor create(File configFile) throws IOException {
        return new ObjectConfigEditor(configFile);
    }

    protected String pid;

    protected File configFile;

    protected TypedProperties loadedProperties;

    protected ObjectConfig.ConfigHint configHint = new ObjectConfig.ConfigHint();

    protected TypedProperties modifiedProperties = new TypedProperties();

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

    protected Map<String, ObjectConfig.ConfigFieldHint> fieldHints = new HashMap<>();

    protected ObjectConfigEditor(File configFile) throws IOException {
        super();
        this.configFile = configFile;
        loadedProperties = new TypedProperties();
        loadedProperties.load(configFile);
        configHint.readFrom(loadedProperties);
        fieldHints = configHint.getFieldsHint();
    }

    public void loadCurrent(String pid) throws Exception {
        ServiceReference<ConfigurationAdmin> ref = null;
        try {
            ref = ObjectConfigHelper.getInstance().getReference(ConfigurationAdmin.class);
            ConfigurationAdmin configurationAdmin = ObjectConfigHelper.getInstance().getService(ref);

            int pos = pid.indexOf('~');
            if (pos >= 0) {
                pid = pid.substring(0, pos);
            }
            Configuration configuration = configurationAdmin.getConfiguration(pid, "?");

            currentProperties.clear();
            if (configuration.getProperties() == null) {
                return;
            }
            Enumeration<String> keys = configuration.getProperties().keys();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement();
                currentProperties.put(key, configuration.getProperties().get(key));
            }

        } finally {
            if (ref != null)
                ObjectConfigHelper.getInstance().ungetService(ref);
        }

    }

    public boolean isDirty() {
        return !modifiedProperties.isEmpty();
    }

    public Set<String> getPropertiesNames() {
        Set<String> result = new HashSet<>();
        result.addAll(loadedProperties.keySet());
        result.addAll(currentProperties.keySet());

        // remove hint field itself
        result.remove(ObjectConfig.CONFIG_HINT_FIELD_NAME);
        // remove felix property
        result.remove("felix.fileinstall.filename");
        // remove osgi properties
        result.remove(Constants.SERVICE_PID);


        return result;
    }

    public ConfigProperty getAsConfigProperty(String name) {
        if (!getPropertiesNames().contains(name))
            return null;
        ConfigProperty result = new ConfigProperty(name);
        if (loadedProperties.containsKey(name))
            result.configValue = loadedProperties.get(name);
        if (currentProperties.containsKey(name))
            result.currentValue = currentProperties.get(name);
        if (modifiedProperties.containsKey(name))
            result.modifiedValue = modifiedProperties.get(name);
        if (fieldHints.containsKey(name))
            result.hint = fieldHints.get(name);
        return result;
    }

    public void setValue(String propertyName, String propertyValue) throws Exception {
        if (!loadedProperties.containsKey(propertyName) && !currentProperties.containsKey(propertyName))
            throw new IllegalArgumentException("Unknown property: [" + propertyName + "]");
        ConfigProperty property = getAsConfigProperty(propertyName);
        String dataType = CommonUtils.getValid(property.hint.getType(), "string").toLowerCase();

        Object valueToSet = propertyValue;

        if ("boolean".equals(dataType)) {
            try {
                valueToSet = Boolean.parseBoolean(propertyValue);
            } catch (Exception e) {
                valueToSet = Boolean.FALSE;
            }
        } else if ("number".equals(valueToSet)) {
            try {
                valueToSet = NumberUtils.createNumber(propertyValue);
            } catch (Exception e) {
                valueToSet = 0;
            }

        }

        modifiedProperties.put(propertyName, valueToSet);

    }

    public File getConfigFile() {
        return configFile;
    }

    public void save() throws IOException {
        if (!isDirty())
            return;
        loadedProperties.putAll(modifiedProperties);
        loadedProperties.save(configFile);
    }

    public void clear() {
        FileUtils.deleteQuietly(configFile);
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public static class ConfigProperty {
        public ConfigFieldHint hint;
        public Object configValue = null;
        public Object currentValue = null;
        public Object modifiedValue = null;

        public String name;

        public ConfigProperty(String name) {
            this.name = name;
            this.hint = new ConfigFieldHint(name);
        }

        public boolean isDirty() {
            return modifiedValue != null;
        }

    }

}
