package com.accenture.rig;

import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Map.Entry;

import com.ericsson.otp.erlang.OtpAuthException;
import com.ericsson.otp.erlang.OtpConnection;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangBinary;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpPeer;
import com.ericsson.otp.erlang.OtpSelf;

public class ErlangInterface {
  private final OtpConnection conn;

  public ErlangInterface(final String remote, final String cookie)
      throws UnknownHostException, OtpAuthException, IOException {
    final String client_name = System.getProperty("client_name");
    final OtpSelf client = new OtpSelf(client_name, cookie);
    final OtpPeer rig = new OtpPeer(remote);
    conn = client.connect(rig);
  }

  public void forward(final Map<String, Object> map)
      throws IOException, OtpErlangDecodeException, OtpErlangExit, OtpAuthException {
    final OtpErlangObject[] args = new OtpErlangObject[] { asErlangDict(map) };
    final String executor = System.getProperty("executor");
    conn.sendRPC(executor, "java_client_callback", args);
    final OtpErlangObject response = conn.receiveMsg().getMsg();
    if (!isExpectedResponse(response)) {
      throw new IOException(String.format("Invalid response when forwarding message: %s", response));
    }
  }

  private OtpErlangList asErlangDict(final Map<String, Object> map) {
    final int nItems = map.size();
    final OtpErlangObject[] items = new OtpErlangObject[nItems];
    int i = 0;
    for (final Entry<String, Object> entry : map.entrySet()) {
      final Object javaObject = entry.getValue();

      OtpErlangObject erlangObject;
      if (javaObject instanceof String)
        erlangObject = new OtpErlangBinary(((String) javaObject).getBytes(StandardCharsets.UTF_8));
      else if (javaObject instanceof ByteBuffer)
        erlangObject = new OtpErlangBinary(((ByteBuffer) javaObject).array());
      else if (javaObject instanceof java.util.Date)
        erlangObject = new OtpErlangBinary(
            formatTimestamp((java.util.Date) javaObject).getBytes(StandardCharsets.UTF_8));
      else
        throw new RuntimeException("cannot convert " + javaObject.getClass().getCanonicalName());

      items[i] = new OtpErlangTuple(new OtpErlangObject[] { new OtpErlangAtom(entry.getKey()), erlangObject });

      ++i;
    }
    return new OtpErlangList(items);
  }

  private static String formatTimestamp(final java.util.Date plainOldDate) {
    return DateTimeFormatter.ISO_INSTANT.format(plainOldDate.toInstant().atZone(ZoneId.of("UTC")));
  }

  /**
   * This only checks whether the RPC went through.
   */
  private boolean isExpectedResponse(OtpErlangObject response) {
    if (response == null || !(response instanceof OtpErlangTuple))
      return false;

    final OtpErlangTuple tuple = (OtpErlangTuple) response;

    if (tuple.arity() != 2)
      return false;

    final OtpErlangObject first = tuple.elementAt(0);
    if (!(first instanceof OtpErlangAtom) || !((OtpErlangAtom) first).atomValue().equals("rex"))
      return false;

    final OtpErlangObject second = tuple.elementAt(1);
    if (!(second instanceof OtpErlangAtom) || !((OtpErlangAtom) second).atomValue().equals("ok"))
      return false;

    return true;
  }
}
