Matrix.kt

/**
 * This file is part of the pl.wrzasq.cform.
 *
 * @license http://mit-license.org/ The MIT license
 * @copyright 2023 - 2024 © by Rafał Wrzeszcz - Wrzasq.pl.
 */

package pl.wrzasq.cform.macro.matrix

import pl.wrzasq.cform.macro.template.asMap
import pl.wrzasq.cform.macro.template.mapValuesOnly

/**
 * Single matrix container.
 *
 * @param id Matrix ID.
 * @param idPattern Logical pattern ID for matrix entries.
 * @param template Resource template.
 * @param key Key entries order.
 * @param entries Matrix entries.
 */
class Matrix(
    private val id: String,
    private val idPattern: String,
    private val template: Map<String, Any>,
    private val key: List<String>,
    private val entries: Map<List<String>, Map<String, String>>,
) {
    /**
     * Returns number of defined matrices.
     *
     * @return Number of matrices in current template.
     */
    val length
        get() = entries.count()

    /**
     * Generates list of all resources defined in this matrx.
     *
     * @return List of resource models.
     */
    fun generateResources() = entries
        .map { generateLogicalId(it.key) to generateValue(template, it.value) }
        .sortedBy { it.first }

    /**
     * Resolves logical ID of matrix element.
     *
     * @param selector Element key parameters.
     * @return Expected logical ID.
     */
    fun resolve(selector: Map<String, String>) = generateLogicalId(
        key.map { checkNotNull(selector[it]) { "Missing $it part of $id matrix selector." } },
    )

    private fun generateLogicalId(params: List<String>) = key.foldIndexed(idPattern) { index, value, param ->
        value.replace("\${$param}", params[index])
    }

    private fun generateValue(input: Any, params: Map<String, String>): Any = when (input) {
        is Map<*, *> -> asMap(input).mapValuesOnly { generateValue(it, params) }
        is List<*> -> input.filterNotNull().map { generateValue(it, params) }
        is String -> params.entries.fold(input) { value, entry ->
            value.replace("\${Each:${entry.key}}", entry.value)
        }
        else -> input
    }
}