/*
 * Copyright (c) 2016 The original author or authors
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *      The Eclipse Public License is available at
 *      http://www.eclipse.org/legal/epl-v10.html
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package io.vertx.ext.consul.dc;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pszymczyk.consul.ConsulProcess;
import com.pszymczyk.consul.ConsulStarterBuilder;
import com.pszymczyk.consul.LogLevel;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;

/**
 * @author <a href="mailto:ruslan.sennov@gmail.com">Ruslan Sennov</a>
 */
public class ConsulAgent {

  private static final ObjectMapper mapper = new ObjectMapper();

  private final ConsulProcess process;
  private final String masterToken;
  private final String host;
  private final String name;
  private final int httpsPort;

  ConsulAgent(ConsulDatacenter dc, ConsulAgentOptions options) {
    JsonObject cfg = new JsonObject()
      .put("server", true)
      .put("advertise_addr", options.getAddress())
      .put("client_addr", options.getAddress())
      .put("leave_on_terminate", true)
      .put("datacenter", dc.getName())
      .put("node_name", options.getNodeName())
      .put("node_id", options.getNodeId())
      .put("acl_default_policy", "deny")
      .put("acl_master_token", dc.getMasterToken())
      .put("acl_datacenter", dc.getName());
    if (options.getConsulVersion().compareTo("0.8.0") >= 0) {
      cfg.put("acl_enforce_version_8", false);
    }
    if (options.getConsulVersion().compareTo("0.9.0") >= 0) {
      cfg.put("enable_script_checks", true);
    }
    int sslCnt = 0;
    if (options.getKeyFile() != null) {
      cfg.put("key_file", options.getKeyFile());
      sslCnt++;
    }
    if (options.getCertFile() != null) {
      cfg.put("cert_file", options.getCertFile());
      sslCnt++;
    }
    if (options.getCaFile() != null) {
      cfg.put("ca_file", options.getCaFile());
      sslCnt++;
    }
    if (sslCnt == 3) {
      httpsPort = getFreePort();
      cfg.put("ports", new JsonObject().put("https", httpsPort))
        .put("addresses", new JsonObject().put("https", "0.0.0.0"));
    } else {
      httpsPort = -1;
    }
    List<ConsulAgent> existingAgents = dc.getAgents();
    if (!existingAgents.isEmpty()) {
      cfg.put("start_join", existingAgents.stream()
        .map(agent -> "127.0.0.1:" + agent.getSerfLanPort())
        .collect(Collector.of(JsonArray::new, JsonArray::add, JsonArray::addAll)));
    }
    host = options.getAddress();
    name = options.getNodeName();
    masterToken = dc.getMasterToken();
    process = ConsulStarterBuilder.consulStarter()
      .withLogLevel(LogLevel.ERR)
      .withConsulVersion(options.getConsulVersion())
      .withCustomConfig(cfg.encode())
      .build()
      .start();
  }

  void stop() {
    process.close();
  }

  public String createAclToken(String rules) throws IOException {
    HttpClient client = new DefaultHttpClient();
    HttpPut put = new HttpPut("http://" + getHost() + ":" + getHttpPort() + "/v1/acl/create");
    put.addHeader("X-Consul-Token", masterToken);
    Map<String, String> tokenRequest = new HashMap<>();
    tokenRequest.put("Type", "client");
    tokenRequest.put("Rules", rules);
    put.setEntity(new StringEntity(mapper.writeValueAsString(tokenRequest)));
    HttpResponse response = client.execute(put);
    if (response.getStatusLine().getStatusCode() != 200) {
      throw new RuntimeException("Bad response");
    }
    Map<String, String> tokenResponse = mapper.readValue(EntityUtils.toString(response.getEntity()), new TypeReference<Map<String, String>>() {
    });
    return tokenResponse.get("ID");
  }

  public int getSerfLanPort() {
    return process.getSerfLanPort();
  }

  public int getHttpPort() {
    return process.getHttpPort();
  }

  public int getHttpsPort() {
    return httpsPort;
  }

  public String getHost() {
    return host;
  }

  public String getName() {
    return name;
  }

  private static int getFreePort() {
    int port = -1;
    try {
      ServerSocket socket = new ServerSocket(0);
      port = socket.getLocalPort();
      socket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return port;
  }
}

@@ -2194,24 +2194,159 @@
 getName());%0A
+    if (options.getConsulVersion().compareTo(%220.8.0%22) %3C 0) %7B%0A      cfg.put(%22ports%22, new JsonObject().put(%22rpc%22, getFreePort()));%0A    %7D%0A
     if (opti
@@ -2972,30 +2972,59 @@
 rt();%0A      
-cfg.pu
+JsonObject ports = cfg.getJsonObjec
 t(%22ports%22, n
@@ -3030,32 +3030,46 @@
 new JsonObject()
+);%0A      ports
 .put(%22https%22, ht
@@ -3076,26 +3076,47 @@
 tpsPort)
-)
+;
 %0A      
-  
+cfg.put(%22ports%22, ports)
 .put(%22ad

set up rpc port for ancient consul