package com.shareable;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

import java.net.InetAddress;
import java.net.UnknownHostException;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.IllegalViewOperationException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

import info.whitebyte.hotspotmanager.WifiApManager;


public class ShareableWifi {

    private WifiManager wifi;
    private static final String ERR_NETWORK_NOT_ADDED = "ERR00";
    private static final String ERR_NETWORK_NOT_DISCONNECTED = "ERR01";
    private static final String ERR_NETWORK_NOT_ENABLED = "ERR02";
    private ReactApplicationContext mContext;

    private WifiStates states = null;
    private NetworkStates network = null;
    private IntentFilter wifiFilter = null;
    private WifiApManager hotspot;

    public ShareableWifi(ReactApplicationContext context) {
        wifi = (WifiManager)context.getApplicationContext().getSystemService(ReactApplicationContext.WIFI_SERVICE);
        hotspot = new WifiApManager(context);
        mContext = context;
    }


    public boolean isEnabled() {
        return wifi.isWifiEnabled();
    }

    public void enable(Promise enable) {

        states = new WifiStates(enable, null);
        wifiFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);

        // check if he opens the hotspot
        if(hotspot.isWifiApEnabled()){
            hotspot.setWifiApEnabled(null, false);
        }

        mContext.registerReceiver(states, wifiFilter);

        if(!wifi.setWifiEnabled(true))
            enable.reject("Error", "Unable to open wifi");
    }

    public void disable(Promise disable) {

        states = new WifiStates(null, disable);
        wifiFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);

        mContext.registerReceiver(states, wifiFilter);

        if(!wifi.setWifiEnabled(false))
            disable.reject("Error", "Unable to close wifi");
    }

    public boolean disconnect() {
        int netId = wifi.getConnectionInfo().getNetworkId();
        wifi.removeNetwork(netId);

        mContext.unregisterReceiver(network);

        return wifi.disconnect();
    }

    public void getAllWifi(Promise promise) {
        try {
            List<ScanResult> results = wifi.getScanResults();
            JSONArray wifiArray = new JSONArray();

            for (ScanResult result: results) {
                JSONObject wifiObject = new JSONObject();
                if(!result.SSID.equals("")){
                    try {
                        wifiObject.put("SSID", result.SSID);
                        wifiObject.put("BSSID", result.BSSID);
                        wifiObject.put("capabilities", result.capabilities);
                        wifiObject.put("frequency", result.frequency);
                        wifiObject.put("level", result.level);
                        wifiObject.put("timestamp", result.timestamp);
                    } catch (JSONException e) {
                        promise.reject("error", e.getMessage());
                    }
                    wifiArray.put(wifiObject);
                }
            }
            promise.resolve(wifiArray.toString());
        } catch (IllegalViewOperationException e) {
            promise.reject("error", e.getMessage());
        }
    }

    public void getSpecificWifi(String ssid, Promise promise) {
        try {
            List<ScanResult> results = wifi.getScanResults();
            JSONArray wifiArray = new JSONArray();

            for (ScanResult result: results) {
                JSONObject wifiObject = new JSONObject();
                if(!result.SSID.equals("") && result.SSID.equals(ssid)){
                    try {
                        wifiObject.put("SSID", result.SSID);
                        wifiObject.put("BSSID", result.BSSID);
                        wifiObject.put("capabilities", result.capabilities);
                        wifiObject.put("frequency", result.frequency);
                        wifiObject.put("level", result.level);
                        wifiObject.put("timestamp", result.timestamp);
                    } catch (JSONException e) {
                        promise.reject("error", e.getMessage());
                    }
                    wifiArray.put(wifiObject);
                    break;
                }
            }
            promise.resolve(wifiArray.toString());
        } catch (IllegalViewOperationException e) {
            promise.reject("error", e.getMessage());
        }
    }

    public void connect(String ssid, String password, Promise promise) {
        List < ScanResult > results = wifi.getScanResults();
        boolean found = false;
        for (ScanResult result: results) {
            String resultString = "" + result.SSID;
            if (ssid.equals(resultString)) {
                found = true;
                connectTo(result, password, ssid, promise);
            }
        }
        if(!found)
            promise.reject("Error", "Unable to find the network");
    }

    private void connectTo(ScanResult result, String password, String ssid, Promise promise) {
        WifiInfo info = wifi.getConnectionInfo();

        if(info.getNetworkId() != -1) {
            // disconnect current network
            boolean disconnect = wifi.disconnect();
            if ( !disconnect ) {
                promise.reject(ERR_NETWORK_NOT_DISCONNECTED, "Unable to disconnect the current network");
                return;
            }
        }
        //Make new configuration
        WifiConfiguration conf = new WifiConfiguration();


        //clear alloweds
        conf.allowedAuthAlgorithms.clear();
        conf.allowedGroupCiphers.clear();
        conf.allowedKeyManagement.clear();
        conf.allowedPairwiseCiphers.clear();
        conf.allowedProtocols.clear();

        // Quote ssid and password
        conf.SSID = String.format("\"%s\"", ssid);

        WifiConfiguration tempConfig = this.IsExist(conf.SSID);
        if (tempConfig != null) {
            wifi.removeNetwork(tempConfig.networkId);
        }

        String capabilities = result.capabilities;

        // appropriate ciper is need to set according to security type used
        if (capabilities.contains("WPA") || capabilities.contains("WPA2") || capabilities.contains("WPA/WPA2 PSK")) {

            // This is needed for WPA/WPA2
            // Reference - https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/wifi/java/android/net/wifi/WifiConfiguration.java#149
            conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);

            conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);

            conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);

            conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
            conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);

            conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
            conf.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
            conf.status = WifiConfiguration.Status.ENABLED;
            conf.preSharedKey = String.format("\"%s\"", password);

        } else if (capabilities.contains("WEP")) {
            // This is needed for WEP
            // Reference - https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/wifi/java/android/net/wifi/WifiConfiguration.java#149
            conf.wepKeys[0] = "\"" + password + "\"";
            conf.wepTxKeyIndex = 0;
            conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
        } else {
            conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
        }

        List<WifiConfiguration> mWifiConfigList = wifi.getConfiguredNetworks();
        int updateNetwork = -1;

        // Use the existing network config if exists
        for (WifiConfiguration wifiConfig : mWifiConfigList) {
            if (wifiConfig.SSID.equals(conf.SSID)) {
                conf=wifiConfig;
                updateNetwork=conf.networkId;
            }
        }

        // If network not already in configured networks add new network
        if ( updateNetwork == -1 ) {
            updateNetwork = wifi.addNetwork(conf);
            wifi.saveConfiguration();
        }

        // if network not added return false
        if ( updateNetwork == -1 ) {
            promise.reject(ERR_NETWORK_NOT_ADDED, "Unable to add the network");
        }


        // enable new network
        boolean enableNetwork = wifi.enableNetwork(updateNetwork, true);
        if ( !enableNetwork ) {
            promise.reject(ERR_NETWORK_NOT_DISCONNECTED, "Unable to enable the new network");
        }
        IntentFilter filter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        network = new NetworkStates(promise);
        mContext.registerReceiver(network, filter);



        //promise.resolve("Connected");
    }

    private WifiConfiguration IsExist(String SSID) {
        List<WifiConfiguration> existingConfigs = wifi.getConfiguredNetworks();
        for (WifiConfiguration existingConfig : existingConfigs) {
            if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
                return existingConfig;
            }
        }
        return null;
    }

    private static String addressToIp(int hostAddress) {
        byte[] addressBytes = {(byte) (0xff & hostAddress),
            (byte) (0xff & (hostAddress >> 8)),
            (byte) (0xff & (hostAddress >> 16)),
            (byte) (0xff & (hostAddress >> 24))};

        try {
            return InetAddress.getByAddress(addressBytes).toString().substring(1);
        } catch (UnknownHostException e) {
            throw new AssertionError();
        }
    }

    public void rescanWifi(Promise promise) {
        WifiReceiver receiverWifi = new WifiReceiver(wifi, promise);
        mContext.registerReceiver(receiverWifi, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
        wifi.startScan();
    }

    public void rescanWifi(String ssid, Promise promise) {
        WifiReceiver receiverWifi = new WifiReceiver(wifi, promise, ssid);
        mContext.registerReceiver(receiverWifi, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
        wifi.startScan();
    }

    public String getServerAddress() {
        String ip = null;
        try {
            ip = addressToIp(wifi.getDhcpInfo().serverAddress);
            return ip;
        } catch(Exception e) {
            return ip;
        }
        
    }



    class WifiStates extends BroadcastReceiver {
        private Promise enable;
        private Promise disable;

        public WifiStates(Promise enable, Promise disable) {
            this.enable = enable;
            this.disable = disable;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            final int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
            switch(wifiState) {
                case WifiManager.WIFI_STATE_ENABLED:
                    if(this.enable != null) {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        enable.resolve("Wifi Enabled");
                        context.unregisterReceiver(states);
                    }

                    break;
                case WifiManager.WIFI_STATE_DISABLED:
                    if(this.disable != null) {
                        disable.resolve("Wifi Disabled");
                        context.unregisterReceiver(states);
                    }

                    break;
            }
        }
    }

    class NetworkStates extends BroadcastReceiver {

        private Promise promise;
        private boolean isConnected = false;

        public NetworkStates(Promise promise) {
            this.promise = promise;
        }
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);

                if(networkInfo.isConnected() && !isConnected) {
                    Log.i("ble", "Network connected");
                    isConnected = true;
                    this.promise.resolve("Network connected");
                }
            }
        }
    }
}

    class WifiReceiver extends BroadcastReceiver {

    private Promise promise;
    private String ssid;
    private WifiManager wifi;

    public WifiReceiver(final WifiManager wifi, Promise promise) {
        super();
        this.promise = promise;
        this.wifi = wifi;
        this.ssid = null;
    }

    public WifiReceiver(final WifiManager wifi, Promise promise, String ssid) {
        super();
        this.promise = promise;
        this.wifi = wifi;
        this.ssid = ssid;
    }

    // This method call when number of wifi connections changed
    public void onReceive(Context c, Intent intent) {

        c.unregisterReceiver(this);

        try {
            List<ScanResult> results = this.wifi.getScanResults();
            JSONArray wifiArray = new JSONArray();



            for (ScanResult result : results) {
                JSONObject wifiObject = new JSONObject();
                if (!result.SSID.equals("") && this.ssid == null) {
                    try {
                        wifiObject.put("SSID", result.SSID);
                        wifiObject.put("BSSID", result.BSSID);
                        wifiObject.put("capabilities", result.capabilities);
                        wifiObject.put("frequency", result.frequency);
                        wifiObject.put("level", result.level);
                        wifiObject.put("timestamp", result.timestamp);
                    } catch (JSONException e) {
                        this.promise.reject("error" ,e.getMessage());
                        return;
                    }
                    wifiArray.put(wifiObject);
                } else {
                    if(result.SSID.equals(this.ssid)) {
                        try {
                            wifiObject.put("SSID", result.SSID);
                            wifiObject.put("BSSID", result.BSSID);
                            wifiObject.put("capabilities", result.capabilities);
                            wifiObject.put("frequency", result.frequency);
                            wifiObject.put("level", result.level);
                            wifiObject.put("timestamp", result.timestamp);
                        } catch (JSONException e) {
                            this.promise.reject("error", e.getMessage());
                            return;
                        }
                        wifiArray.put(wifiObject);
                        break;
                    }
                }
            }
            this.promise.resolve(wifiArray.toString());
            return;
        } catch (IllegalViewOperationException e) {
            this.promise.reject("error", e.getMessage());
            return;
        }
    }
}
