Option.kt

package com.hexagontk.shell

import com.hexagontk.core.text.parseOrNull
import kotlin.reflect.KClass

class Option<T : Any>(
    override val type: KClass<T>,
    override val names: Set<String>,
    override val description: String? = null,
    override val regex: Regex? = null,
    override val optional: Boolean = true,
    override val multiple: Boolean = false,
    override val tag: String? = null,
    override val values: List<T> = emptyList(),
) : Property<T> {

    companion object {
        val optionRegex = "([A-Za-z0-9]|[a-z0-9\\-]{2,})".toRegex()

        private fun namesOf(shortName: Char? = null, name: String? = null): Set<String> =
            setOfNotNull(shortName?.toString(), name)
    }

    constructor(
        type: KClass<T>,
        shortName: Char? = null,
        name: String? = null,
        description: String? = null,
        regex: Regex? = null,
        optional: Boolean = true,
        multiple: Boolean = false,
        tag: String? = null,
    ) : this(type, namesOf(shortName, name), description, regex, optional, multiple, tag)

    constructor(
        type: KClass<T>,
        shortName: Char? = null,
        name: String? = null,
        description: String? = null,
        regex: Regex? = null,
        tag: String? = null,
        values: List<T>,
    ) : this(type, namesOf(shortName, name), description, regex, true, true, tag, values)

    constructor(
        type: KClass<T>,
        shortName: Char? = null,
        name: String? = null,
        description: String? = null,
        regex: Regex? = null,
        tag: String? = null,
        value: T,
    ) : this(type, namesOf(shortName, name), description, regex, true, false, tag, listOf(value))

    init {
        check("Option", optionRegex)
    }

    @Suppress("UNCHECKED_CAST") // Types checked at runtime
    override fun addValues(value: Property<*>): Property<T> =
        Option(
            type,
            names,
            description,
            regex,
            optional,
            multiple,
            tag,
            values + value.values as List<T>
        )

    override fun addValue(value: String): Option<T> =
        value.parseOrNull(type)
            ?.let { Option(type, names, description, regex, optional, multiple, tag, values + it) }
            ?: error("Option '${names.first()}' of type '${typeText()}' can not hold '$value'")

    override fun clearValues(): Option<T> =
        Option(type, names, description, regex, optional, multiple, tag, emptyList())

    // TODO Only used in tests
    fun copy(
        type: KClass<T> = this.type,
        names: Set<String> = this.names,
        description: String? = this.description,
        regex: Regex? = this.regex,
        optional: Boolean = this.optional,
        multiple: Boolean = this.multiple,
        tag: String? = this.tag,
        values: List<T> = this.values,
    ): Option<T> =
        Option(type, names, description, regex, optional, multiple, tag, values)

    // TODO Only used in tests
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Option<*>

        if (type != other.type) return false
        if (names != other.names) return false
        if (description != other.description) return false
        if (regex != other.regex) return false
        if (optional != other.optional) return false
        if (multiple != other.multiple) return false
        if (tag != other.tag) return false
        if (values != other.values) return false

        return true
    }

    // TODO Only used in tests
    override fun hashCode(): Int {
        var result = type.hashCode()
        result = 31 * result + names.hashCode()
        result = 31 * result + (description?.hashCode() ?: 0)
        result = 31 * result + (regex?.hashCode() ?: 0)
        result = 31 * result + optional.hashCode()
        result = 31 * result + multiple.hashCode()
        result = 31 * result + (tag?.hashCode() ?: 0)
        result = 31 * result + values.hashCode()
        return result
    }
}