ActionParameters.kt

/*
 * Copyright 2021 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 androidx.glance.action

import androidx.datastore.preferences.core.Preferences
import java.util.Collections

/**
 * Action parameters, used to pass information to an [Action].
 *
 * Construct action parameters using [actionParametersOf] or [mutableActionParametersOf], with typed
 * key-value pairs. The [Key] class enforces typing of the values inserted.
 */
abstract class ActionParameters internal constructor() {

    /**
     * Key for [ActionParameters] values. Type T is the type of the associated value. The [Key.name]
     * must be unique, keys with the same name are considered equal.
     */
    class Key<T : Any> (val name: String) {
        /**
         * Infix function to create a Parameters.Pair.
         *
         * @param value the value this key should point to
         */
        infix fun to(value: T): Pair<T> = Pair(this, value)

        override fun equals(other: Any?): Boolean = other is Key<*> && name == other.name

        override fun hashCode(): Int = name.hashCode()

        override fun toString(): String = name
    }

    /**
     * Key Value pairs for Parameters. Type T is the type of the value. Pairs must have unique keys,
     * or they will be considered equal.
     *
     * Create this using the infix function [to].
     */
    class Pair<T : Any> internal constructor(
        internal val key: Key<T>,
        internal val value: T
    ) {
        override fun equals(other: Any?): Boolean =
            other is Pair<*> && key == other.key && value == other.value

        override fun hashCode(): Int = key.hashCode() + value.hashCode()

        override fun toString(): String = "(${key.name}, $value)"
    }

    /**
     * Returns true if the Parameters set contains the given Key.
     *
     * @param key the key to check for
     */
    abstract operator fun <T : Any> contains(key: Key<T>): Boolean

    /**
     * Get a parameter with a key. If the key is not set, returns null.
     *
     * @param T the type of the parameter
     * @param key the key for the parameter
     * @throws ClassCastException if there is something stored with the same name as [key] but it
     * cannot be cast to T
     */
    abstract operator fun <T : Any> get(key: Key<T>): T?

    /**
     * Get a parameter with a key. If the key is not set, returns the provided default value.
     *
     * @param T the type of the parameter
     * @param key the key for the parameter
     * @param defaultValue the default value to return if key is missing
     * @throws ClassCastException if there is something stored with the same name as [key] but it
     * cannot be cast to T
     */
    abstract fun <T : Any> getOrDefault(key: Key<T>, defaultValue: @UnsafeVariance T): T

    /**
     * Retrieves a map of all key value pairs. The map is unmodifiable, and attempts to mutate it
     * will throw runtime exceptions.
     *
     * @return a map of all parameters in this Parameters
     */
    abstract fun asMap(): Map<Key<out Any>, Any>

    /**
     * Returns whether there are any keys stored in the parameters.
     */
    abstract fun isEmpty(): Boolean
}

/**
 * Mutable version of [ActionParameters]. Allows for editing the underlying data, and adding or
 * removing key-value pairs.
 */
class MutableActionParameters internal constructor(
    internal val map: MutableMap<Key<out Any>, Any> = mutableMapOf()
) : ActionParameters() {

    override operator fun <T : Any> contains(key: Key<T>): Boolean = map.containsKey(key)

    @Suppress("UNCHECKED_CAST")
    override operator fun <T : Any> get(key: Key<T>): T? = map[key] as T?

    override fun <T : Any> getOrDefault(key: Key<T>, defaultValue: T): T = get(key) ?: defaultValue

    override fun asMap(): Map<Key<out Any>, Any> = Collections.unmodifiableMap(map)

    /**
     * Sets a key value pair in MutableParameters. If the value is null, the key is removed from the
     * parameters.
     *
     * @param key the parameter to set
     * @param value the value to assign to this parameter
     * @return the previous value associated with the key, or null if the key was not present
     */
    operator fun <T : Any> set(key: Key<T>, value: T?): T? {
        val mapValue = get(key)
        when (value) {
            null -> remove(key)
            else -> map[key] = value
        }
        return mapValue
    }

    /**
     * Removes an item from this MutableParameters.
     *
     * @param key the parameter to remove
     * @return the original value of the parameter
     */
    @Suppress("UNCHECKED_CAST")
    fun <T : Any> remove(key: Key<T>) = map.remove(key) as T?

    /**
     * Removes all parameters from this MutableParameters.
     */
    fun clear() = map.clear()

    override fun equals(other: Any?): Boolean = other is MutableActionParameters && map == other.map

    override fun hashCode(): Int = map.hashCode()

    override fun toString(): String = map.toString()

    override fun isEmpty(): Boolean = map.isEmpty()
}

/**
 * Returns a new read-only Parameters, from the specified contents. The key element in the given
 * pairs will point to the corresponding value element.
 *
 * If multiple pairs have the same key, the resulting map will contain only the value from the last
 * of those pairs.
 */
fun actionParametersOf(vararg pairs: ActionParameters.Pair<out Any>): ActionParameters =
    mutableActionParametersOf(*pairs)

/**
 * Returns a new MutableParameters, from the specified contents. The key element in the given pairs
 * will point to the corresponding value element.
 *
 * If multiple pairs have the same key, the resulting Parameters will contain only the value from
 * the last of those pairs.
 */
fun mutableActionParametersOf(
    vararg pairs: ActionParameters.Pair<out Any>
): MutableActionParameters = MutableActionParameters(
    mutableMapOf(*pairs.map { it.key to it.value }.toTypedArray())
)

/**
 * Gets a mutable copy of Parameters, containing all key value pairs. This can be used to edit
 * the parameter data without creating a new object.
 *
 * This is similar to [Map.toMutableMap].
 *
 * @return a MutableParameters with all the same parameters as this Parameters
 */
fun ActionParameters.toMutableParameters(): MutableActionParameters =
    MutableActionParameters(asMap().toMutableMap())

/**
 * Gets a read-only copy of Parameters, containing all key value pairs.
 *
 * This is similar to [Map.toMap].
 *
 * @return a copy of this Parameters
 */
fun ActionParameters.toParameters(): ActionParameters = toMutableParameters()

/** Creates an action key from a preferences key. */
fun <T : Any> Preferences.Key<T>.toParametersKey(): ActionParameters.Key<T> =
    ActionParameters.Key(name)