""" Dieses Python Skript ist nur für Demonstrationszwecke gedacht. Fuer die Nutzung in Archicad muss jedes Skript immer an das jeweilige Projekt angepasst werden. """

from archicad import ACConnection
from typing import List, Tuple, Iterable
from itertools import cycle

conn = ACConnection.connect()
assert conn

acc = conn.commands
act = conn.types
acu = conn.utilities

ROW_GROUPING_LIMIT = 0.25
STORY_GOUPING_LIMIT = 1


def create_clusters(in_positions: Iterable[float], limit: float) -> List[Tuple[float, float]]:
    positions = sorted(in_positions)
    if len(positions) == 0:
        return []

    clusters = []
    pos_iter = iter(positions)
    first_pos = last_pos = next(pos_iter)

    for pos in pos_iter:
        if pos - last_pos <= limit:
            last_pos = pos
        else:
            clusters.append((first_pos, last_pos))
            first_pos = last_pos = pos

    clusters.append((first_pos, last_pos))
    return clusters


element_id_property_id = acu.GetBuiltInPropertyId('General_ElementID')
parking_space_classification_item = acu.FindClassificationItemInSystem(
    'ARCHICAD Klassifizierung', 'Parkplatz')
element_ids = acc.GetElementsByClassification(
    parking_space_classification_item.classificationItemId)
bounding_boxes = acc.Get3DBoundingBoxes(element_ids)

element_bounding_boxes = list(zip(element_ids, bounding_boxes))

z_clusters = create_clusters((bb.boundingBox3D.zMin for bb in bounding_boxes), STORY_GOUPING_LIMIT)

parking_story = 0
elem_property_values = []

for (z_min, z_max) in z_clusters:
    parking_index = 1
    elems_on_story = [e for e in element_bounding_boxes if z_min <= e[1].boundingBox3D.zMin <= z_max]
    y_clusters = create_clusters((e[1].boundingBox3D.yMin for e in elems_on_story), ROW_GROUPING_LIMIT)

    for ((y_min, y_max), reverse_row) in zip(y_clusters, cycle([False, True])):
        elems_in_row = [e for e in elems_on_story
                        if y_min <= e[1].boundingBox3D.yMin <= y_max]

        for (elem, bb) in sorted(elems_in_row, key=lambda e: e[1].boundingBox3D.xMin, reverse=reverse_row):
            elem_property_values.append(act.ElementPropertyValue(
                elem.elementId, element_id_property_id, act.NormalStringPropertyValue(
                    f"P {parking_story:1d}{parking_index:02d}")))
            parking_index += 1
    parking_story += 1

acc.SetPropertyValuesOfElements(elem_property_values)
