package center.sciprog.maps.scheme

import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.scheme.SchemeFeature.Companion.defaultScaleRange

typealias FeatureId = String


public class SchemeFeaturesState internal constructor(
    private val features: MutableMap<FeatureId, SchemeFeature>,
    private val attributes: MutableMap<FeatureId, SnapshotStateMap<Attribute<out Any?>, in Any?>>,
) {
    public interface Attribute<T>

    public fun features(): Map<FeatureId, SchemeFeature> = features


    private fun generateID(feature: SchemeFeature): FeatureId = "@feature[${feature.hashCode().toUInt()}]"

    public fun addFeature(id: FeatureId?, feature: SchemeFeature): FeatureId {
        val safeId = id ?: generateID(feature)
        features[id ?: generateID(feature)] = feature
        return safeId
    }

    public fun <T> setAttribute(id: FeatureId, key: Attribute<T>, value: T) {
        attributes.getOrPut(id) { mutableStateMapOf() }[key] = value
    }

    @Suppress("UNCHECKED_CAST")
    public fun <T> getAttribute(id: FeatureId, key: Attribute<T>): T? =
        attributes[id]?.get(key)?.let { it as T }

    @Suppress("UNCHECKED_CAST")
    public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
        return attributes.filterValues {
            condition(it[key] as T)
        }.keys
    }

    public companion object {

        /**
         * Build, but do not remember map feature state
         */
        public fun build(
            builder: SchemeFeaturesState.() -> Unit = {},
        ): SchemeFeaturesState = SchemeFeaturesState(
            mutableStateMapOf(),
            mutableStateMapOf()
        ).apply(builder)

        /**
         * Build and remember map feature state
         */
        @Composable
        public fun remember(
            builder: SchemeFeaturesState.() -> Unit = {},
        ): SchemeFeaturesState = androidx.compose.runtime.remember(builder) {
            build(builder)
        }

    }
}

fun SchemeFeaturesState.background(
    box: SchemeRectangle,
    id: FeatureId? = null,
    painter: @Composable () -> Painter,
): FeatureId = addFeature(
    id,
    SchemeBackgroundFeature(box, painter = painter)
)

fun SchemeFeaturesState.background(
    width: Float,
    height: Float,
    offset: SchemeCoordinates = SchemeCoordinates(0f, 0f),
    id: FeatureId? = null,
    painter: @Composable () -> Painter,
): FeatureId {
    val box = SchemeRectangle(
        offset,
        SchemeCoordinates(width + offset.x, height + offset.y)
    )
    return background(box, id, painter = painter)
}

fun SchemeFeaturesState.circle(
    center: SchemeCoordinates,
    scaleRange: FloatRange = defaultScaleRange,
    size: Float = 5f,
    color: Color = Color.Red,
    id: FeatureId? = null,
) = addFeature(
    id, SchemeCircleFeature(center, scaleRange, size, color)
)

fun SchemeFeaturesState.circle(
    centerCoordinates: Pair<Number, Number>,
    scaleRange: FloatRange = defaultScaleRange,
    size: Float = 5f,
    color: Color = Color.Red,
    id: FeatureId? = null,
) = addFeature(
    id, SchemeCircleFeature(centerCoordinates.toCoordinates(), scaleRange, size, color)
)

fun SchemeFeaturesState.draw(
    position: Pair<Number, Number>,
    scaleRange: FloatRange = defaultScaleRange,
    id: FeatureId? = null,
    drawFeature: DrawScope.() -> Unit,
) = addFeature(id, SchemeDrawFeature(position.toCoordinates(), scaleRange, drawFeature))

fun SchemeFeaturesState.line(
    aCoordinates: SchemeCoordinates,
    bCoordinates: SchemeCoordinates,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
): FeatureId = addFeature(
    id,
    SchemeLineFeature(aCoordinates, bCoordinates, scaleRange, color)
)

fun SchemeFeaturesState.line(
    aCoordinates: Pair<Number, Number>,
    bCoordinates: Pair<Number, Number>,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
) = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id)

public fun SchemeFeaturesState.arc(
    oval: SchemeRectangle,
    startAngle: Float,
    arcLength: Float,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
): FeatureId = addFeature(
    id,
    SchemeArcFeature(oval, startAngle, arcLength, scaleRange, color)
)

public fun SchemeFeaturesState.arc(
    center: Pair<Double, Double>,
    radius: Float,
    startAngle: Float,
    arcLength: Float,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
): FeatureId = addFeature(
    id,
    SchemeArcFeature(
        oval = SchemeRectangle.square(center.toCoordinates(), radius, radius),
        startAngle = startAngle,
        arcLength = arcLength,
        scaleRange = scaleRange,
        color = color
    )
)

fun SchemeFeaturesState.text(
    position: SchemeCoordinates,
    text: String,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
) = addFeature(id, SchemeTextFeature(position, text, scaleRange, color))

fun SchemeFeaturesState.text(
    position: Pair<Number, Number>,
    text: String,
    scaleRange: FloatRange = defaultScaleRange,
    color: Color = Color.Red,
    id: FeatureId? = null,
) = addFeature(id, SchemeTextFeature(position.toCoordinates(), text, scaleRange, color))

fun SchemeFeaturesState.image(
    position: Pair<Number, Number>,
    image: ImageVector,
    size: DpSize = DpSize(20.dp, 20.dp),
    scaleRange: FloatRange = defaultScaleRange,
    id: FeatureId? = null,
) = addFeature(id, SchemeImageFeature(position.toCoordinates(), image, size, scaleRange))

fun SchemeFeaturesState.group(
    scaleRange: FloatRange = defaultScaleRange,
    id: FeatureId? = null,
    builder: SchemeFeaturesState.() -> Unit,
): FeatureId {
    val groupBuilder = SchemeFeaturesState.build(builder)
    val feature = SchemeFeatureGroup(groupBuilder.features(), scaleRange)
    return addFeature(id, feature)
}