/*
 * Copyright (C) 2016 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.server.wifi.hotspot2;

import android.util.Log;
import android.util.Pair;

import com.android.server.wifi.WifiInjector;
import com.android.server.wifi.hotspot2.anqp.ANQPElement;
import com.android.server.wifi.hotspot2.anqp.Constants;

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

/**
 * This class handles passpoint specific interactions with the AP, such as ANQP
 * elements requests, passpoint icon requests, and wireless network management
 * event notifications.
 */
public class PasspointEventHandler {
    private static final String TAG = "PasspointEventHandler";
    private final WifiInjector mWifiInjector;
    private final Callbacks mCallbacks;

    /**
     * Interface to be implemented by the client to receive callbacks for passpoint
     * related events.
     */
    public interface Callbacks {
        /**
         * Invoked on received of ANQP response. |anqpElements| will be null on failure.
         * @param bssid BSSID of the AP
         * @param anqpElements ANQP elements to be queried
         */
        void onANQPResponse(long bssid,
                            Map<Constants.ANQPElementType, ANQPElement> anqpElements);

        /**
         * Invoked on received of icon response. |filename| and |data| will be null
         * on failure.
         * @param bssid BSSID of the AP
         * @param filename Name of the icon file
         * @data icon data bytes
         */
        void onIconResponse(long bssid, String filename, byte[] data);

        /**
         * Invoked on received of Hotspot 2.0 Wireless Network Management frame.
         * @param data Wireless Network Management frame data
         */
        void onWnmFrameReceived(WnmData data);
    }

    public PasspointEventHandler(WifiInjector wifiInjector, Callbacks callbacks) {
        mWifiInjector = wifiInjector;
        mCallbacks = callbacks;
    }

    /**
     * Request the specified ANQP elements |elements| from the specified AP |bssid|.
     * @param bssid BSSID of the AP
     * @param elements ANQP elements to be queried
     * @return true if request is sent successfully, false otherwise.
     */
    public boolean requestANQP(long bssid, List<Constants.ANQPElementType> elements) {
        Pair<Set<Integer>, Set<Integer>> querySets = buildAnqpIdSet(elements);
        if (bssid == 0 || querySets == null) return false;
        if (!mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager().requestAnqp(
                Utils.macToString(bssid), querySets.first, querySets.second)) {
            Log.d(TAG, "ANQP failed on " + Utils.macToString(bssid));
            return false;
        }
        Log.d(TAG, "ANQP initiated on " + Utils.macToString(bssid));
        return true;
    }

    /**
     * Request the Venue URL ANQP element from the specified AP |bssid|.
     * @param bssid BSSID of the AP
     * @return true if request is sent successfully, false otherwise
     */
    public boolean requestVenueUrlAnqp(long bssid) {
        if (bssid == 0) return false;
        return mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager()
                .requestVenueUrlAnqp(Utils.macToString(bssid));
    }

    /**
     * Request a passpoint icon file |filename| from the specified AP |bssid|.
     * @param bssid BSSID of the AP
     * @param fileName name of the icon file
     * @return true if request is sent successfully, false otherwise
     */
    public boolean requestIcon(long bssid, String fileName) {
        if (bssid == 0 || fileName == null) return false;
        return mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager()
                .requestIcon(Utils.macToString(bssid), fileName);
    }

    /**
     * Invoked when ANQP query is completed.
     * TODO(zqiu): currently ANQP completion notification is through WifiMonitor,
     * this shouldn't be needed once we switch over to wificond for ANQP requests.
     * @param anqpEvent ANQP result data retrieved. ANQP elements could be empty in the event to
     *                  indicate any failures.
     */
    public void notifyANQPDone(AnqpEvent anqpEvent) {
        if (anqpEvent == null) return;
        mCallbacks.onANQPResponse(anqpEvent.getBssid(), anqpEvent.getElements());
    }

    /**
     * Invoked when icon query is completed.
     * TODO(zqiu): currently icon completion notification is through WifiMonitor,
     * this shouldn't be needed once we switch over to wificond for icon requests.
     * @param iconEvent icon event data
     */
    public void notifyIconDone(IconEvent iconEvent) {
        if (iconEvent == null) return;
        mCallbacks.onIconResponse(
                iconEvent.getBSSID(), iconEvent.getFileName(), iconEvent.getData());
    }

    /**
     * Invoked when a Wireless Network Management (WNM) frame is received.
     *
     * @param data WNM frame data
     */
    public void notifyWnmFrameReceived(WnmData data) {
        mCallbacks.onWnmFrameReceived(data);
    }

    /**
     * Create the set of ANQP ID's to query.
     *
     * @param querySet elements to query
     * @return Pair of <set of ANQP ID's, set of HS20 subtypes>
     */
    private static Pair<Set<Integer>, Set<Integer>> buildAnqpIdSet(
            List<Constants.ANQPElementType> querySet) {
        Set<Integer> anqpIds = new HashSet<>();
        Set<Integer> hs20Subtypes = new HashSet<>();
        for (Constants.ANQPElementType elementType : querySet) {
            Integer id = Constants.getANQPElementID(elementType);
            if (id != null) {
                anqpIds.add(id);
            } else {
                id = Constants.getHS20ElementID(elementType);
                hs20Subtypes.add(id);
            }
        }
        return Pair.create(anqpIds, hs20Subtypes);
    }

}
