/*-
 * ~~~~~~licensing~~~~~~
 * atlasmap-entaxy-services
 * ==========
 * 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~~~~~~
 */
/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * 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.
 */
package io.atlasmap.core;

import java.util.List;

import io.atlasmap.api.AtlasException;
import io.atlasmap.spi.AtlasCollectionHelper;
import io.atlasmap.spi.AtlasFieldActionService;
import io.atlasmap.v2.Action;
import io.atlasmap.v2.ActionDetail;
import io.atlasmap.v2.CollectionType;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.FieldGroup;
import io.atlasmap.v2.Multiplicity;

public class DefaultAtlasCollectionHelper implements AtlasCollectionHelper {

    private AtlasFieldActionService fieldActionService;

    public DefaultAtlasCollectionHelper() {
        this.fieldActionService = DefaultAtlasFieldActionService.getInstance();
    }

    public DefaultAtlasCollectionHelper(AtlasFieldActionService fieldActionService) {
        this.fieldActionService = fieldActionService;
    }

    protected AtlasPath createTargetAtlasPath(String path) {
        return new AtlasPath(path);
    }

    public int determineTargetCollectionCount(Field targetField) {
        AtlasPath targetPath = createTargetAtlasPath(targetField.getPath());
        int targetCollectionCount = targetPath.getCollectionSegmentCount();
        if (targetField.getIndex() != null) {
            targetCollectionCount++; //adjust based on index
        }
        return targetCollectionCount;
    }

    public int determineSourceCollectionCount(Field sourceParentField, Field sourceField) {
        AtlasPath sourcePath = new AtlasPath(sourceField.getPath());
        int sourceCollectionCount = sourcePath.getCollectionSegmentCount();
        sourceCollectionCount += getCollectionCountAdjustmentForActions(sourceParentField);
        sourceCollectionCount += getCollectionCountAdjustmentForActions(sourceField);
        if (sourceField.getIndex() != null) {
            sourceCollectionCount--; //adjust based on index
        }
        return sourceCollectionCount;
    }

    private int getCollectionCountAdjustmentForActions(Field sourceField) {
        int sourceCollectionCount = 0;
        if (sourceField != null && sourceField.getActions() != null) {
            for (Action action : sourceField.getActions()) {
                ActionDetail actionDetail = null;
                try {
                    actionDetail = fieldActionService.findActionDetail(action, sourceField.getFieldType());
                } catch (AtlasException e) {
                    throw new RuntimeException(e);
                }

                if (actionDetail != null) {
                    if (Multiplicity.ONE_TO_MANY.equals(actionDetail.getMultiplicity())) {
                        sourceCollectionCount++;
                    } else if (Multiplicity.MANY_TO_ONE.equals(actionDetail.getMultiplicity())) {
                        sourceCollectionCount--;
                    }
                }
            }
        }
        return sourceCollectionCount;
    }

    public void copyCollectionIndexes(Field sourceParentField, Field sourceField, Field targetField, Field previousTargetField) {
        AtlasPath sourcePath = new AtlasPath(sourceField.getPath());
        AtlasPath targetPath = createTargetAtlasPath(targetField.getPath());
        int targetCollectionCount = determineTargetCollectionCount(targetField);
        int sourceCollectionCount = determineSourceCollectionCount(sourceParentField, sourceField);
        int targetIndex = 0;
        int collectionCount = 0;
        List<AtlasPath.SegmentContext> targetSegments = targetPath.getSegments(true);

        if (targetCollectionCount > sourceCollectionCount) {
            //Put 0 index in excessive target collections, if targetCollectionCount > sourceCollectionCount
            while (collectionCount < targetCollectionCount - sourceCollectionCount) {
                AtlasPath.SegmentContext targetSegment = targetSegments.get(targetIndex);
                if (targetSegment.getCollectionType() != CollectionType.NONE) {
                    targetPath.setCollectionIndex(targetIndex, 0);
                    collectionCount++;
                }
                targetIndex++;
            }
        }

        AtlasPath previousTargetPath = previousTargetField != null ? createTargetAtlasPath(previousTargetField.getPath()) : null;
        List<AtlasPath.SegmentContext> sourceCollectionSegments = sourcePath.getCollectionSegments(true);
        AtlasPath.SegmentContext lastSourceSegment = sourcePath.getLastSegment();
        AtlasPath.SegmentContext lastTargetCollectionSegment = targetPath.getLastCollectionSegment();
        for (AtlasPath.SegmentContext sourceSegment : sourcePath.getSegments(true)) {
            if (sourceSegment.getCollectionType() == CollectionType.NONE && sourceSegment != lastSourceSegment) {
                //always process last segment even if not a collection (in case e.g. split has been applied)
                continue;
            }

            while (targetSegments.size() > targetIndex) {
                AtlasPath.SegmentContext targetSegment = targetSegments.get(targetIndex);
                if (targetSegment.getCollectionType() != CollectionType.NONE) {
                    collectionCount++;
                    if (sourceSegment.getCollectionIndex() != null) {
                        if (sourceCollectionCount > targetCollectionCount && targetCollectionCount == collectionCount) {
                            //if needs to flatten excessive rightmost source collections
                            int nextCollectionIndex = determineNextCollectionIndex(previousTargetPath, sourceCollectionSegments);
                            targetPath.setCollectionIndex(targetIndex, nextCollectionIndex);
                        } else {
                            targetPath.setCollectionIndex(targetIndex, sourceSegment.getCollectionIndex());
                        }

                        targetIndex++;
                        break;
                    } else if (targetSegment == lastTargetCollectionSegment && sourceParentField instanceof FieldGroup) {
                        //if the last collection target segment, but no collection index specified (e.g. after split)
                        int nextCollectionIndex = determineNextCollectionIndex(previousTargetPath, sourceCollectionSegments);
                        targetPath.setCollectionIndex(targetIndex, nextCollectionIndex);

                        targetIndex++;
                        break;
                    }
                }

                targetIndex++;
            }
        }

        targetField.setPath(targetPath.toString());
    }

    private int determineNextCollectionIndex(AtlasPath previousTargetPath, List<AtlasPath.SegmentContext> sourceCollectionSegments) {
        int nextCollectionIndex = 0;
        if (previousTargetPath != null) {
            List<AtlasPath.SegmentContext> previousTargetCollectionSegments = previousTargetPath.getCollectionSegments(true);
            boolean parentIndexesChanged = false;
            for (int i = previousTargetCollectionSegments.size() - 2; i >= 0; i--) {
                if (!previousTargetCollectionSegments.get(i).getCollectionIndex()
                    .equals(sourceCollectionSegments.get(i).getCollectionIndex())) {
                    parentIndexesChanged = true;
                    break;
                }
            }

            if (!parentIndexesChanged) {
                //determine previous collection index
                nextCollectionIndex = previousTargetCollectionSegments
                    .get(previousTargetCollectionSegments.size() - 1).getCollectionIndex();
                nextCollectionIndex++;
            }
        }
        return nextCollectionIndex;
    }
}
