/**
 * 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.ratis.logservice.util;

import org.apache.ratis.BaseTest;
import org.apache.ratis.logservice.api.LogName;
import org.apache.ratis.logservice.api.LogStream;
import org.apache.ratis.logservice.api.LogServiceClient;
import org.apache.ratis.logservice.server.LogServer;
import org.apache.ratis.logservice.server.MetadataServer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * MiniCluster for the LogService. Allows to create and manage master nodes as well as to create and manage worker nodes
 */

public class LogServiceCluster implements AutoCloseable {
    private List<MetadataServer> masters;
    private List<LogServer> workers = new ArrayList<>();
    private String baseTestDir = BaseTest.getRootTestDir().getAbsolutePath();

    /**
     * Create a number of worker nodes with random ports and start them
     * @param numWorkers number of Workers to create
     */
    public void createWorkers(int numWorkers) {
        String meta = getMetaIdentity();
        List<LogServer> newWorkers = IntStream.range(0, numWorkers).parallel().mapToObj(i ->
                LogServer.newBuilder()
                        .setHostName("localhost")
                        .setPort(10000 + i)
                        .setMetaQuorum(meta)
                        .setWorkingDir(baseTestDir + "/workers/" + i)
                        .build()).collect(Collectors.toList());
        newWorkers.parallelStream().forEach( worker -> {
            try {
                worker.start();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        workers.addAll(newWorkers);
    }

    /**
     *
     * @return the string that represent the meta quorum ID that can can be used to manually create a worker nodes
     */
    public String getMetaIdentity() {
        // Nb. Can only be called after the masters have been instantiated.
        return masters.stream().map(object -> object.getAddress()).collect(Collectors.joining(","));
    }

    /**
     * Create and start a LogService metadata quorum with N number of masters.
     * They are created with ports starting from 9000
     * @param numServers
     */

    public LogServiceCluster(int numServers) {
        // Have to construct the meta quorum by hand -- `getMetaIdentity()` requires
        // uses the masters to build the quorum (chicken and egg problem).
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < numServers; i++) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append("localhost:").append(9000 + i);
        }
        String metaQuorum = sb.toString();
        this.masters = IntStream.range(0, numServers).parallel().mapToObj(i ->
                MetadataServer.newBuilder()
                        .setHostName("localhost")
                        .setPort(9000 + i)
                        .setWorkingDir(baseTestDir + "/masters/" + i)
                        .setMetaQuorum(metaQuorum)
                        .build())
                .collect(Collectors.toList());
        masters.parallelStream().forEach(master -> {
            try {
                master.cleanUp();
                master.start();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }


    /**
     * Create a new LOG with the given name
     * @param logName
     * @throws IOException
     */
    public LogStream createLog(LogName logName) throws Exception {
        try (LogServiceClient client = new LogServiceClient(getMetaIdentity())) {
            return client.createLog(logName);
        }
    }

    /**
     * @return the current set of the workers
     */
    public List<LogServer> getWorkers() {
        return workers;
    }

    /**
     *
     * @return the current set of the masters
     */
    public List<MetadataServer> getMasters() {
        return masters;
    }



    /**
     * Shutdown the cluster.
     */
    @Override
    public void close() {
        masters.stream().parallel().forEach(master -> {
            try {
                master.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        workers.stream().parallel().forEach ( worker -> {
            try {
                worker.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    public LogStream getLog(LogName logName) throws Exception {
        try (LogServiceClient client = new LogServiceClient(getMetaIdentity())) {
            return client.getLog(logName);
        }
    }

    /**
     * Remove all temporary directories created by the mini cluster
     */
    public void cleanUp() {
//        FileUtils.deleteDirectory();
    }
}