/*
 * Copyright (C) 2010 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 android.os.Parcel;
import android.os.Parcelable;
import timber.log.Timber;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * A class representing the capabilities of a link
 *
 * @hide
 */
public class LinkCapabilities implements Parcelable {
    private static final boolean DBG = false;

    /**
     * The Map of Keys to Values
     */
    private HashMap<Integer, String> mCapabilities;


    /**
     * The set of keys defined for a links capabilities.
     * <p/>
     * Keys starting with RW are read + write, i.e. the application
     * can request for a certain requirement corresponding to that key.
     * Keys starting with RO are read only, i.e. the the application
     * can read the value of that key from the socket but cannot request
     * a corresponding requirement.
     * <p/>
     * TODO: Provide a documentation technique for concisely and precisely
     * define the syntax for each value string associated with a key.
     */
    public static final class Key {
        /**
         * No constructor
         */
        private Key() {
        }

        /**
         * An integer representing the network type.
         *
         * @see ConnectivityManager
         */
        public final static int RO_NETWORK_TYPE = 1;

        /**
         * Desired minimum forward link (download) bandwidth for the
         * in kilobits per second (kbps). Values should be strings such
         * "50", "100", "1500", etc.
         */
        public final static int RW_DESIRED_FWD_BW = 2;

        /**
         * Required minimum forward link (download) bandwidth, in
         * per second (kbps), below which the socket cannot function.
         * Values should be strings such as "50", "100", "1500", etc.
         */
        public final static int RW_REQUIRED_FWD_BW = 3;

        /**
         * Available forward link (download) bandwidth for the socket.
         * This value is in kilobits per second (kbps).
         * Values will be strings such as "50", "100", "1500", etc.
         */
        public final static int RO_AVAILABLE_FWD_BW = 4;

        /**
         * Desired minimum reverse link (upload) bandwidth for the socket
         * in kilobits per second (kbps).
         * Values should be strings such as "50", "100", "1500", etc.
         * <p/>
         * This key is set via the needs map.
         */
        public final static int RW_DESIRED_REV_BW = 5;

        /**
         * Required minimum reverse link (upload) bandwidth, in kilobits
         * per second (kbps), below which the socket cannot function.
         * If a rate is not specified, the default rate of kbps will be
         * Values should be strings such as "50", "100", "1500", etc.
         */
        public final static int RW_REQUIRED_REV_BW = 6;

        /**
         * Available reverse link (upload) bandwidth for the socket.
         * This value is in kilobits per second (kbps).
         * Values will be strings such as "50", "100", "1500", etc.
         */
        public final static int RO_AVAILABLE_REV_BW = 7;

        /**
         * Maximum latency for the socket, in milliseconds, above which
         * socket cannot function.
         * Values should be strings such as "50", "300", "500", etc.
         */
        public final static int RW_MAX_ALLOWED_LATENCY = 8;

        /**
         * Interface that the socket is bound to. This can be a virtual
         * interface (e.g. VPN or Mobile IP) or a physical interface
         * (e.g. wlan0 or rmnet0).
         * Values will be strings such as "wlan0", "rmnet0"
         */
        public final static int RO_BOUND_INTERFACE = 9;

        /**
         * Physical interface that the socket is routed on.
         * This can be different from BOUND_INTERFACE in cases such as
         * VPN or Mobile IP. The physical interface may change over time
         * if seamless mobility is supported.
         * Values will be strings such as "wlan0", "rmnet0"
         */
        public final static int RO_PHYSICAL_INTERFACE = 10;
    }

    /**
     * Role informs the LinkSocket about the data usage patterns of your
     * application.
     * <p/>
     * {@code Role.DEFAULT} is the default role, and is used whenever
     * a role isn't set.
     */
    public static final class Role {
        /**
         * No constructor
         */
        private Role() {
        }

        // examples only, discuss which roles should be defined, and then
        // code these to match

        /**
         * Default Role
         */
        public static final String DEFAULT = "default";
        /**
         * Bulk down load
         */
        public static final String BULK_DOWNLOAD = "bulk.download";
        /**
         * Bulk upload
         */
        public static final String BULK_UPLOAD = "bulk.upload";

        /**
         * VoIP Application at 24kbps
         */
        public static final String VOIP_24KBPS = "voip.24k";
        /**
         * VoIP Application at 32kbps
         */
        public static final String VOIP_32KBPS = "voip.32k";

        /**
         * Video Streaming at 480p
         */
        public static final String VIDEO_STREAMING_480P = "video.streaming.480p";
        /**
         * Video Streaming at 720p
         */
        public static final String VIDEO_STREAMING_720I = "video.streaming.720i";

        /**
         * Video Chat Application at 360p
         */
        public static final String VIDEO_CHAT_360P = "video.chat.360p";
        /**
         * Video Chat Application at 480p
         */
        public static final String VIDEO_CHAT_480P = "video.chat.480i";
    }

    /**
     * Constructor
     */
    public LinkCapabilities() {
        mCapabilities = new HashMap<Integer, String>();
    }

    /**
     * Copy constructor.
     *
     * @param source
     */
    public LinkCapabilities(LinkCapabilities source) {
        if (source != null) {
            mCapabilities = new HashMap<Integer, String>(source.mCapabilities);
        } else {
            mCapabilities = new HashMap<Integer, String>();
        }
    }

    /**
     * Create the {@code LinkCapabilities} with values depending on role type.
     *
     * @param applicationRole a {@code LinkSocket.Role}
     * @return the {@code LinkCapabilities} associated with the applicationRole, empty if none
     */
    public static LinkCapabilities createNeedsMap(String applicationRole) {
        if (DBG) log("createNeededCapabilities(applicationRole) EX");
        return new LinkCapabilities();
    }

    /**
     * Remove all capabilities
     */
    public void clear() {
        mCapabilities.clear();
    }

    /**
     * Returns whether this map is empty.
     */
    public boolean isEmpty() {
        return mCapabilities.isEmpty();
    }

    /**
     * Returns the number of elements in this map.
     *
     * @return the number of elements in this map.
     */
    public int size() {
        return mCapabilities.size();
    }

    /**
     * Given the key return the capability string
     *
     * @param key
     * @return the capability string
     */
    public String get(int key) {
        return mCapabilities.get(key);
    }

    /**
     * Store the key/value capability pair
     *
     * @param key
     * @param value
     */
    public void put(int key, String value) {
        mCapabilities.put(key, value);
    }

    /**
     * Returns whether this map contains the specified key.
     *
     * @param key to search for.
     * @return {@code true} if this map contains the specified key,
     * {@code false} otherwise.
     */
    public boolean containsKey(int key) {
        return mCapabilities.containsKey(key);
    }

    /**
     * Returns whether this map contains the specified value.
     *
     * @param value to search for.
     * @return {@code true} if this map contains the specified value,
     * {@code false} otherwise.
     */
    public boolean containsValue(String value) {
        return mCapabilities.containsValue(value);
    }

    /**
     * Returns a set containing all of the mappings in this map. Each mapping is
     * an instance of {@link Map.Entry}. As the set is backed by this map,
     * changes in one will be reflected in the other.
     *
     * @return a set of the mappings.
     */
    public Set<Entry<Integer, String>> entrySet() {
        return mCapabilities.entrySet();
    }

    /**
     * @return the set of the keys.
     */
    public Set<Integer> keySet() {
        return mCapabilities.keySet();
    }

    /**
     * @return the set of values
     */
    public Collection<String> values() {
        return mCapabilities.values();
    }

    /**
     * Implement the Parcelable interface
     *
     * @hide
     */
    public int describeContents() {
        return 0;
    }

    /**
     * Convert to string for debugging
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        boolean firstTime = true;
        for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
            if (firstTime) {
                firstTime = false;
            } else {
                sb.append(",");
            }
            sb.append(entry.getKey());
            sb.append(":\"");
            sb.append(entry.getValue());
            sb.append("\"");
            return mCapabilities.toString();
        }
        return sb.toString();
    }

    /**
     * Implement the Parcelable interface.
     *
     * @hide
     */
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mCapabilities.size());
        for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
            dest.writeInt(entry.getKey().intValue());
            dest.writeString(entry.getValue());
        }
    }

    /**
     * Implement the Parcelable interface.
     *
     * @hide
     */
    public static final Creator<LinkCapabilities> CREATOR =
            new Creator<LinkCapabilities>() {
                public LinkCapabilities createFromParcel(Parcel in) {
                    LinkCapabilities capabilities = new LinkCapabilities();
                    int size = in.readInt();
                    while (size-- != 0) {
                        int key = in.readInt();
                        String value = in.readString();
                        capabilities.mCapabilities.put(key, value);
                    }
                    return capabilities;
                }

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

    /**
     * Debug logging
     */
    protected static void log(String s) {
        Timber.d(s);
    }
}
