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

import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

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

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

import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.JSONUtils;
import ru.entaxy.platform.base.objects.EntaxyObject.FIELDS;
import ru.entaxy.platform.base.objects.EntaxyObject.HEADERS;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.core.producer.api.EntaxyProducerService;
import ru.entaxy.platform.core.producer.api.ProducerResult;
import ru.entaxy.platform.core.producer.api.ProducerResult.CommandResult;
import ru.entaxy.platform.core.producer.executor.objectmodel.FactoredObject;

public class GenerateTask extends AbstractObjectTask {

	private static final Logger log = LoggerFactory.getLogger(GenerateTask.class);
	
	public static final String INSTRUCTIONS_FIELD = "##instructions";
	public static final String GENERATION_PROPERTIES_FIELD = "##generation";
	
	public String factoryId;
	public String factoryType;
	//public String objectId;
	public JsonElement properties;

	public String outputType = "init";
	public String scope = "public";
	
	public Map<String, Object> generationProperties = new HashMap<>();
	
	public GenerateTask(FactoredObject factoredObject) {
		super(factoredObject);
		fillFromOrigin();
	}
	
	@Override
	public boolean execute(ProducerResult currentResult, CommandResult currentCommand,
			EntaxyProducerService producerService, Map<String, Object> instructions) throws Exception {
		if (!CommonUtils.isValid(factoryId))
			throw new IllegalArgumentException("Factory ID is null");
		if (!CommonUtils.isValid(factoryType))
			try {
				EntaxyFactory f = producerService.findFactoryById(factoryId);
				if (f != null) {
					log.debug("Factory found by id [{}] with type [{}]", f.getFactoryId(), f.getFactoryType());
					factoryType = f.getFactoryType();
				} else
					throw new IllegalArgumentException("Factory not found: " + factoryId);
			} catch (Exception e) {
				throw e;
			}
		log.debug("Execute for type [{}] with factoryId [{}]", factoryType, factoryId);
		Map<String, Object> effectiveProperties = createEffectiveProperties(instructions);
		this.generatedResult = producerService.getProducerForType(factoryType).getFactoryById(factoryId).generate(outputType, scope, effectiveProperties);
		// objectId could be changed by pre/post processor, so we update it
		// TODO move string to const
		String objectId = this.generatedResult.getProperties()
				.getOrDefault(FIELDS.OBJECT_ID, origin.getObjectId()).toString();
		if (!origin.getObjectId().equals(objectId)) {
			log.debug("Changing objectId from [{}] to [{}]", origin.getObjectId(), objectId);
			origin.setObjectId(objectId);
		}
		GeneratedHeaders headers = GeneratedHeaders.getHeaders(this.generatedResult.getProperties());
		headers.set(HEADERS.MAIN_OBJECT, origin.getObjectId() + ":" + factoryType);
		headers.append(HEADERS.GENERATED_OBJECTS, origin.getObjectId() + ":" + factoryType);
		// headers.set(GeneratedHeaders.HEADER_MAIN_OBJECT_TYPE, factoryType);
		// headers.set(GeneratedHeaders.HEADER_MAIN_OBJECT_FACTORY_ID, factoryId);
		// headers.append(GeneratedHeaders.HEADER_OBJECT_ID, origin.getObjectId());
		// headers.append(GeneratedHeaders.HEADER_OBJECT_TYPE, factoryType);
		headers.append(HEADERS.GENERATED_OBJECTS_CONFIG
				, Base64.getEncoder().encodeToString(origin.origin.toString().getBytes()));
		this.generationProperties.clear();
		this.generationProperties.putAll(effectiveProperties);
		this.generatedResult.getProperties().put(GENERATION_PROPERTIES_FIELD, new HashMap<String, Object>(this.generationProperties));
		return true;
	}
	
	protected Map<String, Object> createEffectiveProperties(Map<String, Object> instructions){
		Map<String, Object> result = new HashMap<>();
		result.put(INSTRUCTIONS_FIELD, instructions);
		JsonElement effectiveProperties = this.properties.deepCopy();
		if (effectiveProperties.isJsonObject()) {
			JsonObject jobj = effectiveProperties.getAsJsonObject();
			for (Entry<String, JsonElement> entry: origin.origin.entrySet()) {
				/*if (entry.getKey().equals(FIELDS.PROPERTIES))
					continue;*/
				if (jobj.has(entry.getKey()))
					continue;
				jobj.add(entry.getKey(), entry.getValue());
			}
			result.putAll(JSONUtils.element2map(effectiveProperties));	
		}
		return result;
	}
	
	public void fillFromOrigin() {
		this.factoryId = origin.factoryId;
		// this.objectId = origin.getObjectId();
		this.factoryType = origin.getObjectType();
		this.scope = origin.scope;
		this.properties = origin.origin.get(FIELDS.PROPERTIES);
		
		if (CommonUtils.isValid(origin.getOutputType()))
			this.outputType = origin.getOutputType();
		
	}
	
	public void updateObjectId(String objectId) {
		// this.objectId = objectId;
		this.origin.setObjectId(objectId);
	}
	
	@Override
	public String getInfo() {
		return "GENERATE " + "[" + origin.getObjectId() + "] :: " + factoryId + "/" + outputType;
	}
}
