/*
 * 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 accord.primitives;

import java.util.Objects;

import accord.api.RoutingKey;
import accord.utils.Invariants;
import accord.utils.SortedList.MergeCursor;

import static accord.primitives.Routables.Slice.Minimal;
import static accord.utils.Invariants.illegalArgument;

public class PartialDeps extends Deps
{
    public static final PartialDeps NONE = new PartialDeps(Ranges.EMPTY, KeyDeps.NONE, RangeDeps.NONE);

    public static Builder builder(Participants<?> covering, boolean buildRangeByTxnId)
    {
        return new Builder(covering, buildRangeByTxnId);
    }
    public static class Builder extends AbstractBuilder<PartialDeps>
    {
        final Participants<?> covering;
        public Builder(Participants<?> covering, boolean buildRangeByTxnId)
        {
            super(buildRangeByTxnId);
            this.covering = covering;
        }

        @Override
        public PartialDeps build()
        {
            return new PartialDeps(covering, keyBuilder.build(), rangeBuilder.build());
        }
    }

    public final Participants<?> covering; // set only if this is a range transaction, containing the minimal ranges of the original transaction that we cover
    public PartialDeps(Participants<?> covering, KeyDeps keyDeps, RangeDeps rangeDeps)
    {
        super(keyDeps, rangeDeps);
        this.covering = covering;
    }

    public boolean covers(Unseekables<?> participants)
    {
        return covering.containsAll(participants);
    }

    public boolean covers(RoutingKey key)
    {
        return covering.contains(key);
    }

    @Override
    public MergeCursor<TxnId, DepList> txnIds(RoutingKey key)
    {
        Invariants.requireArgument(covers(key), "%s is not covered by %s", key, this);
        return super.txnIds(key);
    }

    public Deps with(Deps that)
    {
        if (that instanceof PartialDeps)
            return with((PartialDeps) that);
        return super.with(that);
    }

    public PartialDeps with(PartialDeps that)
    {
        return new PartialDeps(
            this.covering.with((Participants)that.covering),
            this.keyDeps.with(that.keyDeps),
            this.rangeDeps.with(that.rangeDeps)
        );
    }

    @Override
    public boolean equals(Deps that)
    {
        return that instanceof PartialDeps
               && Objects.equals(covering, ((PartialDeps) that).covering)
               && super.equals(that);
    }

    @Override
    public PartialDeps intersecting(Participants<?> participants)
    {
        if (!covers(participants))
            throw illegalArgument("This PartialDeps does not cover the requested participants");
        return new PartialDeps(this.covering.intersecting(participants, Minimal), keyDeps.intersecting(participants), rangeDeps.intersecting(participants));
    }

    public PartialDeps intersectStable(Deps that, TxnId until)
    {
        return new PartialDeps(covering, keyDeps.withTxnIds(intersectStable(keyDeps.txnIds, that.keyDeps.txnIds, until)),
                               rangeDeps.withTxnIds(intersectStable(rangeDeps.txnIds, that.rangeDeps.txnIds, until)));
    }

    public Deps asFullUnsafe()
    {
        return new Deps(keyDeps, rangeDeps);
    }

    public Deps reconstitute(FullRoute<?> route)
    {
        if (!covers(route.participants()))
            throw illegalArgument(covering + " does not cover " + route);
        return new Deps(keyDeps, rangeDeps);
    }

    // covering might cover a wider set of ranges, some of which may have no involved keys
    public PartialDeps reconstitutePartial(Participants<?> covering)
    {
        if (!covers(covering))
            throw illegalArgument(this.covering + " does not cover " + covering);

        if (covers(covering)) return this;
        else throw illegalArgument(this.covering + " does not cover " + covering);
    }
}
