/*
 * 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.cassandra.tcm;

import java.util.Collections;

import com.google.common.collect.ImmutableMap;

import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.schema.DistributedMetadataLogKeyspace;
import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.service.accord.topology.AccordFastPath;
import org.apache.cassandra.service.accord.topology.AccordStaleReplicas;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.Commit.Replicator;
import org.apache.cassandra.tcm.log.Entry;
import org.apache.cassandra.tcm.log.LocalLog;
import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.PlacementProvider;
import org.apache.cassandra.tcm.ownership.TokenMap;
import org.apache.cassandra.tcm.ownership.UniformRangePlacement;
import org.apache.cassandra.tcm.sequences.InProgressSequences;
import org.apache.cassandra.tcm.sequences.LockedRanges;

public class StubClusterMetadataService extends ClusterMetadataService
{

    public static StubClusterMetadataService forClientTools()
    {
        KeyspaceMetadata ks = DistributedMetadataLogKeyspace.initialMetadata(Collections.singleton("DC1"));
        DistributedSchema schema = new DistributedSchema(Keyspaces.of(ks));
        return new StubClusterMetadataService(new ClusterMetadata(DatabaseDescriptor.getPartitioner(),
                                                                  Directory.EMPTY,
                                                                  schema));
    }

    public static StubClusterMetadataService forClientTools(DistributedSchema initialSchema)
    {
        ClusterMetadata metadata = new ClusterMetadata(DatabaseDescriptor.getPartitioner());
        metadata = metadata.transformer().with(initialSchema).build().metadata.forceEpoch(Epoch.EMPTY);
        return new StubClusterMetadataService(metadata);
    }

    public static StubClusterMetadataService forTesting()
    {
        return new StubClusterMetadataService(new ClusterMetadata(DatabaseDescriptor.getPartitioner()));
    }

    public static StubClusterMetadataService forTesting(ClusterMetadata metadata)
    {
        return new StubClusterMetadataService(metadata);
    }

    private ClusterMetadata metadata;

    protected StubClusterMetadataService(ClusterMetadata initial)
    {
        super(new UniformRangePlacement(),
              MetadataSnapshots.NO_OP,
              LocalLog.logSpec()
                      .loadSSTables(false)
                      .sync()
                      .withInitialState(initial)
                      .createLog(),
              new StubProcessor(),
              Replicator.NO_OP,
              false);
        this.metadata = initial;
        this.log().readyUnchecked();
    }

    private StubClusterMetadataService(PlacementProvider placement,
                                       MetadataSnapshots snapshots,
                                       LocalLog log,
                                       Processor processor,
                                       Replicator replicator,
                                       boolean isMember)
    {
       super(placement, snapshots, log, processor, replicator, isMember);
       this.metadata = log.metadata();
       this.log().readyUnchecked();
    }

    @Override
    public <T1> T1 commit(Transformation transform, CommitSuccessHandler<T1> onSuccess, CommitFailureHandler<T1> onFailure)
    {
        Transformation.Result result = execute(transform);
        if (result.isSuccess())
        {
            setMetadata(result.success().metadata);
            return  onSuccess.accept(result.success().metadata);
        }
        return onFailure.accept(result.rejected().code, result.rejected().reason);
    }

    protected Transformation.Result execute(Transformation transform)
    {
        return transform.execute(metadata());
    }

    @Override
    public ClusterMetadata fetchLogFromCMS(Epoch awaitAtLeast)
    {
        return metadata;
    }

    @Override
    public ClusterMetadata metadata()
    {
        return metadata;
    }

    public void setMetadata(ClusterMetadata metadata)
    {
        this.metadata = metadata;
    }

    private static class StubProcessor implements Processor
    {

        private StubProcessor() {}

        @Override
        public Commit.Result commit(Entry.Id entryId, Transformation transform, Epoch lastKnown, Retry retryPolicy)
        {
            throw new UnsupportedOperationException();
        }

        @Override
        public ClusterMetadata fetchLogAndWait(Epoch waitFor, Retry retryPolicy)
        {
            throw new UnsupportedOperationException();
        }
    }

    public static Builder builder()
    {
        return new Builder();
    }

    public static Builder builder(IPartitioner partitioner)
    {
        return new Builder(partitioner);
    }

    public static class Builder
    {
        IPartitioner partitioner;
        ClusterMetadata initial;
        MetadataSnapshots snapshots = MetadataSnapshots.NO_OP;

        public StubClusterMetadataService build()
        {
            if (initial == null)
            {
                initial = new ClusterMetadata(Epoch.EMPTY,
                                              partitioner,
                                              DistributedSchema.empty(),
                                              Directory.EMPTY,
                                              new TokenMap(partitioner),
                                              DataPlacements.EMPTY,
                                              AccordFastPath.EMPTY,
                                              LockedRanges.EMPTY,
                                              InProgressSequences.EMPTY,
                                              ConsensusMigrationState.EMPTY,
                                              ImmutableMap.of(),
                                              AccordStaleReplicas.EMPTY,
                                              CMSMembership.EMPTY);
            }
            return new StubClusterMetadataService(new UniformRangePlacement(),
                                                  snapshots != null ? snapshots : MetadataSnapshots.NO_OP,
                                                  LocalLog.logSpec().withInitialState(initial).createLog(),
                                                  new StubProcessor(),
                                                  Replicator.NO_OP,
                                                  false);
        }

        private Builder()
        {
            this(DatabaseDescriptor.getPartitioner());
        }

        private Builder(IPartitioner partitioner)
        {
            this.partitioner = partitioner;
        }

        public Builder withInitial(ClusterMetadata initial)
        {
            this.initial = initial;
            return this;
        }

        public Builder withSnapshots(MetadataSnapshots snapshots)
        {
            this.snapshots = snapshots;
            return this;
        }
    }
}
