package network.loki.lokinet;

import java.lang.Thread;
import java.nio.ByteBuffer;
import java.io.File;

import android.net.VpnService;
import android.util.Log;
import android.content.Intent;
import android.os.ParcelFileDescriptor;

public class LokinetDaemon extends VpnService
{
  static {
    System.loadLibrary("lokinet-android");
  }

  private static native ByteBuffer Obtain();
  private static native void Free(ByteBuffer buf);
  public native boolean Configure(LokinetConfig config);
  public native int Mainloop();
  public native boolean IsRunning();
  public native boolean Stop();
  public native void InjectVPNFD();
  public native int GetUDPSocket();

  private static native String DetectFreeRange();

  public native String DumpStatus();


  public static final String LOG_TAG = "LokinetDaemon";

  ByteBuffer impl = null;
  ParcelFileDescriptor iface;
  int m_FD = -1;
  int m_UDPSocket = -1;

  @Override
    public void onCreate()
    {
      super.onCreate();
    }

  @Override
    public void onDestroy()
    {
      super.onDestroy();

      if (IsRunning())
      {
        Stop();
      }
      if (impl != null)
      {
        Free(impl);
        impl = null;
      }
    }

  public int onStartCommand(Intent intent, int flags, int startID)
  {
    Log.d(LOG_TAG, "onStartCommand()");

    if (!IsRunning())
    {
      if (impl != null)
      {
        Free(impl);
        impl = null;
      }
      impl = Obtain();
      if (impl == null)
      {
        Log.e(LOG_TAG, "got nullptr when creating llarp::Context in jni");
        return START_NOT_STICKY;
      }

      String dataDir = getFilesDir().toString();
      LokinetConfig config;
      try
      {
        config = new LokinetConfig(dataDir);
      }
      catch(RuntimeException ex)
      {
        Log.e(LOG_TAG, ex.toString());
        return START_NOT_STICKY;
      }

      // FIXME: make these configurable
      String exitNode = "exit.loki";
      String upstreamDNS = "1.1.1.1";
      String ourRange = DetectFreeRange();

      if(ourRange.isEmpty())
      {
        Log.e(LOG_TAG, "cannot detect free range");
        return START_NOT_STICKY;
      }


      // set up config values
      config.AddDefaultValue("network", "exit-node", exitNode);
      config.AddDefaultValue("network", "ifaddr", ourRange);
      config.AddDefaultValue("dns", "upstream", upstreamDNS);


      if (!config.Load())
      {
        Log.e(LOG_TAG, "failed to load (or create) config file at: " + dataDir + "/lokinet.ini");
        return START_NOT_STICKY;
      }

      VpnService.Builder builder = new VpnService.Builder();

      builder.setMtu(1500);

      String[] parts = ourRange.split("/");
      String ourIPv4 = parts[0];
      int ourMask = Integer.parseInt(parts[1]);

      // set ip4
      builder.addAddress(ourIPv4, ourMask);
      builder.addRoute("0.0.0.0", 0);
      // set ip6
      // TODO: convert ipv4 to fd00::/8 range for ipv6
      // builder.addAddress(ourIPv6, ourMask + 96);
      // builder.addRoute("::", 0);

      builder.addDnsServer(upstreamDNS);
      builder.setSession("Lokinet");
      builder.setConfigureIntent(null);

      iface = builder.establish();
      if (iface == null)
      {
        Log.e(LOG_TAG, "VPN Interface from builder.establish() came back null");
        return START_NOT_STICKY;
      }

      m_FD = iface.detachFd();

      InjectVPNFD();

      new Thread(() -> {
          Configure(config);
          m_UDPSocket = GetUDPSocket();
          protect(m_UDPSocket);
          Mainloop();
          }).start();

      Log.d(LOG_TAG, "started successfully!");
    }
    else
    {
      Log.d(LOG_TAG, "already running");
    }

    return START_STICKY;
  }
}
