/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.wifi.dpp;

import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_NO_PASSWORD;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_SAE;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WEP;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WPA_PSK;

import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.AuthAlgorithm;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

/**
 * Wraps the parameters of ZXing reader library's Wi-Fi Network config format.
 * Please check {@code WifiQrCode} for detail of the format.
 *
 * Checks below members of {@code WifiDppUtils} for more information.
 * EXTRA_WIFI_SECURITY / EXTRA_WIFI_SSID / EXTRA_WIFI_PRE_SHARED_KEY / EXTRA_WIFI_HIDDEN_SSID /
 * EXTRA_QR_CODE
 */
public class WifiNetworkConfig {

    static final String FAKE_SSID = "fake network";
    static final String FAKE_PASSWORD = "password";
    private static final String TAG = "WifiNetworkConfig";

    private String mSecurity;
    private String mSsid;
    private String mPreSharedKey;
    private boolean mHiddenSsid;
    private int mNetworkId;
    private boolean mIsHotspot;

    @VisibleForTesting
    WifiNetworkConfig(String security, String ssid, String preSharedKey,
            boolean hiddenSsid, int networkId, boolean isHotspot) {
        mSecurity = security;
        mSsid = ssid;
        mPreSharedKey = preSharedKey;
        mHiddenSsid = hiddenSsid;
        mNetworkId = networkId;
        mIsHotspot = isHotspot;
    }

    public WifiNetworkConfig(WifiNetworkConfig config) {
        mSecurity = config.mSecurity;
        mSsid = config.mSsid;
        mPreSharedKey = config.mPreSharedKey;
        mHiddenSsid = config.mHiddenSsid;
        mNetworkId = config.mNetworkId;
        mIsHotspot = config.mIsHotspot;
    }

    /**
     * Wi-Fi DPP activities should implement this interface for fragments to retrieve the
     * WifiNetworkConfig for configuration
     */
    public interface Retriever {
        public WifiNetworkConfig getWifiNetworkConfig();
    }

    /**
     * Retrieve WifiNetworkConfig from below 2 intents
     *
     * android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR
     * android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER
     */
    public static WifiNetworkConfig getValidConfigOrNull(Intent intent) {
        final String security = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SECURITY);
        final String ssid = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SSID);
        final String preSharedKey = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY);
        final boolean hiddenSsid = intent.getBooleanExtra(WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID,
                false);
        final int networkId = intent.getIntExtra(WifiDppUtils.EXTRA_WIFI_NETWORK_ID,
                WifiConfiguration.INVALID_NETWORK_ID);
        final boolean isHotspot = intent.getBooleanExtra(WifiDppUtils.EXTRA_IS_HOTSPOT, false);

        return getValidConfigOrNull(security, ssid, preSharedKey, hiddenSsid, networkId, isHotspot);
    }

    public static WifiNetworkConfig getValidConfigOrNull(String security, String ssid,
            String preSharedKey, boolean hiddenSsid, int networkId, boolean isHotspot) {
        if (!isValidConfig(security, ssid, preSharedKey, hiddenSsid)) {
            return null;
        }

        return new WifiNetworkConfig(security, ssid, preSharedKey, hiddenSsid, networkId,
                isHotspot);
    }

    public static boolean isValidConfig(WifiNetworkConfig config) {
        if (config == null) {
            return false;
        } else {
            return isValidConfig(config.mSecurity, config.mSsid, config.mPreSharedKey,
                    config.mHiddenSsid);
        }
    }

    public static boolean isValidConfig(String security, String ssid, String preSharedKey,
            boolean hiddenSsid) {
        if (!TextUtils.isEmpty(security) && !SECURITY_NO_PASSWORD.equals(security)) {
            if (TextUtils.isEmpty(preSharedKey)) {
                return false;
            }
        }

        if (!hiddenSsid && TextUtils.isEmpty(ssid)) {
            return false;
        }

        return true;
    }

    /**
     * Escaped special characters "\", ";", ":", "," with a backslash
     * See https://github.com/zxing/zxing/wiki/Barcode-Contents
     */
    private String escapeSpecialCharacters(String str) {
        if (TextUtils.isEmpty(str)) {
            return str;
        }

        StringBuilder buf = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (ch =='\\' || ch == ',' || ch == ';' || ch == ':') {
                buf.append('\\');
            }
            buf.append(ch);
        }

        return buf.toString();
    }

    /**
     * Construct a barcode string for WiFi network login.
     * See https://en.wikipedia.org/wiki/QR_code#WiFi_network_login
     */
    public String getQrCode() {
        final String empty = "";
        String barcode = new StringBuilder("WIFI:")
                .append("S:")
                .append(escapeSpecialCharacters(mSsid))
                .append(";")
                .append("T:")
                .append(TextUtils.isEmpty(mSecurity) ? empty : mSecurity)
                .append(";")
                .append("P:")
                .append(TextUtils.isEmpty(mPreSharedKey) ? empty
                        : escapeSpecialCharacters(mPreSharedKey))
                .append(";")
                .append("H:")
                .append(mHiddenSsid)
                .append(";;")
                .toString();
        return barcode;
    }

    public String getSecurity() {
        return mSecurity;
    }

    public String getSsid() {
        return mSsid;
    }

    public String getPreSharedKey() {
        return mPreSharedKey;
    }

    public boolean getHiddenSsid() {
        return mHiddenSsid;
    }

    public int getNetworkId() {
        return mNetworkId;
    }

    public boolean isHotspot() {
        return mIsHotspot;
    }

    public boolean isSupportWifiDpp(Context context) {
        if (!WifiDppUtils.isWifiDppEnabled(context)) {
            return false;
        }

        if (TextUtils.isEmpty(mSecurity)) {
            return false;
        }

        // DPP 1.0 only supports SAE and PSK.
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);
        switch (mSecurity) {
            case SECURITY_SAE:
                if (wifiManager.isWpa3SaeSupported()) {
                    return true;
                }
                break;
            case SECURITY_WPA_PSK:
                return true;
            default:
        }
        return false;
    }

    /**
     * This is a simplified method from {@code WifiConfigController.getConfig()}
     *
     * TODO (b/129021867): WifiConfiguration is a deprecated class, should replace it with
     *       {@code android.net.wifi.WifiNetworkSuggestion}
     *
     * @return When it's a open network, returns 2 WifiConfiguration in the List, the 1st is
     *         open network and the 2nd is enhanced open network. Returns 1 WifiConfiguration in the
     *         List for all other supported Wi-Fi securities.
     */
    List<WifiConfiguration> getWifiConfigurations() {
        final List<WifiConfiguration> wifiConfigurations = new ArrayList<>();

        if (!isValidConfig(this)) {
            return wifiConfigurations;
        }

        if (TextUtils.isEmpty(mSecurity) || SECURITY_NO_PASSWORD.equals(mSecurity)) {
            // TODO (b/129835824): we add both open network and enhanced open network to WifiManager
            //                     for android Q, should improve it in the future.
            final WifiConfiguration openNetworkWifiConfiguration = getBasicWifiConfiguration();
            openNetworkWifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE);
            wifiConfigurations.add(openNetworkWifiConfiguration);

            final WifiConfiguration enhancedOpenNetworkWifiConfiguration =
                    getBasicWifiConfiguration();
            enhancedOpenNetworkWifiConfiguration.allowedKeyManagement.set(KeyMgmt.OWE);
            enhancedOpenNetworkWifiConfiguration.requirePMF = true;
            wifiConfigurations.add(enhancedOpenNetworkWifiConfiguration);
            return wifiConfigurations;
        }

        final WifiConfiguration wifiConfiguration = getBasicWifiConfiguration();
        if (mSecurity.startsWith(SECURITY_WEP)) {
            wifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE);
            wifiConfiguration.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
            wifiConfiguration.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);

            // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
            final int length = mPreSharedKey.length();
            if ((length == 10 || length == 26 || length == 58)
                    && mPreSharedKey.matches("[0-9A-Fa-f]*")) {
                wifiConfiguration.wepKeys[0] = mPreSharedKey;
            } else {
                wifiConfiguration.wepKeys[0] = addQuotationIfNeeded(mPreSharedKey);
            }
        } else if (mSecurity.startsWith(SECURITY_WPA_PSK)) {
            wifiConfiguration.allowedKeyManagement.set(KeyMgmt.WPA_PSK);

            if (mPreSharedKey.matches("[0-9A-Fa-f]{64}")) {
                wifiConfiguration.preSharedKey = mPreSharedKey;
            } else {
                wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey);
            }
        } else if (mSecurity.startsWith(SECURITY_SAE)) {
            wifiConfiguration.allowedKeyManagement.set(KeyMgmt.SAE);
            wifiConfiguration.requirePMF = true;
            if (mPreSharedKey.length() != 0) {
                wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey);
            }
        } else {
            Log.w(TAG, "Unsupported security");
            return wifiConfigurations;
        }

        wifiConfigurations.add(wifiConfiguration);
        return wifiConfigurations;
    }

    private WifiConfiguration getBasicWifiConfiguration() {
        final WifiConfiguration wifiConfiguration = new WifiConfiguration();

        wifiConfiguration.SSID = addQuotationIfNeeded(mSsid);
        wifiConfiguration.hiddenSSID = mHiddenSsid;
        wifiConfiguration.networkId = mNetworkId;
        return wifiConfiguration;
    }

    private String addQuotationIfNeeded(String input) {
        if (TextUtils.isEmpty(input)) {
            return "";
        }

        if (input.length() >= 2 && input.startsWith("\"") && input.endsWith("\"")) {
            return input;
        }

        StringBuilder sb = new StringBuilder();
        sb.append("\"").append(input).append("\"");
        return sb.toString();
    }
}
