/*-
 * ~~~~~~licensing~~~~~~
 * connection-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.base.objects.connection.producing.adapters;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import ru.entaxy.platform.base.objects.EntaxyObject;
import ru.entaxy.platform.base.objects.EntaxyObject.OBJECT_TYPES;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory.CONFIGURATION.OUTPUTS;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryUtils;
import ru.entaxy.platform.base.objects.factory.configuration.FactoryElement;
import ru.entaxy.platform.base.objects.factory.tracker.TrackedFactoryManager;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;

public class ConfigConverter {

	private static final Logger log = LoggerFactory.getLogger(ConfigConverter.class);
	
	public static Set<String> supportedRegulars = EntaxyFactory.CONFIGURATION.FIELDS.REGULAR_TYPES.asSet();
	
	protected static Gson gson = new Gson();
	
	public static String convertFromAdapter(String adapterMetadata) {
		
		String result = "{}";
		
		Map<String, Map<String, JsonObject>> fieldsForOutputs = new HashMap<>();
		
		JsonObject metadataRoot = JSONUtils.getJsonRootObject(adapterMetadata);
		JsonObject configuration = new JsonObject();
		
		JsonObject metadata = metadataRoot.get("adapter").getAsJsonObject();

		log.info("CONVERTING CONFIG FOR: [" + metadata.get("id").getAsString() + "]");
		
		// add 'factory' section
		
		FactoryElement factoryElement = new FactoryElement();
		factoryElement.setId(metadata.get("id").getAsString());
		factoryElement.setType(OBJECT_TYPES.CONNECTION);
		
		JsonObject factoryJson = gson.toJsonTree(factoryElement).getAsJsonObject();
		factoryJson.addProperty(EntaxyFactory.CONFIGURATION.FACTORY.PARENT
				, TrackedFactoryManager.DEFAULT_PARENT);
		configuration.add(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME, factoryJson);
		
		// add type specific section
		
		configuration.add(OBJECT_TYPES.CONNECTION, new JsonObject());

		// add fields
		
		JsonObject targetFields = new JsonObject();
		JsonArray sourceFields = metadata.get("fields").getAsJsonArray();
		
		for (int i=0; i<sourceFields.size(); i++) {
			JsonObject sourceField = sourceFields.get(i).getAsJsonObject();
			ConvertedField convertedField = convertField(sourceField);
			targetFields.add(convertedField.name, convertedField.fieldObject);
			for (Entry<String, JsonObject> entry: convertedField.outputConfiguration.entrySet()) {
				if (!fieldsForOutputs.containsKey(entry.getKey()))
					fieldsForOutputs.put(entry.getKey(), new HashMap<>());
				((Map<String, JsonObject>)fieldsForOutputs.get(entry.getKey())).put(convertedField.name, entry.getValue());
			}
		}
		
		configuration.add(EntaxyFactory.CONFIGURATION.FIELDS_SECTION_NAME, targetFields);

		// add outputs
		
		JsonObject targetOutputs = new JsonObject();
		JsonArray sourceGenerators = metadata.get("generators").getAsJsonArray();
		
		for (int i=0; i<sourceGenerators.size(); i++) {
			JsonObject sourceGenerator = sourceGenerators.get(i).getAsJsonObject();
			JsonObject targetOutput = new JsonObject();
			
			log.debug("GENERATOR: " + sourceGenerator.toString());
			
			// name			
			String name = sourceGenerator.get("type").getAsString();
			log.debug("GENERATOR NAME: " + name);
			
			// isDefault: true for 'init'
			if (EntaxyFactory.CONFIGURATION.OUTPUTS.OUTPUT_TYPE_INIT.equals(name))
				targetOutput.addProperty(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.IS_DEFAULT
						, true);
			
			// generator
			if (sourceGenerator.has("generator"))
				targetOutput.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.GENERATOR
						, sourceGenerator.get("generator"));
			
			// config
			if (sourceGenerator.has("config"))
				targetOutput.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.CONFIG
						, sourceGenerator.get("config"));
			
			// fields for output
			if (fieldsForOutputs.containsKey(name)) {
				Map<String, JsonObject> fieldsForOutput = (Map<String, JsonObject>)fieldsForOutputs.get(name);
				if (!fieldsForOutput.isEmpty()) {
					JsonObject fields = new JsonObject();
					for (Entry<String, JsonObject> entry: fieldsForOutput.entrySet())
						fields.add(entry.getKey(), entry.getValue());
					targetOutput.add(EntaxyFactory.CONFIGURATION.OUTPUTS.ATTRIBUTES.FIELDS, fields);
				}
			}
				
			targetOutputs.add(name, targetOutput);
			
		}
		
		configuration.add(EntaxyFactory.CONFIGURATION.OUTPUTS_SECTION_NAME, targetOutputs);
		
		// return
		
		result = configuration.toString();
		
		return result;
	}
	
	protected static class ConvertedField {
		String name;
		FieldType fieldType;
		JsonObject fieldObject;
		Map<String, JsonObject> outputConfiguration = new HashMap<>();
		
		public void addToOutput(String outputName, JsonObject configuration) {
			outputConfiguration.put(outputName, configuration);
		}
	}
	
	public static ConvertedField convertField(JsonObject sourceField) {
		ConvertedField result = new ConvertedField();
		JsonObject resultJson = new JsonObject();
		
		// name
		
		result.name = sourceField.get("name").getAsString();
		
		// field type
		
		String type = null;
		if (sourceField.has("type"))
			type = sourceField.get("type").getAsString();
		if (!CommonUtils.isValid(type))
			type = EntaxyFactory.CONFIGURATION.FIELDS.REGULAR_TYPES.STRING;
		
		FieldType targetType = new FieldType(type);
		result.fieldType = targetType;
		
		resultJson.addProperty(EntaxyFactory.CONFIGURATION.FIELDS.ATTRIBUTES.FIELD_TYPE, targetType.targetFieldType);
		if (targetType.isEntaxyType) {
			resultJson.addProperty(EntaxyFactory.CONFIGURATION.FIELDS.ATTRIBUTES.IS_REF, true);
		}
		
		// default value
		
		boolean hasDefaultValue = false;
		
		if (sourceField.has("defaultValue")) {
			hasDefaultValue = true;
			String defaultValue = sourceField.get("defaultValue").getAsString();
			if (CommonUtils.isValid(defaultValue)) {
				Object targetDefaultValue = targetType.convertValue(defaultValue);
				JsonElement je = gson.toJsonTree(targetDefaultValue);
				resultJson.add(EntaxyFactory.CONFIGURATION.FIELDS.ATTRIBUTES.DEFAULT_VALUE, je);
			}
		}

		// required and immutable
		
		boolean isProperty = readBoolean(sourceField, "property", false);
		boolean isOption = readBoolean(sourceField, "option", false);
		
		boolean isRequired = hasDefaultValue;
		boolean isImmutable = false;
		
		if (isProperty) {
			
			if (isOption) {
				// not immutable and needed for init
				isImmutable = false;
				result.addToOutput(OUTPUTS.OUTPUT_TYPE_INIT, new JsonObject());
			} else {
				// immutable and needed for init
				isImmutable = true;
				result.addToOutput(OUTPUTS.OUTPUT_TYPE_INIT, new JsonObject());
			}
			
		} else {

			if (isOption) {
				// not immutable and not needed for init
				isImmutable = false;
			} else {
				// some hidden field, no idea how to process it
			}
			
		}

		resultJson.addProperty(EntaxyFactory.CONFIGURATION.FIELDS.ATTRIBUTES.REQUIRED, isRequired);
		resultJson.addProperty(EntaxyFactory.CONFIGURATION.FIELDS.ATTRIBUTES.IMMUTABLE, isImmutable);
		
		result.fieldObject = resultJson;
		
		return result;
	}
	
	protected static boolean readBoolean(JsonObject sourceField, String name, boolean defaultValue) {
		boolean result = defaultValue;
		
		if (sourceField.has(name))
			try {
				result = sourceField.get(name).getAsBoolean(); 
			} catch (Exception e) {
				
			}
		
		return result;
	}
	
	protected static class FieldType {
		String targetFieldType;
		String sourceFieldType;
		boolean isEntaxyType = false;
		boolean isClassOrService = false;
		boolean isRegular = true;
		
		public FieldType(String sourceFieldType) {
			this.sourceFieldType = sourceFieldType;
			
			if (!checkRegular(this.sourceFieldType))
				if (!checkEntaxyRuntime(this.sourceFieldType)) {
					// assume as Service or Class type
					this.isRegular = false;
					this.isEntaxyType = false;
					this.isClassOrService = true;
				}
		}

		protected boolean checkRegular(String sourceValue) {
			Set<String> foundRegulars = supportedRegulars.stream().filter(val -> val.equalsIgnoreCase(sourceValue))
					.collect(Collectors.toSet());
			if (foundRegulars.size() == 0) {
				this.isRegular = false;
				return false;
			}
			
			this.targetFieldType = foundRegulars.iterator().next();
			this.isRegular = true;
			this.isEntaxyType = false;
			this.isClassOrService = false;
			return true;
			
		}
		
		protected boolean checkEntaxyRuntime(String sourceValue) {
			boolean result = EntaxyObject.OBJECT_TYPES.isRuntimeType(sourceValue);
			if (result) {
				this.isRegular = false;
				this.isEntaxyType = true;
				this.isClassOrService = false;
			}
			return result;
		}
		
		public Object convertValue(String value) {
			if (EntaxyFactory.CONFIGURATION.FIELDS.REGULAR_TYPES.BOOLEAN.equals(this.targetFieldType)) {
				try {
					Boolean result = Boolean.valueOf(value);
					return result;
				} catch (Exception e) {
					return false;
				}
			}
			if (EntaxyFactory.CONFIGURATION.FIELDS.REGULAR_TYPES.LONG.equals(this.targetFieldType)) {
				try {
					Long result = Long.valueOf(value);
					return result;
				} catch (Exception e) {
					return Long.valueOf(0);
				}
			}
			if (EntaxyFactory.CONFIGURATION.FIELDS.REGULAR_TYPES.DOUBLE.equals(this.targetFieldType)) {
				try {
					Double result = Double.valueOf(value);
					return result;
				} catch (Exception e) {
					return Double.valueOf(0);
				}
			}
			return value;
		}
		
	}
	
}
