/*-
 * ~~~~~~licensing~~~~~~
 * object-runtime-core
 * ==========
 * 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.objects.runtime.impl.manager;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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

import ru.entaxy.platform.core.artifact.legacy.BundleController;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject.ENTAXY_RUNTIME_OBJECT_STATE;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectManager.ENTAXY_RUNTIME_OBJECT_OPERATION;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectManager.OperationConfig;
import ru.entaxy.platform.objects.runtime.impl.EntaxyRuntimeObjectServiceImpl;

public abstract class OperationExecutor<T extends OperationExecutor<T>> {
    
    private static final Logger log = LoggerFactory.getLogger(OperationExecutor.class);

	EntaxyRuntimeObjectManagerImpl objectManager;
	
	public T objectManager(EntaxyRuntimeObjectManagerImpl objectManager) {
		this.objectManager = objectManager;
		return (T)this;
	}
	
	public OperationResultImpl execute(OperationConfig operationConfig) {
		EntaxyRuntimeObject runtimeObject = getObjectFromConfig(operationConfig);
		if (runtimeObject == null)
			return OperationResultImpl.createFailed().message("Object not found: " + operationConfig.getObjectFullId());
		Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> prerequisites = getPrerequisites(operationConfig, runtimeObject);
		if (!prerequisites.isEmpty()) {
		    log.warn("Operation aborted due to failure to meet " + operationConfig.getObjectFullId() + " prerequisites");
			return OperationResultImpl.createBlocked().affected(prerequisites);
		}
		if (operationConfig.isForced()) {
			// execute all operations on affected objects
			operationConfig.getAffected().clear();
			operationConfig.getAffected().putAll(getAffected(operationConfig));
			return doExecute(operationConfig, runtimeObject);
		}
		if (operationConfig.isSoft()) {
			// NOT execute operations on affected objects
			operationConfig.getAffected().clear();
			return doExecute(operationConfig, runtimeObject);
		}
		if (checkAffected(operationConfig)) {
			return doExecute(operationConfig, runtimeObject);
		}
		return OperationResultImpl.createRequest(getAffected(operationConfig));
	}

	protected Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> createMapForObjects(List<EntaxyRuntimeObject> objects, ENTAXY_RUNTIME_OBJECT_OPERATION operation) {
		Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> map = new HashMap<>();
		for (EntaxyRuntimeObject obj: objects)
			map.put(obj.getObjectFullId(), operation);
		return map;
	}
	
	protected boolean colocated(EntaxyRuntimeObject...objects) {
		if (objects.length == 0)
			return true;
		String containerId = objects[0].getContainer().getId();
		for (int i=0; i<objects.length; i++)
			if (!objects[i].getContainer().getId().equals(containerId))
				return false;
		return true;
	}
	
	protected ENTAXY_RUNTIME_OBJECT_STATE getObjectState(EntaxyRuntimeObject object) {
		return EntaxyRuntimeObjectServiceImpl.getInstance().getObjectState(object);
	}
	
	protected ENTAXY_RUNTIME_OBJECT_STATE getObjectState(EntaxyRuntimeObject object, ENTAXY_RUNTIME_OBJECT_STATE expectedState) {
        if (expectedState != null) {
            synchronized (this) {
                int limit = 0;
                EntaxyRuntimeObjectServiceImpl objService = EntaxyRuntimeObjectServiceImpl.getInstance();
                while (!(expectedState.equals(objService.getObjectState(object)))  && limit++ < 30) {
                    try {
                        this.wait(1000);
                    } catch (InterruptedException e) {
                        log.trace(e.getMessage(), e);
                    }
                }
            }
        }
        return EntaxyRuntimeObjectServiceImpl.getInstance().getObjectState(object);
    }
	
	protected EntaxyRuntimeObject getObjectFromConfig(OperationConfig operationConfig) {
		try {
			return EntaxyRuntimeObjectServiceImpl.getInstance().getRuntimeObject(operationConfig.getObjectFullId());
		} catch (Exception e) {
			return null;
		}
	}
	
	protected Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> getAffected(OperationConfig operationConfig){
		EntaxyRuntimeObject object = getObjectFromConfig(operationConfig);
		if (object == null)
			return new HashMap<>();
		return doGetAffected(operationConfig, object);
	};
	
	protected abstract Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> doGetAffected(
			OperationConfig operationConfig
			, EntaxyRuntimeObject runtimeObject);

	protected Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> getPrerequisites(
			OperationConfig operationConfig
			, EntaxyRuntimeObject runtimeObject){
		return Collections.emptyMap();
	};
	
	protected boolean checkAffected(OperationConfig operationConfig) {
		Map<String, ENTAXY_RUNTIME_OBJECT_OPERATION> affected = getAffected(operationConfig);
		for (Entry<String, ENTAXY_RUNTIME_OBJECT_OPERATION> entry: operationConfig.getAffected().entrySet()) {
			if (!affected.containsKey(entry.getKey()))
				// found not affected object
				return false;
			if (!affected.get(entry.getKey()).equals(entry.getValue()))
				// operation not equal
				return false;
		}
		return affected.size() == operationConfig.getAffected().size();
	}
	
	protected abstract OperationResultImpl doExecute(OperationConfig operationConfig, EntaxyRuntimeObject runtimeObject);
	
	// TOOD return local or cluster controller depending on object location
	protected BundleController getBundleController(EntaxyRuntimeObject runtimeObject) {
		return EntaxyRuntimeObjectServiceImpl.getInstance().getArtifactService()
				.installers().cluster().bundleController();
	}
	
}
