/* 
 * Copyright 2014, 2015 Bram Bonné
 *
 * This file is part of Wi-Fi PrivacyPolice.
 * 
 * Wi-Fi PrivacyPolice is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Wi-Fi PrivacyPolice is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Wi-Fi PrivacyPolice.  If not, see <http://www.gnu.org/licenses/>.
*/

package be.uhasselt.privacypolice;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.preference.PreferenceManager;
import android.util.Log;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/* Class used for storing and retreiving user preferences, including the list of trusted and
   untrusted access points
 */

public class PreferencesStorage {
    private SharedPreferences prefs;
    private WifiManager wifiManager;
    // String used to identify MAC addresses of allowed access points
    private final String ALLOWED_BSSID_PREFIX = "ABSSID//";
    private final String BLOCKED_BSSID_PREFIX = "BBSSID//";

    public PreferencesStorage(Context ctx) {
        this.prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
        this.wifiManager =  (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
        try {
            Log.v("PrivacyPolice", "Current preferences are: " + prefs.getAll().toString());
        } catch (NullPointerException npe) {
            Log.d("PrivacyPolice", "No preferences found!");
        }
    }

    public boolean getEnableOnlyAvailableNetworks() {
        return prefs.getBoolean("enableOnlyAvailableNetworks", true);
    }

    public boolean getOnlyConnectToKnownAccessPoints() {
        return prefs.getBoolean("onlyConnectToKnownAccessPoints", false);
    }

    public boolean getLocationNoticeEnabled() {
        return prefs.getBoolean("showLocationNotice", true);
    }

    /**
     * Will be enabled if we decide to implement tracking
     * @return
     *
    public boolean getTrackingAllowed() {
        return prefs.getBoolean("trackingAllowed", false);
    } */

    /**
     * Get a list of trusted MAC addresses for a given SSID
       @param SSID the SSID of the network
     */
    public Set<String> getAllowedBSSIDs(String SSID) {
        return getBSSIDs(SSID, true);
    }

    /**
     * Get a list of blocked MAC addresses for a given SSID
     @param SSID the SSID of the network
     */
    public Set<String> getBlockedBSSIDs(String SSID) {
        return getBSSIDs(SSID, false);
    }

    /**
     * Helper function for getAllowedBSSIDs and getBlockedSSIDs
     @param SSID the SSID of the network
     @param allowed when true, return the allowed BSSIDs, when false, return blocked BSSIDs
     */
    private Set<String> getBSSIDs(String SSID, boolean allowed) {
        String prefix;
        if (allowed)
            prefix = ALLOWED_BSSID_PREFIX;
        else
            prefix = BLOCKED_BSSID_PREFIX;
        // Return a copy so the receiver cannot edit the list
        return new HashSet<>(prefs.getStringSet(prefix + SSID, new HashSet<String>()));
    }

    /**
     * Get a list of SSIDs for which we remembered at least one BSSID (either allowed or blocked)
     */
    public Set<String> getNonemptySSIDs() {
        Set<String> results = new HashSet<>();

        Map<String, ?> allPrefs = prefs.getAll();
        for (String key : allPrefs.keySet()) {
            if (key.startsWith(ALLOWED_BSSID_PREFIX) && prefs.getStringSet(key, new HashSet<String>()).size() > 0) {
                results.add(key.substring(ALLOWED_BSSID_PREFIX.length()));
            } else if (key.startsWith(BLOCKED_BSSID_PREFIX) && prefs.getStringSet(key, new HashSet<String>()).size() > 0) {
                results.add(key.substring(BLOCKED_BSSID_PREFIX.length()));
            }
        }
        return results;
    }

    /**
     * Adds all BSSIDs that are currently in range for the specified SSID (prevents nagging)
     * We are assuming the user does not know the BSSID of the AP it wants to trust, anyway, and
     * choose the more useable option.
     * @param SSID the SSID of the network that needs to be allowed at this location
     */
    public void addAllowedBSSIDsForLocation(String SSID) {
        List<ScanResult> scanResults = wifiManager.getScanResults();
        for (ScanResult result : scanResults) {
            if (SSID.equals(result.SSID))
                addAllowedBSSID(SSID, result.BSSID);
        }
    }

    public void addAllowedBSSID(String SSID, String BSSID) {
        Log.i("PrivacyPolice", "Adding allowed BSSID " + BSSID + " for network " + SSID);
        editBSSID(SSID, BSSID, true, true);
    }

    public void addBlockedBSSID(String SSID, String BSSID) {
        Log.i("PrivacyPolice", "Adding blocked BSSID " + BSSID + " for network " + SSID);
        editBSSID(SSID, BSSID, false, true);
    }

    /**
     * Remove a specific MAC address as trusted for the given SSID
     * @param SSID the SSID of the network
     * @param BSSID the MAC address of the trusted access point
     */
    public void removeAllowedBSSID(String SSID, String BSSID) {
        Log.i("PrivacyPolice", "Removing allowed BSSID " + BSSID + " for network " + SSID);
        editBSSID(SSID, BSSID, true, false);
    }

    public void removeBlockedBSSID(String SSID, String BSSID) {
        Log.i("PrivacyPolice", "Removing blocked BSSID " + BSSID + " for network " + SSID);
        editBSSID(SSID, BSSID, false, false);
    }

    /**
     * Helper function for addAllowedBSSID, addBlockedSSID, removedAllowedBSSID and removeBlockedBSSID
     * @param SSID the SSID of the network
     * @param BSSID the MAC address of the trusted access point
     * @param allowed when true, add to allowed BSSIDs, when false, add to blocked BSSIDs
     * @param add when true, add the network to the list, when false, remove the network
     */
    private void editBSSID(String SSID, String BSSID, boolean allowed, boolean add) {
        // Get the correct prefix (allowed or blocked hotspots)
        String prefix;
        if (allowed)
            prefix = ALLOWED_BSSID_PREFIX;
        else
            prefix = BLOCKED_BSSID_PREFIX;

        // Get the current list of known networks
        Set<String> currentlyInList = prefs.getStringSet(prefix + SSID, new HashSet<String>());
        // Create copy of list, because sharedPreferences only checks whether *reference* is the same
        // In order to add elements, we thus need a new object (otherwise nothing changes)
        Set<String> newList = new HashSet<>(currentlyInList);
        if (add)
            newList.add(BSSID);
        else
            newList.remove(BSSID);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putStringSet(prefix + SSID, newList);
        editor.commit();
    }

    /**
     * Erase all trusted and untrusted hotspots.
     */
    public void clearBSSIDLists() {
        Log.d("PrivacyPolice", "Removing all trusted/untrusted hotspots");
        SharedPreferences.Editor editor = prefs.edit();

        // Erase all allowed SSIDs, by emptying their MAC address lists.
        for (String key: prefs.getAll().keySet()) {
            if (key.startsWith(ALLOWED_BSSID_PREFIX) || key.startsWith(BLOCKED_BSSID_PREFIX))
                editor.putStringSet(key, new HashSet<String>());
        }

        editor.commit();
    }

    /**
     * Erase all known hotspots for a specific SSID.
     */
    public void clearBSSIDsForNetwork(String SSID) {
        Log.d("PrivacyPolice", "Removing all known hotspots for network " + SSID);
        SharedPreferences.Editor editor = prefs.edit();

        // Erase all trusted network for this SSID, by emptying its MAC address list.
        editor.putStringSet(ALLOWED_BSSID_PREFIX + SSID, new HashSet<String>());
        // Erase all blocked network for this SSID
        editor.putStringSet(BLOCKED_BSSID_PREFIX + SSID, new HashSet<String>());

        editor.commit();
    }
}