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

import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;

/**
 * @hide
 */
public final class PhysicalChannelConfig implements Parcelable {

    // TODO(b/72993578) consolidate these enums in a central location.
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({CONNECTION_PRIMARY_SERVING, CONNECTION_SECONDARY_SERVING, CONNECTION_UNKNOWN})
    public @interface ConnectionStatus {}

    /**
     * UE has connection to cell for signalling and possibly data (3GPP 36.331, 25.331).
     */
    public static final int CONNECTION_PRIMARY_SERVING = 1;

    /**
     * UE has connection to cell for data (3GPP 36.331, 25.331).
     */
    public static final int CONNECTION_SECONDARY_SERVING = 2;

    /** Connection status is unknown. */
    public static final int CONNECTION_UNKNOWN = Integer.MAX_VALUE;

    /**
     * Connection status of the cell.
     *
     * <p>One of {@link #CONNECTION_PRIMARY_SERVING}, {@link #CONNECTION_SECONDARY_SERVING}.
     */
    @ConnectionStatus
    private int mCellConnectionStatus;

    /**
     * Cell bandwidth, in kHz.
     */
    private int mCellBandwidthDownlinkKhz;

    /**
     * The radio technology for this physical channel.
     */
    @NetworkType
    private int mRat;

    /**
     * The rough frequency range for this physical channel.
     */
    @ServiceState.FrequencyRange
    private int mFrequencyRange;

    /**
     * The absolute radio frequency channel number, {@link Integer#MAX_VALUE} if unknown.
     */
    private int mChannelNumber;

    /**
     * A list of data calls mapped to this physical channel. An empty list means the physical
     * channel has no data call mapped to it.
     */
    private int[] mContextIds;

    /**
     * The physical cell identifier for this cell - PCI, PSC, {@link Integer#MAX_VALUE} if known.
     */
    private int mPhysicalCellId;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mCellConnectionStatus);
        dest.writeInt(mCellBandwidthDownlinkKhz);
        dest.writeInt(mRat);
        dest.writeInt(mChannelNumber);
        dest.writeInt(mFrequencyRange);
        dest.writeIntArray(mContextIds);
        dest.writeInt(mPhysicalCellId);
    }

    /**
     * @return Cell bandwidth, in kHz
     */
    public int getCellBandwidthDownlink() {
        return mCellBandwidthDownlinkKhz;
    }

    /**
     * Get the list of data call ids mapped to this physical channel. This list is sorted into
     * ascending numerical order. Each id in this list must match the id in
     * {@link com.android.internal.telephony.dataconnection.DataConnection}. An empty list means the
     * physical channel has no data call mapped to it.
     *
     * @return an integer list indicates the data call ids.
     */
    public int[] getContextIds() {
        return mContextIds;
    }

    /**
     * @return the rough frequency range for this physical channel.
     * @see {@link ServiceState#FREQUENCY_RANGE_LOW}
     * @see {@link ServiceState#FREQUENCY_RANGE_MID}
     * @see {@link ServiceState#FREQUENCY_RANGE_HIGH}
     * @see {@link ServiceState#FREQUENCY_RANGE_MMWAVE}
     */
    @ServiceState.FrequencyRange
    public int getFrequencyRange() {
        return mFrequencyRange;
    }

    /**
     * @return the absolute radio frequency channel number for this physical channel,
     * {@link Integer#MAX_VALUE} if unknown.
     */
    public int getChannelNumber() {
        return mChannelNumber;
    }

    /**
     * In UTRAN, this value is primary scrambling code. The range is [0, 511].
     * Reference: 3GPP TS 25.213 section 5.2.2.
     *
     * In EUTRAN, this value is physical layer cell identity. The range is [0, 503].
     * Reference: 3GPP TS 36.211 section 6.11.
     *
     * In 5G RAN, this value is physical layer cell identity. The range is [0, 1008].
     * Reference: 3GPP TS 38.211 section 7.4.2.1.
     *
     * @return the physical cell identifier for this cell, {@link Integer#MAX_VALUE} if unknown.
     */
    public int getPhysicalCellId() {
        return mPhysicalCellId;
    }

    /**The radio technology for this physical channel. */
    @NetworkType
    public int getRat() {
        return mRat;
    }

    /**
     * Gets the connection status of the cell.
     *
     * @see #CONNECTION_PRIMARY_SERVING
     * @see #CONNECTION_SECONDARY_SERVING
     * @see #CONNECTION_UNKNOWN
     *
     * @return Connection status of the cell
     */
    @ConnectionStatus
    public int getConnectionStatus() {
        return mCellConnectionStatus;
    }

    /** @return String representation of the connection status */
    private String getConnectionStatusString() {
        switch(mCellConnectionStatus) {
            case CONNECTION_PRIMARY_SERVING:
                return "PrimaryServing";
            case CONNECTION_SECONDARY_SERVING:
                return "SecondaryServing";
            case CONNECTION_UNKNOWN:
                return "Unknown";
            default:
                return "Invalid(" + mCellConnectionStatus + ")";
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof PhysicalChannelConfig)) {
            return false;
        }

        PhysicalChannelConfig config = (PhysicalChannelConfig) o;
        return mCellConnectionStatus == config.mCellConnectionStatus
                && mCellBandwidthDownlinkKhz == config.mCellBandwidthDownlinkKhz
                && mRat == config.mRat
                && mFrequencyRange == config.mFrequencyRange
                && mChannelNumber == config.mChannelNumber
                && mPhysicalCellId == config.mPhysicalCellId
                && Arrays.equals(mContextIds, config.mContextIds);
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                mCellConnectionStatus, mCellBandwidthDownlinkKhz, mRat, mFrequencyRange,
                mChannelNumber, mPhysicalCellId, mContextIds);
    }

    public static final @android.annotation.NonNull Parcelable.Creator<PhysicalChannelConfig> CREATOR =
        new Parcelable.Creator<PhysicalChannelConfig>() {
            public PhysicalChannelConfig createFromParcel(Parcel in) {
                return new PhysicalChannelConfig(in);
            }

            public PhysicalChannelConfig[] newArray(int size) {
                return new PhysicalChannelConfig[size];
            }
        };

    @Override
    public String toString() {
        return new StringBuilder()
                .append("{mConnectionStatus=")
                .append(getConnectionStatusString())
                .append(",mCellBandwidthDownlinkKhz=")
                .append(mCellBandwidthDownlinkKhz)
                .append(",mRat=")
                .append(TelephonyManager.getNetworkTypeName(mRat))
                .append(",mFrequencyRange=")
                .append(ServiceState.frequencyRangeToString(mFrequencyRange))
                .append(",mChannelNumber=")
                .append(mChannelNumber)
                .append(",mContextIds=")
                .append(Arrays.toString(mContextIds))
                .append(",mPhysicalCellId=")
                .append(mPhysicalCellId)
                .append("}")
                .toString();
    }

    private PhysicalChannelConfig(Parcel in) {
        mCellConnectionStatus = in.readInt();
        mCellBandwidthDownlinkKhz = in.readInt();
        mRat = in.readInt();
        mChannelNumber = in.readInt();
        mFrequencyRange = in.readInt();
        mContextIds = in.createIntArray();
        mPhysicalCellId = in.readInt();
    }

    private PhysicalChannelConfig(Builder builder) {
        mCellConnectionStatus = builder.mCellConnectionStatus;
        mCellBandwidthDownlinkKhz = builder.mCellBandwidthDownlinkKhz;
        mRat = builder.mRat;
        mChannelNumber = builder.mChannelNumber;
        mFrequencyRange = builder.mFrequencyRange;
        mContextIds = builder.mContextIds;
        mPhysicalCellId = builder.mPhysicalCellId;
    }

    /** The builder of {@code PhysicalChannelConfig}. */
    public static final class Builder {
        private int mRat;
        private int mFrequencyRange;
        private int mChannelNumber;
        private int mCellBandwidthDownlinkKhz;
        private int mCellConnectionStatus;
        private int[] mContextIds;
        private int mPhysicalCellId;

        /** @hide */
        public Builder() {
            mRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
            mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
            mChannelNumber = Integer.MAX_VALUE;
            mCellBandwidthDownlinkKhz = 0;
            mCellConnectionStatus = CONNECTION_UNKNOWN;
            mContextIds = new int[0];
            mPhysicalCellId = Integer.MAX_VALUE;
        }

        /** @hide */
        public PhysicalChannelConfig build() {
            return new PhysicalChannelConfig(this);
        }

        /** @hide */
        public Builder setRat(int rat) {
            this.mRat = rat;
            return this;
        }

        /** @hide */
        public Builder setFrequencyRange(int frequencyRange) {
            this.mFrequencyRange = frequencyRange;
            return this;
        }

        /** @hide */
        public Builder setChannelNumber(int channelNumber) {
            this.mChannelNumber = channelNumber;
            return this;
        }

        /** @hide */
        public Builder setCellBandwidthDownlinkKhz(int cellBandwidthDownlinkKhz) {
            this.mCellBandwidthDownlinkKhz = cellBandwidthDownlinkKhz;
            return this;
        }

        /** @hide */
        public Builder setCellConnectionStatus(int connectionStatus) {
            this.mCellConnectionStatus = connectionStatus;
            return this;
        }

        /** @hide */
        public Builder setContextIds(int[] contextIds) {
            if (contextIds != null) Arrays.sort(contextIds);
            this.mContextIds = contextIds;
            return this;
        }

        /** @hide */
        public Builder setPhysicalCellId(int physicalCellId) {
            this.mPhysicalCellId = physicalCellId;
            return this;
        }
    }
}
