package com.nutomic.syncthingandroid.preferences;

import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
import android.widget.Toast;

import com.nutomic.syncthingandroid.R;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * MultiSelectListPreference which allows the user to select on which WiFi networks (based on SSID)
 * syncing should be allowed.
 *
 * Setting can be "All networks" (none selected), or selecting individual networks.
 *
 * Due to restrictions in Android, it is possible/likely, that the list of saved WiFi networks
 * cannot be retrieved if the WiFi is turned off. In this case, an explanation is shown.
 *
 * The preference is stored as Set&lt;String&gt; where an empty set represents
 * "all networks allowed".
 *
 * SSIDs are formatted according to the naming convention of WifiManager, i.e. they have the
 * surrounding double-quotes (") for UTF-8 names, or they are hex strings (if not quoted).
 */
public class WifiSsidPreference extends MultiSelectListPreference {

    public WifiSsidPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WifiSsidPreference(Context context) {
        super(context);
    }

    /**
     * Show the dialog if WiFi is available and configured networks can be loaded.
     * Otherwise will display a Toast requesting to turn on WiFi.
     *
     * On opening of the dialog, will also remove any SSIDs from the set that have been removed
     * by the user in the WiFi manager. This change will be persisted only if the user changes
     * any other setting
     */
    @Override
    protected void showDialog(Bundle state) {
        WifiConfiguration[] networks = loadConfiguredNetworksSorted();
        if (networks != null) {
            Set<String> selected = getSharedPreferences().getStringSet(getKey(), new HashSet<String>());
            // from JavaDoc: Note that you must not modify the set instance returned by this call.
            // therefore required to make a defensive copy of the elements
            selected = new HashSet<>(selected);
            CharSequence[] all = extractSsid(networks, false);
            filterRemovedNetworks(selected, all);
            setEntries(extractSsid(networks, true)); // display without surrounding quotes
            setEntryValues(all); // the value of the entry is the SSID "as is"
            setValues(selected); // the currently selected values (without meanwhile deleted networks)
            super.showDialog(state);
        } else {
            Toast.makeText(getContext(), R.string.sync_only_wifi_ssids_wifi_turn_on_wifi, Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Removes any network that is no longer saved on the device. Otherwise it will never be
     * removed from the allowed set by MultiSelectListPreference.
     */
    private void filterRemovedNetworks(Set<String> selected, CharSequence[] all) {
        HashSet<CharSequence> availableNetworks = new HashSet<>(Arrays.asList(all));
        selected.retainAll(availableNetworks);
    }

    /**
     * Converts WiFi configuration to it's string representation, using the SSID.
     *
     * It can also remove surrounding quotes which indicate that the SSID is an UTF-8
     * string and not a Hex-String, if the strings are intended to be displayed to the
     * user, who will not expect the quotes.
     *
     * @param configs the objects to convert
     * @param stripQuotes if to remove surrounding quotes
     * @return the formatted SSID of the wifi configurations
     */
    private CharSequence[] extractSsid(WifiConfiguration[] configs, boolean stripQuotes) {
        CharSequence[] result = new CharSequence[configs.length];
        for (int i = 0; i < configs.length; i++) {
            // WiFi SSIDs can either be UTF-8 (encapsulated in '"') or hex-strings
            if (stripQuotes)
                result[i] = configs[i].SSID.replaceFirst("^\"", "").replaceFirst("\"$", "");
            else
                result[i] = configs[i].SSID;
        }
        return result;
    }

    /**
     * Load the configured WiFi networks, sort them by SSID.
     *
     * @return a sorted array of WifiConfiguration, or null, if data cannot be retrieved
     */
    private WifiConfiguration[] loadConfiguredNetworksSorted() {
        WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
        if (wifiManager != null) {
            List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
            // if WiFi is turned off, getConfiguredNetworks returns null on many devices
            if (configuredNetworks != null) {
                WifiConfiguration[] result = configuredNetworks.toArray(new WifiConfiguration[configuredNetworks.size()]);
                Arrays.sort(result, new Comparator<WifiConfiguration>() {
                    @Override
                    public int compare(WifiConfiguration lhs, WifiConfiguration rhs) {
                        return lhs.SSID.compareToIgnoreCase(rhs.SSID);
                    }
                });
                return result;
            }
        }
        // WiFi is turned off or device doesn't have WiFi
        return null;
    }

}

@@ -3976,24 +3976,147 @@
 gth; i++) %7B%0A
+            // See #620: there may be null-SSIDs%0A            String ssid = configs%5Bi%5D.SSID != null ? configs%5Bi%5D.SSID : %22%22;%0A
             
@@ -4243,31 +4243,20 @@
 lt%5Bi%5D = 
-configs%5Bi%5D.SSID
+ssid
 .replace
@@ -4342,31 +4342,20 @@
 lt%5Bi%5D = 
-configs%5Bi%5D.SSID
+ssid
 ;%0A      
@@ -5333,16 +5333,215 @@
     
+// See #620: There may be null-SSIDs%0A                        String l = lhs.SSID != null ? lhs.SSID : %22%22;%0A                        String r = rhs.SSID != null ? rhs.SSID : %22%22;%0A                        
 return l
 hs.S
@@ -5536,23 +5536,16 @@
 return l
-hs.SSID
 .compare
@@ -5558,23 +5558,16 @@
 reCase(r
-hs.SSID
 );%0A     

Add checks for null-SSID (fix for #620)