/*-
 * ~~~~~~licensing~~~~~~
 * application-impl
 * ==========
 * 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.integration.applications.impl.project;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.gson.JsonObject;

import ru.entaxy.esb.resources.EntaxyResource;
import ru.entaxy.platform.base.support.JSONUtils;
import ru.entaxy.platform.integration.applications.ApplicationProjectContent;
import ru.entaxy.platform.integration.applications.ApplicationProjectItem;
import ru.entaxy.platform.integration.applications.EntaxyApplication;
import ru.entaxy.platform.integration.applications.exceptions.ItemException;
import ru.entaxy.platform.integration.applications.exceptions.ItemIsGhostException;
import ru.entaxy.platform.integration.applications.exceptions.ItemIsPlatformException;
import ru.entaxy.platform.integration.applications.exceptions.ItemNotFoundException;
import ru.entaxy.platform.integration.applications.impl.ServiceHelper;
import ru.entaxy.platform.integration.applications.impl.project.item.ItemFactory;
import ru.entaxy.platform.integration.applications.impl.project.model.ContentModel;
import ru.entaxy.platform.integration.applications.impl.project.model.item.ContentModelItem;
import ru.entaxy.platform.integration.applications.item.project.EntaxyObjectItem;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectResource;

public class ApplicationProjectContentImpl implements ApplicationProjectContent {

    final protected Map<String, Object> properties = new HashMap<>();

    final protected Map<String, ApplicationProjectItemImpl> items = new HashMap<>();

    final protected Object itemsLock = new Object();

    protected ApplicationProjectItemImpl getItemImpl(String itemId) throws ItemException {
        ApplicationProjectItemImpl result = items.get(itemId);
        if (result == null)
            throw new ItemNotFoundException(null, null, itemId);
        return result;
    }

    protected JsonObject getAsJson() {
        ContentModel contentModel = new ContentModel();
        contentModel.update(this);
        return contentModel.getAsJson();
    }

    public String marshall() {
        return getAsJson().toString();
    }

    public void unmarshall(String data) {

        ContentModel contentModel = ContentModel.load(JSONUtils.getJsonRootObject(data));
        properties.put(STANDARD_PROPERTIES.APPLICATION, contentModel.getApplication());
        properties.put(STANDARD_PROPERTIES.GROUP, contentModel.getGroup());
        properties.put(STANDARD_PROPERTIES.VERSION, contentModel.getVersion());
        properties.put(STANDARD_PROPERTIES.CREATED_ON, contentModel.getCreatedOn());
        properties.put(STANDARD_PROPERTIES.CREATED_BY, contentModel.getCreatedBy());
        properties.put(STANDARD_PROPERTIES.MODIFIED_ON, contentModel.getModifiedOn());
        properties.put(STANDARD_PROPERTIES.MODIFIED_BY, contentModel.getModifiedBy());

        for (ContentModelItem modelItem : contentModel.getIncludedItems()) {
            ApplicationProjectItem item;
            switch (modelItem.getItemType()) {
                case BUNDLE:
                    continue;
                case CONFIG:
                case OBJECT:
                    EntaxyRuntimeObject runtimeObject = ServiceHelper.getInstance().getEntaxyRuntimeObjectService()
                            .getRuntimeObject(modelItem.getId());
                    if(runtimeObject != null) {
                        item = addObject(runtimeObject);
                    } else {
                        item = addDefaultItem(modelItem);
                        ((ApplicationProjectItemImpl)item).setDeleted(true);
                    }
                    break;
                case RESOURCE:
                    EntaxyResource entaxyResource =
                            ServiceHelper.getInstance().getEntaxyResourceService().getResource(modelItem.getId());
                    if(entaxyResource != null && entaxyResource.exists()) {
                        item = addResource(entaxyResource);
                    } else {
                        item = addDefaultItem(modelItem);
                        ((ApplicationProjectItemImpl)item).setDeleted(true);
                    }
                    break;

                default:
                    continue;
            }
            ApplicationProjectItemImpl itemImpl = items.get(item.getId());
            itemImpl.setScope(modelItem.getItemScope());
        }

        for (ContentModelItem modelItem : contentModel.getRequiredItems()) {
            ApplicationProjectItem item;
            switch (modelItem.getItemType()) {
                case BUNDLE:
                    continue;
                case CONFIG:
                case OBJECT:
                    EntaxyRuntimeObject runtimeObject = ServiceHelper.getInstance().getEntaxyRuntimeObjectService()
                            .getRuntimeObject(modelItem.getId());
                    if(runtimeObject != null) {
                        item = addObject(runtimeObject);
                    } else {
                        item = addDefaultItem(modelItem);
                        ((ApplicationProjectItemImpl)item).setDeleted(true);
                    }
                    break;
                case RESOURCE:
                    EntaxyResource entaxyResource =
                            ServiceHelper.getInstance().getEntaxyResourceService().getResource(modelItem.getId());
                    if(entaxyResource != null && entaxyResource.exists()) {
                        item = addResource(entaxyResource);
                    } else {
                        item = addDefaultItem(modelItem);
                        ((ApplicationProjectItemImpl)item).setDeleted(true);
                    }
                    break;

                default:
                    continue;
            }
            ApplicationProjectItemImpl itemImpl = items.get(item.getId());
            itemImpl.setScope(modelItem.getItemScope());
            itemImpl.setRequired(modelItem.isRequired());
            itemImpl.setIgnored(modelItem.isIgnored());
        }


    }

    public Map<String, ApplicationProjectItemImpl> getItemsMap() {
        return items;
    }

    @Override
    public List<ApplicationProjectItem> getItems() {
        return new ArrayList<>(items.values());
    }

    @Override
    public <T extends ApplicationProjectItem> List<T> getItems(Class<T> itemClass) {
        return getItems().stream().filter(item -> itemClass.isInstance(item)).map(item -> itemClass.cast(item))
                .collect(Collectors.toList());
    }

    @Override
    public void addItem(ApplicationProjectItem item) throws ItemException {
        ApplicationProjectItemImpl itemImpl = getItemImpl(item.getId());
        if (!item.isRequired())
            // already added
            return;
        if (item.isGhost())
            throw new ItemIsGhostException(null, null, item.getId());
        if (item.isPlatform())
            throw new ItemIsPlatformException(null, null, item.getId());
        itemImpl.setIgnored(false);
        itemImpl.setRequired(false);
        if (item.getType().equals(EntaxyApplication.ITEM_TYPE.OBJECT)) {
            EntaxyRuntimeObject runtimeObject = ServiceHelper.getInstance().getEntaxyRuntimeObjectService()
                    .getRuntimeObject(((EntaxyObjectItem) item).getObject().getObjectFullId());
            Map<String, ApplicationProjectItemImpl> linkedItems = new HashMap<>();
            ItemFactory.INSTANCE.processDependencies(itemImpl, runtimeObject, items, linkedItems);
            processColocatedObjects(runtimeObject, linkedItems);
            items.putAll(linkedItems);
        }
    }

    @Override
    public void ignoreItem(ApplicationProjectItem item) throws ItemException {
        ApplicationProjectItemImpl itemImpl = getItemImpl(item.getId());
        itemImpl.setIgnored(true);
        itemImpl.setRequired(true);
        for (ApplicationProjectItem req : itemImpl.getRequirements()) {
            if (!req.isRequired())
                // that means it's added
                continue;
            if (req.getRequiredBy().size() > 1)
                // required item is required by someone else
                continue;
            if (req.getRequiredBy().isEmpty()) {
                // something strange
                ignoreItem(req);
                continue;
            }
            ApplicationProjectItem backRef = req.getRequiredBy().get(0);
            if (backRef == null) {
                // something strange
                ignoreItem(req);
                continue;
            }
            if (backRef.getId().equals(item.getId())) {
                ignoreItem(req);
            }
        }
    }

    @Override
    public void removeItem(ApplicationProjectItem item) throws ItemException {
        ApplicationProjectItemImpl itemImpl = getItemImpl(item.getId());
        if (itemImpl.getRequiredBy().isEmpty()) {
            for (ApplicationProjectItem required : itemImpl.getRequirements()) {
                // unlink required items
                try {
                    ApplicationProjectItemImpl requiredImpl = getItemImpl(required.getId());
                    itemImpl.removeRequired(requiredImpl);
                } catch (Exception ignore) {
                    // NOOP
                }
            }
            synchronized (itemsLock) {
                items.remove(itemImpl.getId());
            }
        } else if (!item.isPlatform()) {
            // just mark as s requirement
            itemImpl.setRequired(true);
            itemImpl.setIgnored(false);
        }
    }

    @Override
    public ApplicationProjectItem addObject(EntaxyRuntimeObject runtimeObject) {
        synchronized (itemsLock) {
            Map<String, ApplicationProjectItemImpl> linked = new HashMap<>();
            ApplicationProjectItemImpl result = ItemFactory.INSTANCE.createItem(runtimeObject, items, linked);

            result.setScope(EntaxyApplication.ITEM_SCOPE.RUNTIME);

            items.put(result.getId(), result);
            items.putAll(linked);

            processColocatedObjects(runtimeObject, linked);
            
            if (result.isPlatform()) {
                result.setRequired(true);
                result.setIgnored(true);
            } else {
                result.setRequired(false);
            }

            return result;
        }
    }

    protected void processColocatedObjects(EntaxyRuntimeObject runtimeObject,
            Map<String, ApplicationProjectItemImpl> linked) {
        List<EntaxyRuntimeObject> colocatedObjects = runtimeObject.getColocatedObjects();
        for (EntaxyRuntimeObject obj : colocatedObjects) {
            ApplicationProjectItemImpl item;
            if (items.containsKey(obj.getObjectFullId())) {
                item = items.get(obj.getObjectFullId());
            } else {
                linked = new HashMap<>();
                item = ItemFactory.INSTANCE.createItem(obj, items, linked);
                if (!items.containsKey(item.getId()))
                    items.put(item.getId(), item);
                items.putAll(linked);
            }
            
            if (item.isPlatform()) {
                item.setRequired(true);
                item.setIgnored(true);
            } else {
                item.setRequired(false);
                item.setIgnored(false);
            } 
            
            item.setScope(EntaxyApplication.ITEM_SCOPE.RUNTIME);
        }
    }

    @Override
    public ApplicationProjectItem addResource(EntaxyRuntimeObjectResource runtimeObjectResource) {
        synchronized (itemsLock) {
            ApplicationProjectItemImpl result = ItemFactory.INSTANCE.createItem(runtimeObjectResource, items);
            items.put(result.getId(), result);
            return result;
        }
    }

    @Override
    public ApplicationProjectItem addResource(EntaxyResource entaxyResource) {
        synchronized (itemsLock) {
            ApplicationProjectItemImpl result = ItemFactory.INSTANCE.createItem(entaxyResource, items);
            result.setScope(EntaxyApplication.ITEM_SCOPE.RUNTIME);
            items.put(result.getId(), result);
            return result;
        }
    }

    @Override
    public Map<String, Object> getProperties() {
        return properties;
    }
    
    public ApplicationProjectItem addDefaultItem(ContentModelItem modelItem) {
        synchronized (itemsLock) {
            ApplicationProjectItemImpl result = DefaultApplicationProjectItem.create(modelItem);
            items.put(result.getId(), result);
            return result;
        }
    }
}
