/*
 * This is the source code of Telegram for Android v. 3.x.x.
 * It is licensed under GNU GPL v. 2 or later.
 * You should have received a copy of the license in this archive (see LICENSE).
 *
 * Copyright Grishka, 2013-2016.
 */

package org.telegram.messenger.voip;

import android.app.Activity;
import android.content.SharedPreferences;
import android.media.audiofx.AcousticEchoCanceler;
import android.media.audiofx.NoiseSuppressor;
import android.os.Build;
import android.os.SystemClock;

import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildConfig;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.voip.VoIPHelper;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Locale;

public class VoIPController {

	public static final int NET_TYPE_UNKNOWN = 0;
	public static final int NET_TYPE_GPRS = 1;
	public static final int NET_TYPE_EDGE = 2;
	public static final int NET_TYPE_3G = 3;
	public static final int NET_TYPE_HSPA = 4;
	public static final int NET_TYPE_LTE = 5;
	public static final int NET_TYPE_WIFI = 6;
	public static final int NET_TYPE_ETHERNET = 7;
	public static final int NET_TYPE_OTHER_HIGH_SPEED = 8;
	public static final int NET_TYPE_OTHER_LOW_SPEED = 9;
	public static final int NET_TYPE_DIALUP = 10;
	public static final int NET_TYPE_OTHER_MOBILE = 11;

	public static final int STATE_WAIT_INIT = 1;
	public static final int STATE_WAIT_INIT_ACK = 2;
	public static final int STATE_ESTABLISHED = 3;
	public static final int STATE_FAILED = 4;
	public static final int STATE_RECONNECTING = 5;

	public static final int DATA_SAVING_NEVER=0;
	public static final int DATA_SAVING_MOBILE=1;
	public static final int DATA_SAVING_ALWAYS=2;

	public static final int ERROR_LOCALIZED=-3;
	public static final int ERROR_PRIVACY=-2;
	public static final int ERROR_PEER_OUTDATED=-1;
	public static final int ERROR_UNKNOWN=0;
	public static final int ERROR_INCOMPATIBLE=1;
	public static final int ERROR_TIMEOUT=2;
	public static final int ERROR_AUDIO_IO=3;

	private long nativeInst = 0;
	private long callStartTime;
	private ConnectionStateListener listener;

	public VoIPController() {
		nativeInst = nativeInit(Build.VERSION.SDK_INT);
	}

	public void start() {
		ensureNativeInstance();
		nativeStart(nativeInst);
	}

	public void connect() {
		ensureNativeInstance();
		nativeConnect(nativeInst);
	}

	public void setRemoteEndpoints(TLRPC.TL_phoneConnection[] endpoints, boolean allowP2p) {
		if (endpoints.length == 0) {
			throw new IllegalArgumentException("endpoints size is 0");
		}
		for (int a = 0; a < endpoints.length; a++) {
			TLRPC.TL_phoneConnection endpoint = endpoints[a];
			if (endpoint.ip == null || endpoint.ip.length() == 0) {
				throw new IllegalArgumentException("endpoint " + endpoint + " has empty/null ipv4");
			}
			if (endpoint.peer_tag != null && endpoint.peer_tag.length != 16) {
				throw new IllegalArgumentException("endpoint " + endpoint + " has peer_tag of wrong length");
			}
		}
		ensureNativeInstance();
		nativeSetRemoteEndpoints(nativeInst, endpoints, allowP2p);
	}

	public void setEncryptionKey(byte[] key, boolean isOutgoing) {
		if (key.length != 256) {
			throw new IllegalArgumentException("key length must be exactly 256 bytes but is " + key.length);
		}
		ensureNativeInstance();
		nativeSetEncryptionKey(nativeInst, key, isOutgoing);
	}

	public static void setNativeBufferSize(int size) {
		nativeSetNativeBufferSize(size);
	}

	public void release() {
		ensureNativeInstance();
		nativeRelease(nativeInst);
		nativeInst = 0;
	}

	public String getDebugString() {
		ensureNativeInstance();
		return nativeGetDebugString(nativeInst);
	}

	private void ensureNativeInstance() {
		if (nativeInst == 0) {
			throw new IllegalStateException("Native instance is not valid");
		}
	}

	public void setConnectionStateListener(ConnectionStateListener connectionStateListener) {
		listener = connectionStateListener;
	}

	private void handleStateChange(int state) {
		if(state==STATE_ESTABLISHED && callStartTime==0)
			callStartTime = SystemClock.elapsedRealtime();
		if (listener != null) {
			listener.onConnectionStateChanged(state);
		}
	}

	public void setNetworkType(int type) {
		ensureNativeInstance();
		nativeSetNetworkType(nativeInst, type);
	}

	public long getCallDuration() {
		return SystemClock.elapsedRealtime() - callStartTime;
	}

	public void setMicMute(boolean mute) {
		ensureNativeInstance();
		nativeSetMicMute(nativeInst, mute);
	}

	public void setConfig(double recvTimeout, double initTimeout, int dataSavingOption, long callID){
		ensureNativeInstance();
		boolean sysAecAvailable=false, sysNsAvailable=false;
		if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN){
			try{
				sysAecAvailable=AcousticEchoCanceler.isAvailable();
				sysNsAvailable=AcousticEchoCanceler.isAvailable();
			}catch(Throwable x){

			}
		}
		SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
		boolean dump = preferences.getBoolean("dbg_dump_call_stats", false);
		nativeSetConfig(nativeInst, recvTimeout, initTimeout, dataSavingOption,
				Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN || !(sysAecAvailable && VoIPServerConfig.getBoolean("use_system_aec", true)),
				Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN || !(sysNsAvailable && VoIPServerConfig.getBoolean("use_system_ns", true)),
				true, BuildConfig.DEBUG ? getLogFilePath("voip") : getLogFilePath(callID), BuildConfig.DEBUG && dump ? getLogFilePath("voipStats") : null);
	}

	public void debugCtl(int request, int param){
		ensureNativeInstance();
		nativeDebugCtl(nativeInst, request, param);
	}

	public long getPreferredRelayID(){
		ensureNativeInstance();
		return nativeGetPreferredRelayID(nativeInst);
	}

	public int getLastError(){
		ensureNativeInstance();
		return nativeGetLastError(nativeInst);
	}

	public void getStats(Stats stats){
		ensureNativeInstance();
		if(stats==null)
			throw new NullPointerException("You're not supposed to pass null here");
		nativeGetStats(nativeInst, stats);
	}

	public static String getVersion(){
		return nativeGetVersion();
	}

	private String getLogFilePath(String name){
		Calendar c=Calendar.getInstance();
		return new File(ApplicationLoader.applicationContext.getExternalFilesDir(null),
				String.format(Locale.US, "logs/%02d_%02d_%04d_%02d_%02d_%02d_%s.txt",
						c.get(Calendar.DATE), c.get(Calendar.MONTH)+1, c.get(Calendar.YEAR),
						c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND), name)).getAbsolutePath();
	}

	private String getLogFilePath(long callID){
		File dir=VoIPHelper.getLogsDir();
		if(!BuildConfig.DEBUG){
			File[] _logs=dir.listFiles();
			ArrayList<File> logs=new ArrayList<>();
			logs.addAll(Arrays.asList(_logs));
			while(logs.size()>20){
				File oldest=logs.get(0);
				for(File file : logs){
					if(file.getName().endsWith(".log") && file.lastModified()<oldest.lastModified())
						oldest=file;
				}
				oldest.delete();
				logs.remove(oldest);
			}
		}
		return new File(dir, callID+".log").getAbsolutePath();
	}

	public String getDebugLog(){
		ensureNativeInstance();
		return nativeGetDebugLog(nativeInst);
	}

	public void setProxy(String address, int port, String username, String password){
		ensureNativeInstance();
		if(address==null)
			throw new NullPointerException("address can't be null");
		nativeSetProxy(nativeInst, address, port, username, password);
	}

	private native long nativeInit(int systemVersion);
	private native void nativeStart(long inst);
	private native void nativeConnect(long inst);
	private static native void nativeSetNativeBufferSize(int size);
	private native void nativeSetRemoteEndpoints(long inst, TLRPC.TL_phoneConnection[] endpoints, boolean allowP2p);
	private native void nativeRelease(long inst);
	private native void nativeSetNetworkType(long inst, int type);
	private native void nativeSetMicMute(long inst, boolean mute);
	private native void nativeDebugCtl(long inst, int request, int param);
	private native void nativeGetStats(long inst, Stats stats);
	private native void nativeSetConfig(long inst, double recvTimeout, double initTimeout, int dataSavingOption, boolean enableAEC, boolean enableNS, boolean enableAGC, String logFilePath, String statsDumpPath);
	private native void nativeSetEncryptionKey(long inst, byte[] key, boolean isOutgoing);
	private native void nativeSetProxy(long inst, String address, int port, String username, String password);
	private native long nativeGetPreferredRelayID(long inst);
	private native int nativeGetLastError(long inst);
	private native String nativeGetDebugString(long inst);
	private static native String nativeGetVersion();
	private native String nativeGetDebugLog(long inst);

	public interface ConnectionStateListener {
		void onConnectionStateChanged(int newState);
	}

	public static class Stats{
		public long bytesSentWifi;
		public long bytesRecvdWifi;
		public long bytesSentMobile;
		public long bytesRecvdMobile;

		@Override
		public String toString(){
			return "Stats{"+
					"bytesRecvdMobile="+bytesRecvdMobile+
					", bytesSentWifi="+bytesSentWifi+
					", bytesRecvdWifi="+bytesRecvdWifi+
					", bytesSentMobile="+bytesSentMobile+
					'}';
		}
	}
}
