/*-
 * ~~~~~~licensing~~~~~~
 * test-producers
 * ==========
 * 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.core.producer.executor.objectmodel;

import java.util.Map;

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

import com.google.gson.JsonObject;

import ru.entaxy.platform.base.objects.EntaxyObject.FIELDS;

public class FactoredObjectRef extends AbstractFactored {

    private static final Logger log = LoggerFactory.getLogger(FactoredObjectRef.class);

    public static final String DIRECTIVE_AUTO_CREATED = "@AUTOCREATED";
    public static final String DIRECTIVE_REF_CONFIG = "@REF_CONFIG";

    public static final String DIRECTIVE_SOFT_UPDATE = "@SOFT_UPDATE";

    public static String IS_EXTERNAL_FIELD = "isExternal";
    public static String TARGET_ID_FIELD = "targetId";
    public static String TARGET_UUID_FIELD = "targetUuid";
    public static String TARGET_TYPE_FIELD = "targetType";
    public static String TARGET_VALUE_FIELD = "targetValue";
    public static String IS_RESOLVED_FIELD = "isResolved";
    public static String RESOLVE_ALWAYS_FIELD = "resolveAlways";
    public static String IS_LINK = "isLink";
    public static String IS_DEFINITION_ORIGIN = "isDefinitionOrigin";

    protected FactoredObject targetObject = null;

    protected String targetId = "";
    protected String targetUuid = "";
    protected String targetType = "";

    protected boolean isExternal = true;
    protected boolean isResolved = true;
    protected boolean isBackRef = false;
    protected boolean refByValueOnly = false;
    protected boolean isLink = false;
    protected boolean resolveAlways = false;
    protected boolean isDefinitionOrigin = false;

    public FactoredObjectRef(ObjectModel objectModel) {
        super(objectModel);
        this.origin = new JsonObject();
        this.origin.addProperty(FIELDS.IS_REF, true);
        this.origin.addProperty(IS_EXTERNAL_FIELD, false);
        this.origin.addProperty(TARGET_ID_FIELD, getTargetId());
        this.origin.addProperty(TARGET_UUID_FIELD, getTargetUuid());
        this.origin.addProperty(TARGET_TYPE_FIELD, getTargetType());
        this.origin.addProperty(FIELDS.FACTORY_ID, this.factoryId);
        this.origin.addProperty(FIELDS.SCOPE, this.scope);
        this.origin.addProperty(IS_RESOLVED_FIELD, this.isResolved);
        this.origin.addProperty(RESOLVE_ALWAYS_FIELD, this.resolveAlways);
        this.origin.addProperty(FIELDS.IS_BACK_REF, this.isBackRef);
        this.origin.addProperty(IS_LINK, false);
    }

    public FactoredObjectRef(ObjectModel objectModel, FactoredObject targetObject) {
        super(objectModel);
        this.origin = new JsonObject();

        this.setTargetObject(targetObject);
        this.definedIn = targetObject.definedIn;
        this.scope = targetObject.scope;
        this.factoryId = targetObject.factoryId;
        this.jsonPath = targetObject.jsonPath;
        this.relativePath = targetObject.relativePath;
        this.isExternal = false;

        this.isLink = false;

        this.origin.addProperty(FIELDS.IS_REF, true);
        this.origin.addProperty(IS_EXTERNAL_FIELD, false);
        this.origin.addProperty(TARGET_ID_FIELD, getTargetId());
        this.origin.addProperty(TARGET_UUID_FIELD, getTargetUuid());
        this.origin.addProperty(TARGET_TYPE_FIELD, getTargetType());
        this.origin.addProperty(FIELDS.FACTORY_ID, this.factoryId);
        this.origin.addProperty(FIELDS.SCOPE, this.scope);
        this.origin.addProperty(IS_RESOLVED_FIELD, this.isResolved);
        this.origin.addProperty(RESOLVE_ALWAYS_FIELD, this.resolveAlways);
        this.origin.addProperty(FIELDS.IS_BACK_REF, this.isBackRef);

        this.origin.addProperty(IS_LINK, this.isLink);

        objectModel.addRef(this);
    }

    public static boolean isRef(JsonObject jsonObject) {
        try {

            // if object has 'targetId' we assume it as a ref
            // even if there's no 'isRef'
            if (jsonObject.has(TARGET_ID_FIELD))
                if (!jsonObject.has(FIELDS.IS_REF))
                    jsonObject.addProperty(FIELDS.IS_REF, true);

            if (!jsonObject.has(TARGET_ID_FIELD) && !AbstractFactored.isFactored(jsonObject))
                return false;
            if (!jsonObject.has(FIELDS.IS_REF))
                return false;
            boolean isRef = jsonObject.get(FIELDS.IS_REF).getAsBoolean();
            return isRef;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public void wrap(JsonObject object, Map<String, Object> context, String path) {
        super.wrap(object, context, path);

        updateFromObject(object);

        AbstractFactored contextOwner = getContextOwner(context);
        if ((contextOwner != null) && (contextOwner instanceof FactoredObject)) {
            FactoredObject fo = (FactoredObject) contextOwner;
            log.debug("\t IN CONTEXT :: " + fo.factoryId + "[" + fo.jsonPath + "]");
            computeRelativePath(fo.jsonPath);
            if (null == this.ownScope || this.ownScope.isEmpty())
                this.scope = fo.scope;
            this.definedIn = fo;
        }


        objectModel.addRef(this);
    }

    public boolean isAutoGenerated() {
        return this.origin.has(DIRECTIVE_AUTO_CREATED);
    }

    public void setAutoGenerated(boolean value) {
        this.origin.remove(DIRECTIVE_AUTO_CREATED);
        if (value)
            this.origin.addProperty(DIRECTIVE_AUTO_CREATED, true);
    }

    @Override
    public void updateFromOrigin() {
        super.updateFromOrigin();
        updateFromObject(origin);
    }

    protected void updateFromObject(JsonObject object) {
        if (object.has(TARGET_ID_FIELD))
            this.targetId = object.get(TARGET_ID_FIELD).getAsString();

        if (object.has(TARGET_UUID_FIELD))
            this.targetUuid = object.get(TARGET_UUID_FIELD).getAsString();
        /*
         * if (object.has(TARGET_VALUE_FIELD)) this.targetValue =
         * object.get(TARGET_VALUE_FIELD).getAsString();
         */
        if (object.has(TARGET_TYPE_FIELD))
            this.targetType = object.get(TARGET_TYPE_FIELD).getAsString();

        if (object.has(IS_EXTERNAL_FIELD))
            try {
                this.isExternal = object.get(IS_EXTERNAL_FIELD).getAsBoolean();
            } catch (Exception e) {

            }

        if (object.has(IS_RESOLVED_FIELD))
            try {
                this.isResolved = object.get(IS_RESOLVED_FIELD).getAsBoolean();
            } catch (Exception e) {

            }

        if (object.has(RESOLVE_ALWAYS_FIELD))
            try {
                this.resolveAlways = object.get(RESOLVE_ALWAYS_FIELD).getAsBoolean();
            } catch (Exception e) {

            }

        if (object.has(IS_DEFINITION_ORIGIN))
            try {
                this.isDefinitionOrigin = object.get(IS_DEFINITION_ORIGIN).getAsBoolean();
            } catch (Exception e) {

            }

        if (object.has(FIELDS.IS_BACK_REF))
            try {
                this.isBackRef = object.get(FIELDS.IS_BACK_REF).getAsBoolean();
            } catch (Exception e) {

            }

        if (object.has(IS_LINK))
            try {
                this.isLink = object.get(IS_LINK).getAsBoolean();
            } catch (Exception e) {

            }


        if (object.has(FIELDS.IS_REF_BY_VALUE_ONLY))
            try {
                this.refByValueOnly = object.get(FIELDS.IS_REF_BY_VALUE_ONLY).getAsBoolean();
            } catch (Exception e) {

            }


    }

    public boolean isBackRef() {
        return isBackRef;
    }

    public boolean isExternal() {
        return isExternal;
    }

    public boolean isResolved() {
        return isResolved;
    }

    public void setResolved(boolean isResolved) {
        this.isResolved = isResolved;
        this.origin.remove(IS_RESOLVED_FIELD);
        this.origin.addProperty(IS_RESOLVED_FIELD, this.isResolved);
    }

    public boolean isResolveAlways() {
        return resolveAlways;
    }

    public void setResolveAlways(boolean resolveAlways) {
        this.resolveAlways = resolveAlways;
    }

    public void setBackRef(boolean isBaskRef) {
        this.isBackRef = isBaskRef;
        this.origin.remove(FIELDS.IS_BACK_REF);
        this.origin.addProperty(FIELDS.IS_BACK_REF, this.isBackRef);
    }

    public void setExternal(boolean isExternal) {
        this.isExternal = isExternal;
        this.origin.remove(IS_EXTERNAL_FIELD);
        this.origin.addProperty(IS_EXTERNAL_FIELD, this.isExternal);
    }

    public void setLink(boolean isLink) {
        this.isLink = isLink;
        this.origin.remove(IS_LINK);
        this.origin.addProperty(IS_LINK, this.isLink);
    }

    public boolean isLink() {
        return isLink;
    }

    public boolean isDefinitionOrigin() {
        return isDefinitionOrigin;
    }

    public void setDefinitionOrigin(boolean isDefinitionOrigin) {
        this.isDefinitionOrigin = isDefinitionOrigin;
    }

    public boolean isRefByValueOnly() {
        return refByValueOnly;
    }

    public void setRefByValueOnly(boolean isRefByValueOnly) {
        this.refByValueOnly = isRefByValueOnly;
    }

    public String getTargetId() {
        if (targetObject == null)
            return targetId;
        return targetObject.getObjectId();
    }

    public String getTargetUuid() {
        return targetUuid;
    }

    public String getTargetType() {
        if (targetObject == null)
            return targetType;
        return targetObject.getObjectType();
    }

    public void setTargetType(String value) {
        this.targetType = value;
        this.origin.remove(TARGET_TYPE_FIELD);
        this.origin.addProperty(TARGET_TYPE_FIELD, value);
    }

    public void refeshTargetType() {
        if (targetObject != null)
            setTargetType(targetObject.getObjectType());
    }

    public FactoredObject getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(FactoredObject targetObject) {
        if (this.targetObject != null)
            this.targetObject.references.remove(this);
        this.targetObject = targetObject;
        this.targetUuid = this.targetObject.getUuid();
        this.targetObject.references.add(this);
        if (this.origin == null)
            log.debug("ORIGIN IS NULL!!!");
        this.origin.remove(TARGET_ID_FIELD);
        this.origin.addProperty(TARGET_ID_FIELD, getTargetId());

        if (!(targetObject instanceof FactoredObjectProxy)) {
            this.targetType = targetObject.getObjectType();
            this.origin.remove(TARGET_TYPE_FIELD);
            this.origin.addProperty(TARGET_TYPE_FIELD, getTargetType());
        }

    }

}
