/*-
 * ~~~~~~licensing~~~~~~
 * object-producer-core
 * ==========
 * Copyright (C) 2020 - 2022 EmDev LLC
 * ==========
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.producer.executor.objectmodel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import ru.entaxy.platform.base.support.JSONUtils;

public class Calculation extends AbstractItem {

	// TODO move const to EntaxyObject
	public static final String FIELD_IS_CALCULATED = "isCalculated"; 
	
	public static final Object NOT_ENOUGH_DATA = new Object();
	
	public static boolean isCalculated(JsonObject jsonObject) {
		if (!jsonObject.has(FIELD_IS_CALCULATED))
			return false;
		try {
			return jsonObject.get(FIELD_IS_CALCULATED).getAsBoolean();
		} catch (Exception e) {
			return false;
		}
	}
	
	protected ExpressionCalculation expressionCalculation;
	
	public Calculation(ObjectModel objectModel) {
		super(objectModel);
	}
	
	@Override
	public void wrap(JsonObject object, Map<String, Object> context, String path) {
		if (!ExpressionCalculation.isExpressionCalculation(object))
			return;
		
		super.wrap(object, context, path);
		
		this.expressionCalculation = new ExpressionCalculation();
		this.expressionCalculation.init(object);
		
		AbstractFactored contextOwner = AbstractFactored.getContextOwner(context);
		if (contextOwner instanceof FactoredObject)
			this.definedIn = (FactoredObject)contextOwner;
		
		contextOwner.addInternal(this);
		
		computeRelativePath(contextOwner.jsonPath);
	}

	public Object calculate(ObjectModel objectModel, FactoredObject factoredObject) {
		return this.expressionCalculation.calculate(objectModel, factoredObject);
	}
	
	public static class ExpressionCalculation {
		
		// TODO move const to EntaxyObject
		public static final String ATTR_EXPRESSION = "expression"; 
		
		protected static final Object TOKEN_NOT_FOUND = new Object();
		
		JsonObject jsonObject;
		
		String expression;
		
		List<String> toResolve = new ArrayList<>();
		Map<String, Object> resolved = new HashMap<>();
		
		public static boolean isExpressionCalculation(JsonObject jsonObject) {
			return jsonObject.has(ATTR_EXPRESSION);
		}
		
		public void init(JsonObject jsonObject) {
			this.jsonObject = jsonObject;
			this.expression = jsonObject.get(ATTR_EXPRESSION).getAsString();
		    Pattern pattern = Pattern.compile("\\$\\{.+?\\}");
		    Matcher matcher = pattern.matcher(this.expression);
		    while (matcher.find()) {
		        String value = this.expression.substring(matcher.start(), matcher.end());
		        // System.out.println("\\nFOUND :: " + value);
		        this.toResolve.add(value.substring(2, value.length()-1));
		    }
		}
		
		public Object calculate(ObjectModel objectModel, FactoredObject factoredObject) {
			for (String expr: toResolve) {
				if (!resolved.containsKey(expr)) {
					Object result = resolve(expr, objectModel, factoredObject);
					if (!TOKEN_NOT_FOUND.equals(result)) {
						resolved.put(expr, result);
					}
				}
			}
			toResolve.removeAll(resolved.keySet());
			if (toResolve.isEmpty()) {
				return doCalculate();
			}
			return NOT_ENOUGH_DATA;
		}
		
		public Object doCalculate() {
			String result = expression;
			for (Entry<String, Object> entry: resolved.entrySet()) {
				Object value = entry.getValue();
				String val = value==null?"":value.toString();
				result = result.replace("${" + entry.getKey() + "}", val);
			}
			return result;
		}
		
		public Object resolve(String token, ObjectModel objectModel, FactoredObject factoredObject) {
			JsonElement result = findElement(factoredObject.origin, token);
			if (result == null)
				return TOKEN_NOT_FOUND;
			return JSONUtils.element2object(result);
		}
		
		public JsonElement findElement(JsonObject jsonObject, String pathFragment) {
			JsonElement result = findElement(jsonObject, pathFragment, "");
			
			return result;
		}

		public JsonElement findElement(JsonObject jsonObject, String pathFragment, String parentPath) {
			
			for (Entry<String, JsonElement> entry: jsonObject.entrySet()) {
				
				String currentPath = parentPath + "." + entry.getKey();
				if (currentPath.endsWith(pathFragment))
					return entry.getValue();
				
			}
			
			for (Entry<String, JsonElement> entry: jsonObject.entrySet()) {
				
				if (!entry.getValue().isJsonObject())
					continue;
				
				String currentPath = parentPath + "." + entry.getKey();
				
				JsonElement currentResult = findElement(entry.getValue().getAsJsonObject(), pathFragment, currentPath);
				
				if (currentResult != null)
					return currentResult;
			}
			
			
			return null;
		}
		
	}
	
}
