package org.torproject.onionmasq;

import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.IBinder;

import androidx.lifecycle.MutableLiveData;

import org.torproject.onionmasq.errors.CountryCodeException;
import org.torproject.onionmasq.errors.OnionmasqException;
import org.torproject.onionmasq.errors.ProxyStoppedException;
import org.torproject.onionmasq.logging.LogObservable;
import org.torproject.onionmasq.events.OnionmasqEvent;

import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;

public class OnionMasq {

    private static final String TAG = OnionMasq.class.getSimpleName();
    private static OnionMasq instance;
    private final ConnectivityManager connectivityManager;
    private final Context appContext;
    private ISocketProtect serviceBinder;
    private final OnionmasqEventObservable eventObservable;


    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            if (service instanceof ISocketProtect) {
                serviceBinder =  (ISocketProtect) service;
            } else {
                throw new IllegalArgumentException("Bound service needs to implement ISocketProtect interface");
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            serviceBinder = null;
        }
    };

    private static OnionMasq getInstance() throws IllegalStateException {
        if (instance == null) {
            throw new IllegalStateException("OnionmasqHelper is not initialized");
        }
        return instance;
    }

    public static void init(Context context) throws OnionmasqException {
        if (instance == null) {
            instance = new OnionMasq(context);
        }
    }

    private OnionMasq(Context context) throws OnionmasqException {
        this.appContext = context.getApplicationContext();
        this.connectivityManager = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        this.eventObservable = new OnionmasqEventObservable();
        OnionMasqJni.init();
    }

    @TargetApi(Build.VERSION_CODES.Q)
    static int getConnectionOwnerUid(int protocol, byte[] rawSourceAddress, int sourcePort, byte[] rawDestinationAddress, int destinationPort) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            try {
                InetSocketAddress sourceSocketAddress = new InetSocketAddress(InetAddress.getByAddress(rawSourceAddress), sourcePort);
                InetSocketAddress destinationSocketAddress = new InetSocketAddress(InetAddress.getByAddress(rawDestinationAddress), destinationPort);
                return getInstance().connectivityManager.getConnectionOwnerUid(protocol, sourceSocketAddress, destinationSocketAddress);
            } catch (UnknownHostException | IllegalStateException e) {
                e.printStackTrace();
            }
        }

        return -1; // Process.INVALID_UID
    }

    public static void bindVPNService(Class vpnServiceClass) {
        Intent intent = new Intent(getInstance().appContext, vpnServiceClass);
        getInstance().appContext.bindService(intent, getInstance().connection, Context.BIND_AUTO_CREATE);
    }

    public static void unbindVPNService() {
        getInstance().appContext.unbindService(getInstance().connection);
    }

    public static void start(int fileDescriptor) throws OnionmasqException {
        start(fileDescriptor, null);
    }

    public static void start(int fileDescriptor, String bridgeLines) throws OnionmasqException {
        OnionMasqJni.runProxy(
                fileDescriptor,
                getInstance().appContext.getCacheDir().getAbsolutePath(),
                getInstance().appContext.getFilesDir().getAbsolutePath(),
                bridgeLines);
    }

    public static void stop() {
        OnionMasqJni.closeProxy();
        OnionMasqJni.resetCounters();
    }

    public static void refreshCircuits() throws ProxyStoppedException {
        OnionMasqJni.refreshCircuits();
    }

    public static void refreshCircuitsForApp(long appId) throws ProxyStoppedException {
        OnionMasqJni.refreshCircuitsForApp(appId);
    }

    static boolean protect(int socket)  {
        if (getInstance().serviceBinder == null) {
            LogObservable.getInstance().addLog("Cannot protect Socket " + socket + ". VpnService is not registered.");
            return false;
        }

        return  getInstance().serviceBinder.protect(socket);
    }

    public static MutableLiveData<OnionmasqEvent> getEventObservable() {
        return getInstance().eventObservable.getEvent();
    }

    static void updateEventObservable(OnionmasqEvent event) {
        getInstance().eventObservable.update(event);
    }
    public static long getBytesReceived() {
        return OnionMasqJni.getBytesReceived();
    }

    public static long getBytesReceivedForApp(long appId) { return OnionMasqJni.getBytesReceivedForApp(appId); };

    public static long getBytesSent() {
        return OnionMasqJni.getBytesSent();
    }

    public static long getBytesSentForApp(long appId) { return OnionMasqJni.getBytesSentForApp(appId); };

    public static void resetCounters() {
        OnionMasqJni.resetCounters();
    }

    public static void setCountryCode(String cc) throws CountryCodeException { OnionMasqJni.setCountryCode(cc); }
}
