/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nifi.controller.repository;

import org.apache.nifi.controller.queue.FlowFileQueue;
import org.apache.nifi.controller.repository.claim.ContentClaim;
import org.apache.nifi.controller.repository.claim.ResourceClaim;
import org.apache.nifi.controller.repository.claim.ResourceClaimManager;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>
 * An in-memory implementation of the {@link FlowFileRepository}. Upon restart, all FlowFiles will be discarded, including those that have been swapped out by a {@link FlowFileSwapManager}.
 * </p>
 */
public class VolatileFlowFileRepository implements FlowFileRepository {

    private final AtomicLong idGenerator = new AtomicLong(0L);
    private ResourceClaimManager claimManager; // effectively final

    @Override
    public void initialize(final ResourceClaimManager claimManager) {
        this.claimManager = claimManager;
    }

    @Override
    public boolean isVolatile() {
        return true;
    }

    @Override
    public long getStorageCapacity() throws IOException {
        return 1L;
    }

    @Override
    public long getUsableStorageSpace() throws IOException {
        return 0L;
    }

    @Override
    public String getFileStoreName() {
        return null;
    }

    @Override
    public void close() throws IOException {
    }

    private void markDestructable(final ContentClaim contentClaim) {
        if (contentClaim == null) {
            return;
        }

        final ResourceClaim resourceClaim = contentClaim.getResourceClaim();
        if (resourceClaim == null) {
            return;
        }

        claimManager.markDestructable(resourceClaim);
    }

    private int getClaimantCount(final ContentClaim claim) {
        if (claim == null) {
            return 0;
        }

        final ResourceClaim resourceClaim = claim.getResourceClaim();
        if (resourceClaim == null) {
            return 0;
        }

        return claimManager.getClaimantCount(resourceClaim);
    }

    @Override
    public void updateRepository(final Collection<RepositoryRecord> records) throws IOException {
        for (final RepositoryRecord record : records) {
            updateClaimCounts(record);

            if (record.getType() == RepositoryRecordType.DELETE) {
                // For any DELETE record that we have, if current claim's claimant count <= 0, mark it as destructable
                if (record.getCurrentClaim() != null && getClaimantCount(record.getCurrentClaim()) <= 0) {
                    markDestructable(record.getCurrentClaim());
                }

                // If the original claim is different than the current claim and the original claim has a claimant count <= 0, mark it as destructable.
                if (record.getOriginalClaim() != null && !record.getOriginalClaim().equals(record.getCurrentClaim()) && getClaimantCount(record.getOriginalClaim()) <= 0) {
                    markDestructable(record.getOriginalClaim());
                }
            } else if (record.getType() == RepositoryRecordType.UPDATE) {
                // if we have an update, and the original is no longer needed, mark original as destructable
                if (record.getOriginalClaim() != null && record.getCurrentClaim() != record.getOriginalClaim() && getClaimantCount(record.getOriginalClaim()) <= 0) {
                    markDestructable(record.getOriginalClaim());
                }
            }
        }
    }

    private void updateClaimCounts(final RepositoryRecord record) {
        final ContentClaim currentClaim = record.getCurrentClaim();
        final ContentClaim originalClaim = record.getOriginalClaim();
        final boolean claimChanged = !Objects.equals(currentClaim, originalClaim);

        if (record.getType() == RepositoryRecordType.DELETE || record.getType() == RepositoryRecordType.CONTENTMISSING) {
            decrementClaimCount(currentClaim);
        }

        if (claimChanged) {
            // records which have been updated - remove original if exists
            decrementClaimCount(originalClaim);
        }
    }

    private void decrementClaimCount(final ContentClaim claim) {
        if (claim == null) {
            return;
        }

        claimManager.decrementClaimantCount(claim.getResourceClaim());
    }

    @Override
    public long loadFlowFiles(final QueueProvider queueProvider) throws IOException {
        return 0;
    }

    @Override
    public Set<String> findQueuesWithFlowFiles(FlowFileSwapManager swapManager) throws IOException {
        return Collections.emptySet();
    }

    @Override
    public void updateMaxFlowFileIdentifier(final long maxId) {
        while (true) {
            final long currentId = idGenerator.get();
            if (currentId >= maxId) {
                return;
            }

            final boolean updated = idGenerator.compareAndSet(currentId, maxId);
            if (updated) {
                return;
            }
        }
    }

    @Override
    public long getNextFlowFileSequence() {
        return idGenerator.getAndIncrement();
    }

    @Override
    public long getMaxFlowFileIdentifier() throws IOException {
        return idGenerator.get() - 1;
    }

    @Override
    public void swapFlowFilesIn(String swapLocation, List<FlowFileRecord> flowFileRecords, FlowFileQueue queue) throws IOException {
    }

    @Override
    public void swapFlowFilesOut(List<FlowFileRecord> swappedOut, FlowFileQueue queue, String swapLocation) throws IOException {
    }

    @Override
    public boolean isValidSwapLocationSuffix(final String swapLocationSuffix) {
        return false;
    }
}
