/*
 * Copyright (C) 2011 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 android.net;

import static android.net.ConnectivityManager.TYPE_WIFI;

import android.annotation.Nullable;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.service.NetworkIdentityProto;
import android.telephony.Annotation.NetworkType;
import android.util.proto.ProtoOutputStream;

import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.net.module.util.NetworkIdentityUtils;

import java.util.ArrayList;
import java.util.Objects;

/**
 * Network definition that includes strong identity. Analogous to combining
 * {@link NetworkCapabilities} and an IMSI.
 *
 * @hide
 */
public class NetworkIdentity implements Comparable<NetworkIdentity> {
    private static final String TAG = "NetworkIdentity";

    public static final int SUBTYPE_COMBINED = -1;

    /**
     * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}.
     * @hide
     */
    public static final int OEM_NONE = 0x0;
    /**
     * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
     * @hide
     */
    public static final int OEM_PAID = 0x1;
    /**
     * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
     * @hide
     */
    public static final int OEM_PRIVATE = 0x2;

    final int mType;
    final int mSubType;
    final String mSubscriberId;
    final String mNetworkId;
    final boolean mRoaming;
    final boolean mMetered;
    final boolean mDefaultNetwork;
    final int mOemManaged;

    public NetworkIdentity(
            int type, int subType, String subscriberId, String networkId, boolean roaming,
            boolean metered, boolean defaultNetwork, int oemManaged) {
        mType = type;
        mSubType = subType;
        mSubscriberId = subscriberId;
        mNetworkId = networkId;
        mRoaming = roaming;
        mMetered = metered;
        mDefaultNetwork = defaultNetwork;
        mOemManaged = oemManaged;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
                mDefaultNetwork, mOemManaged);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (obj instanceof NetworkIdentity) {
            final NetworkIdentity ident = (NetworkIdentity) obj;
            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
                    && Objects.equals(mSubscriberId, ident.mSubscriberId)
                    && Objects.equals(mNetworkId, ident.mNetworkId)
                    && mMetered == ident.mMetered
                    && mDefaultNetwork == ident.mDefaultNetwork
                    && mOemManaged == ident.mOemManaged;
        }
        return false;
    }

    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder("{");
        builder.append("type=").append(mType);
        builder.append(", subType=");
        if (mSubType == SUBTYPE_COMBINED) {
            builder.append("COMBINED");
        } else {
            builder.append(mSubType);
        }
        if (mSubscriberId != null) {
            builder.append(", subscriberId=")
                    .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
        }
        if (mNetworkId != null) {
            builder.append(", networkId=").append(mNetworkId);
        }
        if (mRoaming) {
            builder.append(", ROAMING");
        }
        builder.append(", metered=").append(mMetered);
        builder.append(", defaultNetwork=").append(mDefaultNetwork);
        builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
        return builder.append("}").toString();
    }

    /**
     * Get the human readable representation of a bitfield representing the OEM managed state of a
     * network.
     */
    static String getOemManagedNames(int oemManaged) {
        if (oemManaged == OEM_NONE) {
            return "OEM_NONE";
        }
        final int[] bitPositions = NetworkCapabilitiesUtils.unpackBits(oemManaged);
        final ArrayList<String> oemManagedNames = new ArrayList<String>();
        for (int position : bitPositions) {
            oemManagedNames.add(nameOfOemManaged(1 << position));
        }
        return String.join(",", oemManagedNames);
    }

    private static String nameOfOemManaged(int oemManagedBit) {
        switch (oemManagedBit) {
            case OEM_PAID:
                return "OEM_PAID";
            case OEM_PRIVATE:
                return "OEM_PRIVATE";
            default:
                return "Invalid(" + oemManagedBit + ")";
        }
    }

    public void dumpDebug(ProtoOutputStream proto, long tag) {
        final long start = proto.start(tag);

        proto.write(NetworkIdentityProto.TYPE, mType);

        // Not dumping mSubType, subtypes are no longer supported.

        if (mSubscriberId != null) {
            proto.write(NetworkIdentityProto.SUBSCRIBER_ID,
                    NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
        }
        proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
        proto.write(NetworkIdentityProto.ROAMING, mRoaming);
        proto.write(NetworkIdentityProto.METERED, mMetered);
        proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
        proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged);

        proto.end(start);
    }

    public int getType() {
        return mType;
    }

    public int getSubType() {
        return mSubType;
    }

    public String getSubscriberId() {
        return mSubscriberId;
    }

    public String getNetworkId() {
        return mNetworkId;
    }

    public boolean getRoaming() {
        return mRoaming;
    }

    public boolean getMetered() {
        return mMetered;
    }

    public boolean getDefaultNetwork() {
        return mDefaultNetwork;
    }

    public int getOemManaged() {
        return mOemManaged;
    }

    /**
     * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and
     * {@code subType}, assuming that any mobile networks are using the current IMSI.
     * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_*
     * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not.
     */
    public static NetworkIdentity buildNetworkIdentity(Context context,
            NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) {
        final int legacyType = snapshot.getLegacyType();

        final String subscriberId = snapshot.getSubscriberId();
        String networkId = null;
        boolean roaming = !snapshot.getNetworkCapabilities().hasCapability(
                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
        boolean metered = !(snapshot.getNetworkCapabilities().hasCapability(
                NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
                || snapshot.getNetworkCapabilities().hasCapability(
                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED));

        final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());

        if (legacyType == TYPE_WIFI) {
            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
                    .getTransportInfo();
            if (transportInfo instanceof WifiInfo) {
                final WifiInfo info = (WifiInfo) transportInfo;
                networkId = info != null ? info.getCurrentNetworkKey() : null;
            }
        }

        return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered,
                defaultNetwork, oemManaged);
    }

    /**
     * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}.
     * @hide
     */
    public static int getOemBitfield(NetworkCapabilities nc) {
        int oemManaged = OEM_NONE;

        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
            oemManaged |= OEM_PAID;
        }
        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
            oemManaged |= OEM_PRIVATE;
        }

        return oemManaged;
    }

    @Override
    public int compareTo(NetworkIdentity another) {
        int res = Integer.compare(mType, another.mType);
        if (res == 0) {
            res = Integer.compare(mSubType, another.mSubType);
        }
        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
            res = mSubscriberId.compareTo(another.mSubscriberId);
        }
        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
            res = mNetworkId.compareTo(another.mNetworkId);
        }
        if (res == 0) {
            res = Boolean.compare(mRoaming, another.mRoaming);
        }
        if (res == 0) {
            res = Boolean.compare(mMetered, another.mMetered);
        }
        if (res == 0) {
            res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
        }
        if (res == 0) {
            res = Integer.compare(mOemManaged, another.mOemManaged);
        }
        return res;
    }
}
