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

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import com.google.common.collect.ImmutableSet;

import accord.local.Node;
import accord.utils.SortedArrays.SortedArrayList;

import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.membership.Location;
import org.apache.cassandra.tcm.membership.NodeAddresses;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.tcm.membership.NodeVersion;
import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.tcm.ownership.DataPlacements;

public class AccordTopologyUtils
{
    public static final Node.Id ID1 = new Node.Id(1);
    public static final Node.Id ID2 = new Node.Id(2);
    public static final Node.Id ID3 = new Node.Id(3);
    public static final SortedArrayList<Node.Id> NODE_LIST = new SortedArrayList<>(new Node.Id[] { ID1, ID2, ID3 });
    public static final Set<Node.Id> NODE_SET = ImmutableSet.copyOf(NODE_LIST);

    private static final IPartitioner partitioner = Murmur3Partitioner.instance;
    private static final Location LOCATION = new Location("DC1", "RACK1");

    public static InetAddressAndPort ep(int i)
    {
        try
        {
            return InetAddressAndPort.getByAddressOverrideDefaults(InetAddress.getByAddress(new byte[]{ 127, 0, 0, (byte)i}), 7012);
        }
        catch (UnknownHostException e)
        {
            throw new RuntimeException(e);
        }
    }

    private static NodeId nodeId(int id)
    {
        return new NodeId(id);
    }

    static void addNode(ClusterMetadata.Transformer transformer, int node, Token token)
    {
        NodeId nodeId = nodeId(node);
        InetAddressAndPort ep = ep(node);
        NodeAddresses addresses = new NodeAddresses(nodeId.toUUID(), ep, ep, ep);
        transformer.unsafeRegisterForTesting(nodeId, addresses, LOCATION, NodeVersion.CURRENT);
        transformer.withNodeState(nodeId, NodeState.JOINED);
        transformer.proposeToken(nodeId, Collections.singleton(token));
        transformer.addToRackAndDC(nodeId);
    }

    public static ClusterMetadata configureCluster(List<Range<Token>> ranges, Keyspaces keyspaces)
    {
        assert ranges.size() == 3;

        IPartitioner partitioner = Murmur3Partitioner.instance;
        ClusterMetadata empty = new ClusterMetadata(partitioner);
        ClusterMetadata.Transformer transformer = empty.transformer();
        transformer.with(new DistributedSchema(keyspaces));
        addNode(transformer, 1, ranges.get(0).right);
        addNode(transformer, 2, ranges.get(1).right);
        addNode(transformer, 3, ranges.get(2).right);
        ClusterMetadata metadata = transformer.build().metadata;

        for (KeyspaceMetadata keyspace : keyspaces)
        {
            ReplicationParams replication = keyspace.params.replication;
            AbstractReplicationStrategy strategy = AbstractReplicationStrategy.createReplicationStrategy(keyspace.name, replication);
            DataPlacements.Builder placements = metadata.placements().unbuild();
            DataPlacement placement = strategy.calculateDataPlacement(Epoch.EMPTY, metadata.tokenMap.toRanges(), metadata);
            placements.with(replication, placement);
            metadata = transformer.with(placements.build()).build().metadata;
        }

        return metadata;
    }

    static Token token(long t)
    {
        return new Murmur3Partitioner.LongToken(t);
    }

    static Range<Token> range(Token left, Token right)
    {
        return new Range<>(left, right);
    }

    public static Range<Token> range(long left, long right)
    {
        return range(token(left), token(right));
    }

}
