/*
 * 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.anqp;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wifi.ByteBufferReader;

import java.net.ProtocolException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * The WAN Metrics vendor specific ANQP Element,
 * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
 * section 4.4
 *
 * Format:
 * | WAN Info | Downlink Speed | Uplink Speed | Downlink Load | Uplink Load | LMD |
 *      1             4               4               1              1         2
 *
 * WAN Info Format:
 * | Link Status | Symmetric Link | At Capacity | Reserved |
 *      B0 B1            B2             B3        B4 - B7
 */
public class HSWanMetricsElement extends ANQPElement {
    public static final int LINK_STATUS_RESERVED = 0;
    public static final int LINK_STATUS_UP = 1;
    public static final int LINK_STATUS_DOWN = 2;
    public static final int LINK_STATUS_TEST = 3;

    @VisibleForTesting
    public static final int EXPECTED_BUFFER_SIZE = 13;

    @VisibleForTesting
    public static final int LINK_STATUS_MASK = (1 << 0 | 1 << 1);

    @VisibleForTesting
    public static final int SYMMETRIC_LINK_MASK = 1 << 2;

    @VisibleForTesting
    public static final int AT_CAPACITY_MASK = 1 << 3;

    private static final int MAX_LOAD = 256;

    private final int mStatus;
    private final boolean mSymmetric;
    private final boolean mAtCapacity;
    private final long mDownlinkSpeed;
    private final long mUplinkSpeed;
    private final int mDownlinkLoad;
    private final int mUplinkLoad;
    private final int mLMD;     // Load Measurement Duration.

    @VisibleForTesting
    public HSWanMetricsElement(int status, boolean symmetric, boolean atCapacity,
            long downlinkSpeed, long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) {
        super(Constants.ANQPElementType.HSWANMetrics);
        mStatus = status;
        mSymmetric = symmetric;
        mAtCapacity = atCapacity;
        mDownlinkSpeed = downlinkSpeed;
        mUplinkSpeed = uplinkSpeed;
        mDownlinkLoad = downlinkLoad;
        mUplinkLoad = uplinkLoad;
        mLMD = lmd;
    }

    /**
     * Parse a HSWanMetricsElement from the given buffer.
     *
     * @param payload The byte buffer to read from
     * @return {@link HSWanMetricsElement}
     * @throws ProtocolException
     */
    public static HSWanMetricsElement parse(ByteBuffer payload)
            throws ProtocolException {
        if (payload.remaining() != EXPECTED_BUFFER_SIZE) {
            throw new ProtocolException("Unexpected buffer size: " + payload.remaining());
        }

        int wanInfo = payload.get() & 0xFF;
        int status = wanInfo & LINK_STATUS_MASK;
        boolean symmetric = (wanInfo & SYMMETRIC_LINK_MASK) != 0;
        boolean capped = (wanInfo & AT_CAPACITY_MASK) != 0;
        long downlinkSpeed = ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 4)
                & 0xFFFFFFFFL;
        long uplinkSpeed = ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 4)
                & 0xFFFFFFFFL;
        int downlinkLoad = payload.get() & 0xFF;
        int uplinkLoad = payload.get() & 0xFF;
        int lmd = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
        return new HSWanMetricsElement(status, symmetric, capped, downlinkSpeed, uplinkSpeed,
                downlinkLoad, uplinkLoad, lmd);
    }

    public int getStatus() {
        return mStatus;
    }

    public boolean isSymmetric() {
        return mSymmetric;
    }

    public boolean isAtCapacity() {
        return mAtCapacity;
    }

    public long getDownlinkSpeed() {
        return mDownlinkSpeed;
    }

    public long getUplinkSpeed() {
        return mUplinkSpeed;
    }

    public int getDownlinkLoad() {
        return mDownlinkLoad;
    }

    public int getUplinkLoad() {
        return mUplinkLoad;
    }

    public int getLMD() {
        return mLMD;
    }

    /**
     * Check if the WAN Metrics ANQP-element contains values other than all 0's
     *
     * @return true if element contains non-0 values, false otherwise
     */
    public boolean isElementInitialized() {
        // Check if the WAN Metrics ANQP element is initialized with values other than 0's
        if (mStatus == LINK_STATUS_RESERVED && !mAtCapacity && !mSymmetric && mDownlinkLoad == 0
                && mDownlinkSpeed == 0 && mUplinkLoad == 0 && mUplinkSpeed == 0 && mLMD == 0) {
            // WAN Metrics ANQP element is not initialized in this network. Ignore it.
            return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object thatObject) {
        if (this == thatObject) {
            return true;
        }
        if (!(thatObject instanceof HSWanMetricsElement)) {
            return false;
        }
        HSWanMetricsElement that = (HSWanMetricsElement) thatObject;
        return mStatus == that.mStatus
                && mSymmetric == that.mSymmetric
                && mAtCapacity == that.mAtCapacity
                && mDownlinkSpeed == that.mDownlinkSpeed
                && mUplinkSpeed == that.mUplinkSpeed
                && mDownlinkLoad == that.mDownlinkLoad
                && mUplinkLoad == that.mUplinkLoad
                && mLMD == that.mLMD;
    }

    @Override
    public int hashCode() {
        return (int) (mStatus + mDownlinkSpeed + mUplinkSpeed + mDownlinkLoad
                + mUplinkLoad + mLMD);
    }

    @Override
    public String toString() {
        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mAtCapacity=%s, "
                        + "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
                mStatus, mSymmetric, mAtCapacity,
                mDownlinkSpeed, mUplinkSpeed,
                mDownlinkLoad * 100.0 / MAX_LOAD,
                mUplinkLoad * 100.0 / MAX_LOAD,
                mLMD);
    }
}
