""" 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
import string

conn = ACConnection.connect()
assert conn

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

ROW_GROUPING_LIMIT = 0.05


def get_elem_id(row_index, index_in_row, is_right):
    return string.ascii_uppercase[row_index] + "." + str(index_in_row) + "/" + ("Rechts" if is_right else "Links")


def get_xMin(elem_with_bounding_box):
    return elem_with_bounding_box[1].boundingBox3D.xMin


def get_new_elem_property_values(elems_with_bounding_box, is_right, row_index):
    property_values = []
    index_in_row = 1
    for (elem, _) in sorted(elems_with_bounding_box, key=get_xMin, reverse=is_right):
        property_values.append(act.ElementPropertyValue(
            elem.elementId, element_id_property_id, act.NormalStringPropertyValue(
                get_elem_id(row_index, index_in_row, is_right)
            )))
        index_in_row += 1
    return property_values


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')
chair_classification_item = acu.FindClassificationItemInSystem(
    'ARCHICAD Klassifizierung', 'Möbel')
element_ids = acc.GetElementsByClassification(
    chair_classification_item.classificationItemId)
bounding_boxes = acc.Get3DBoundingBoxes(element_ids)

average_x_position = sum([bb.boundingBox3D.xMin for bb in bounding_boxes]) / len(bounding_boxes)
z_clusters = create_clusters((bb.boundingBox3D.zMin for bb in bounding_boxes), ROW_GROUPING_LIMIT)
element_bounding_boxes = list(zip(element_ids, bounding_boxes))

row_index = 0
elem_property_values = []

for (z_min, z_max) in z_clusters:
    elems_in_row = [e for e in element_bounding_boxes if z_min <= e[1].boundingBox3D.zMin <= z_max]

    rightSide, leftSide = [], []
    for elem in elems_in_row:
        if get_xMin(elem) > average_x_position:
            leftSide.append(elem)
        else:
            rightSide.append(elem)

    elem_property_values.extend(get_new_elem_property_values(rightSide, True, row_index))
    elem_property_values.extend(get_new_elem_property_values(leftSide, False, row_index))

    row_index += 1

acc.SetPropertyValuesOfElements(elem_property_values)
