/*
 * Copyright 2023 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.bluetooth;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * This class contains the broadcast group settings information for this Broadcast Group.
 *
 * @hide
 */
@SystemApi
public final class BluetoothLeBroadcastSettings implements Parcelable {
    private final boolean mIsPublicBroadcast;
    private final String mBroadcastName;
    private final byte[] mBroadcastCode;
    private final BluetoothLeAudioContentMetadata mPublicBroadcastMetadata;
    private final List<BluetoothLeBroadcastSubgroupSettings> mSubgroupSettings;

    private BluetoothLeBroadcastSettings(
            boolean isPublicBroadcast,
            String broadcastName,
            byte[] broadcastCode,
            BluetoothLeAudioContentMetadata publicBroadcastMetadata,
            List<BluetoothLeBroadcastSubgroupSettings> subgroupSettings) {
        mIsPublicBroadcast = isPublicBroadcast;
        mBroadcastName = broadcastName;
        mBroadcastCode = broadcastCode;
        mPublicBroadcastMetadata = publicBroadcastMetadata;
        mSubgroupSettings = subgroupSettings;
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (!(o instanceof BluetoothLeBroadcastSettings)) {
            return false;
        }
        final BluetoothLeBroadcastSettings other = (BluetoothLeBroadcastSettings) o;
        return mIsPublicBroadcast == other.isPublicBroadcast()
                && Objects.equals(mBroadcastName, other.getBroadcastName())
                && Arrays.equals(mBroadcastCode, other.getBroadcastCode())
                && Objects.equals(mPublicBroadcastMetadata, other.getPublicBroadcastMetadata())
                && mSubgroupSettings.equals(other.getSubgroupSettings());
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                mIsPublicBroadcast,
                mBroadcastName,
                Arrays.hashCode(mBroadcastCode),
                mPublicBroadcastMetadata,
                mSubgroupSettings);
    }

    /**
     * Return {@code true} if this Broadcast Group is set to broadcast Public Broadcast Announcement
     * otherwise return {@code false}.
     *
     * @hide
     */
    @SystemApi
    public boolean isPublicBroadcast() {
        return mIsPublicBroadcast;
    }

    /**
     * Return the broadcast code for this Broadcast Group.
     *
     * @return Broadcast name for this Broadcast Group, null if no name provided
     * @hide
     */
    @SystemApi
    @Nullable
    public String getBroadcastName() {
        return mBroadcastName;
    }

    /**
     * Get the Broadcast Code currently set for this broadcast group.
     *
     * <p>Only needed when encryption is enabled
     *
     * <p>As defined in Volume 3, Part C, Section 3.2.6 of Bluetooth Core Specification, Version
     * 5.3, Broadcast Code is used to encrypt a broadcast audio stream.
     *
     * <p>It must be a UTF-8 string that has at least 4 octets and should not exceed 16 octets.
     *
     * @return Broadcast Code currently set for this broadcast group, null if code is not required
     *     or code is currently unknown
     * @hide
     */
    @SystemApi
    @Nullable
    public byte[] getBroadcastCode() {
        return mBroadcastCode;
    }

    /**
     * Get public broadcast metadata for this Broadcast Group.
     *
     * @return public broadcast metadata for this Broadcast Group,
     * null if no public metadata exists
     * @hide
     */
    @SystemApi
    @Nullable
    public BluetoothLeAudioContentMetadata getPublicBroadcastMetadata() {
        return mPublicBroadcastMetadata;
    }

    /**
     * Get available subgroup settings in the broadcast group.
     *
     * @return list of subgroup settings in the broadcast group
     * @hide
     */
    @SystemApi
    @NonNull
    public List<BluetoothLeBroadcastSubgroupSettings> getSubgroupSettings() {
        return mSubgroupSettings;
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeBoolean(mIsPublicBroadcast);
        out.writeString(mBroadcastName);
        if (mBroadcastCode != null) {
            out.writeInt(mBroadcastCode.length);
            out.writeByteArray(mBroadcastCode);
        } else {
            // -1 indicates missing broadcast code
            out.writeInt(-1);
        }
        out.writeTypedObject(mPublicBroadcastMetadata, 0);
        out.writeTypedList(mSubgroupSettings);
    }

    /**
     * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastSettings} from parcel.
     *
     * @hide
     */
    @SystemApi
    @NonNull
    public static final Creator<BluetoothLeBroadcastSettings> CREATOR =
            new Creator<>() {
                public @NonNull BluetoothLeBroadcastSettings createFromParcel(@NonNull Parcel in) {
                    Builder builder = new Builder();
                    builder.setPublicBroadcast(in.readBoolean());
                    builder.setBroadcastName(in.readString());
                    final int codeLen = in.readInt();
                    byte[] broadcastCode = null;
                    if (codeLen != -1) {
                        broadcastCode = new byte[codeLen];
                        if (codeLen > 0) {
                            in.readByteArray(broadcastCode);
                        }
                    }
                    builder.setBroadcastCode(broadcastCode);
                    builder.setPublicBroadcastMetadata(
                            in.readTypedObject(BluetoothLeAudioContentMetadata.CREATOR));
                    final List<BluetoothLeBroadcastSubgroupSettings> subgroupSettings =
                            new ArrayList<>();
                    in.readTypedList(
                            subgroupSettings, BluetoothLeBroadcastSubgroupSettings.CREATOR);
                    for (BluetoothLeBroadcastSubgroupSettings setting : subgroupSettings) {
                        builder.addSubgroupSettings(setting);
                    }
                    return builder.build();
                }

                public @NonNull BluetoothLeBroadcastSettings[] newArray(int size) {
                    return new BluetoothLeBroadcastSettings[size];
                }
            };

    /**
     * Builder for {@link BluetoothLeBroadcastSettings}.
     *
     * @hide
     */
    @SystemApi
    public static final class Builder {
        private boolean mIsPublicBroadcast = false;
        private String mBroadcastName = null;
        private byte[] mBroadcastCode = null;
        private BluetoothLeAudioContentMetadata mPublicBroadcastMetadata = null;
        private List<BluetoothLeBroadcastSubgroupSettings> mSubgroupSettings = new ArrayList<>();
        /**
         * Create an empty builder.
         *
         * @hide
         */
        @SystemApi
        public Builder() {}

        /**
         * Create a builder with copies of information from original object.
         *
         * @param original original object
         * @hide
         */
        @SystemApi
        public Builder(@NonNull BluetoothLeBroadcastSettings original) {
            mIsPublicBroadcast = original.isPublicBroadcast();
            mBroadcastName = original.getBroadcastName();
            mBroadcastCode = original.getBroadcastCode();
            mPublicBroadcastMetadata = original.getPublicBroadcastMetadata();
            mSubgroupSettings = original.getSubgroupSettings();
        }

        /**
         * Set whether the Public Broadcast is on for this broadcast group.
         *
         * @param isPublicBroadcast whether the Public Broadcast is enabled
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setPublicBroadcast(boolean isPublicBroadcast) {
            mIsPublicBroadcast = isPublicBroadcast;
            return this;
        }

        /**
         * Set broadcast name for the broadcast group.
         *
         * @param broadcastName Broadcast name for this broadcast group, null if no name provided
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setBroadcastName(@Nullable String broadcastName) {
            mBroadcastName = broadcastName;
            return this;
        }

        /**
         * Set the Broadcast Code currently set for this broadcast group.
         *
         * <p>Only needed when encryption is enabled
         *
         * <p>As defined in Volume 3, Part C, Section 3.2.6 of Bluetooth Core Specification, Version
         * 5.3, Broadcast Code is used to encrypt a broadcast audio stream.
         *
         * <p>It must be a UTF-8 string that has at least 4 octets and should not exceed 16 octets.
         *
         * @param broadcastCode Broadcast Code for this broadcast group, null if code is not
         *     required
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setBroadcastCode(@Nullable byte[] broadcastCode) {
            mBroadcastCode = broadcastCode;
            return this;
        }

        /**
         * Set public broadcast metadata for this Broadcast Group.
         * PBS should include the Program_Info length-type-value (LTV) structure metadata
         *
         * @param  publicBroadcastMetadata public broadcast metadata for this Broadcast Group,
                                           null if no public meta data provided
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setPublicBroadcastMetadata(
                @Nullable BluetoothLeAudioContentMetadata publicBroadcastMetadata) {
            mPublicBroadcastMetadata = publicBroadcastMetadata;
            return this;
        }

        /**
         * Add a subgroup settings to the broadcast group.
         *
         * @param subgroupSettings contains subgroup's setting data
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder addSubgroupSettings(
                @NonNull BluetoothLeBroadcastSubgroupSettings subgroupSettings) {
            Objects.requireNonNull(subgroupSettings, "subgroupSettings cannot be null");
            mSubgroupSettings.add(subgroupSettings);
            return this;
        }

        /**
         * Clear subgroup settings list so that one can reset the builder
         *
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder clearSubgroupSettings() {
            mSubgroupSettings.clear();
            return this;
        }

        /**
         * Build {@link BluetoothLeBroadcastSettings}.
         *
         * @return {@link BluetoothLeBroadcastSettings}
         * @throws IllegalArgumentException if the object cannot be built
         * @hide
         */
        @SystemApi
        @NonNull
        public BluetoothLeBroadcastSettings build() {
            if (mSubgroupSettings.isEmpty()) {
                throw new IllegalArgumentException("Must contain at least one subgroup");
            }
            return new BluetoothLeBroadcastSettings(
                    mIsPublicBroadcast, mBroadcastName, mBroadcastCode,
                    mPublicBroadcastMetadata, mSubgroupSettings);
        }
    }
}
