import re
import config
import markup3dmodule as m3dm
import polygon3dmodule as p3dm
import json
import numpy as np
import open3d as o3d
import open3d.core as o3c
import os


# Function to create triangles at each corner in 3D
def create_corner_triangles(box_points, triangle_size=1):
    triangles = []
    for i, point in enumerate(box_points):
        x, y, z = point
        if i == 0:  # Bottom-left-front corner
            triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y + triangle_size, z]])
        elif i == 1:  # Bottom-right-front corner
            triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y + triangle_size, z]])
        elif i == 2:  # Top-left-front corner
            triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y - triangle_size, z]])
        elif i == 3:  # Bottom-left-back corner
            triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y + triangle_size, z]])
        elif i == 4:  # Top-right-back corner
            triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y - triangle_size, z]])
        elif i == 5:  # Top-left-back corner
            triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y - triangle_size, z]])
        elif i == 6:  # Bottom-right-back corner
            triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y + triangle_size, z]])
        elif i == 7:  # Top-right-front corner
            triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y - triangle_size, z]])
    return triangles


def addTranslationParameters(e, i, trans_param):
    # Convert lists to numpy arrays for easier manipulation
    e = np.array(e)
    # i = np.array(i)
    i_translated = []
    trans_param = np.array(trans_param)
    # case distinction for empty translation parameters
    if len(trans_param) > 0:
        # Subtract the translation parameters from each point
        if len(e) > 0:
            e_translated = e - trans_param
        elif len(e) == 0:
            e_translated = np.asarray([])
        # Iterate over all the different interior rings
        if len(i) > 0:
            for interior_ring in i:
                interior_ring = np.asarray(interior_ring)
                # Translate the interior ring
                interior_ring_translated = interior_ring - trans_param
                # Coollect the translated interior rings
                i_translated.append(interior_ring_translated.tolist())
        else:
            i_translated = i
        return e_translated.tolist(), i_translated
    else:
        return e.tolist(), i

def getBufferedBBoxPoints(b):
    # Schritt 1: identifying all wallsurfaces and roof surfaces of the building
    output = {}
    specifyVersion()
    # comprehensive list of semantic surfaces
    semanticSurfaces = ['GroundSurface', 'WallSurface', 'RoofSurface', 'ClosureSurface', 'CeilingSurface', ]

    for semanticSurface in semanticSurfaces:
        output[semanticSurface] = []
    data = []
    for cl in output:
        cls = []
        for child in b.getiterator():
            if child.tag == '{%s}%s' % (ns_bldg, cl):
                cls.append(child)

        for feature in cls:
            for p in feature.findall('.//{%s}Polygon' % ns_gml):
                e, i = m3dm.polydecomposer(p)
                epoints = m3dm.GMLpoints(e[0])
                # -- Clean recurring points, except the last one
                last_ep = epoints[-1]
                epoints_clean = list(remove_reccuring(epoints))
                epoints_clean.append(last_ep)
                for point in epoints_clean:
                    data.append(point)

    # Schritt 2: Idetify the Bounding volume
    # 2.1 creating an open3d pointcloud from all the idetified vertex points
    pcd = o3d.t.geometry.PointCloud(o3c.Tensor(data, o3c.float32))
    # 2.2 obtain the axis aligned boundign box of the point cloud
    axis_aligned_bb = pcd.get_axis_aligned_bounding_box()
    # Schritt 3: Construct small triangles that describe the boundign box sufficienly
    box_points = axis_aligned_bb.get_box_points().numpy().tolist()
    # Convert the list to a numpy array for easier manipulation
    box_points = np.array(box_points)
    # Calculate the min and max coordinates
    min_x, min_y, min_z = np.min(box_points, axis=0)
    max_x, max_y, max_z = np.max(box_points, axis=0)
    # Add a 3m buffer
    buffer = 3
    min_x -= buffer
    min_y -= buffer
    min_z -= buffer
    max_x += buffer
    max_y += buffer
    max_z += buffer
    # Define the buffered bounding box points
    buffered_box_points = np.array([
        [min_x, min_y, min_z],
        [max_x, min_y, min_z],
        [min_x, max_y, min_z],
        [min_x, min_y, max_z],
        [max_x, max_y, max_z],
        [min_x, max_y, max_z],
        [max_x, min_y, max_z],
        [max_x, max_y, min_z]
    ])
    return buffered_box_points

def obtainSRSInfo(root):
    specifyVersion()
    # obtain the envelope object
    envelopes = []
    for envelope in root.getiterator('{%s}Envelope' % ns_gml):
        envelopes.append(envelope)

    # Extracting the srsName attribute from each Envelope
    srs_names = [envelope.get('srsName') for envelope in envelopes]
    srs_Dimensions = [envelope.get('srsDimension') for envelope in envelopes]
    return srs_names, srs_Dimensions


# This function is used to create a corresponding json file defining the bbox of an object for each corresponding obj file
def writeBBOXJSON(b, overall_counter, path, b_counter, trans_param):
    if len(trans_param) > 0:
        translation_parameters = {
            "d_x": str(trans_param[0]),
            "d_y": str(trans_param[1]),
            "d_z": str(trans_param[2])
        }
    else:
        translation_parameters = {
            "d_x": str(0),
            "d_y": str(0),
            "d_z": str(0)
        }

    buffered_box_points_global = getBufferedBBoxPoints(b)

    # translate to the local coordinate system
    buffered_box_points, _ = addTranslationParameters(buffered_box_points_global, [], trans_param=trans_param)

    # From this set of points, obtain the minimum set that is necessary to describe the bounding box.
    min_point = buffered_box_points[0]
    max_point = buffered_box_points[4]

    # Construct the json file path
    json_file_path = str(path) + str(b_counter) + "_" + str(overall_counter) + "_bbox_" + ".json"

    # Write the bbox to a designated json file
    # Prüfen, ob die JSON-Datei existiert und laden
    if os.path.exists(json_file_path):
        with open(json_file_path, 'r') as json_file:
            axis_aligned_bbox = json.load(json_file)


    else:
        axis_aligned_bbox = {}

    # Neuen Identifier hinzufügen
    axis_aligned_bbox["axis_aligned_bbox"] = {
        "min_point": str(min_point),
        "max_point": str(max_point),
        "translation_parameters": translation_parameters

    }

    # Zuordnungen in JSON-Datei schreiben
    with open(json_file_path, 'w') as json_file:
        json.dump(axis_aligned_bbox, json_file, indent=4)

    return 0


# This function is used to add information about the used spatial reference system to the json file
def addCRSToJSON(root, json_file_path):
    specifyVersion()
    # obtain the envelope object
    envelopes = []
    for envelope in root.getiterator('{%s}Envelope' % ns_gml):
        envelopes.append(envelope)
    if envelopes:
        # Extracting the srsName attribute from each Envelope
        srs_names = [envelope.get('srsName') for envelope in envelopes]
        srs_Dimensions = [envelope.get('srsDimension') for envelope in envelopes]
    elif not envelopes:
        srs_names = "Unkown"
        srs_Dimensions = "Unkown"


    used_srs = srs_names[0]
    # Prüfen, ob die JSON-Datei existiert und laden
    if os.path.exists(json_file_path):
        with open(json_file_path, 'r') as json_file:
            crs_info = json.load(json_file)
    else:
        crs_info = {}

    # Neuen Identifier hinzufügen
    crs_info["CRS"] = {
        "srsName": used_srs,
        "srsDimensions": srs_Dimensions
    }

    # Zuordnungen in JSON-Datei schreiben
    with open(json_file_path, 'w') as json_file:
        json.dump(crs_info, json_file, indent=4)

    return 0


def claculateCornerTriangles(b, trans_param):
    buffered_box_points = getBufferedBBoxPoints(b)

    # Translate the Bounding box into the local coordinate system
    # buffered_box_points, _ = addTranslationParameters(buffered_box_points_global, [], trans_param=trans_param)

    # Create triangles at the corners of the buffered bounding box
    corner_triangles = create_corner_triangles(buffered_box_points)

    # Convert the triangles to lists
    corner_triangles = [np.array(triangle).tolist() for triangle in corner_triangles]

    print("\nCorner Triangles:")
    for i, triangle in enumerate(corner_triangles):
        print(f"Triangle {i + 1}: {triangle}")

    return corner_triangles


# diese funktion dient dazu ein JSON file zu schreiben um die meta informationen über die einzelnen objekte zuspeichern
def add_identifier_to_json(filename, tag, parentID, gmlID, json_file_path):
    """
    Adds the identifier information for one .obj file to a JSON file.

    Parameters:
    - number (int): The number corresponding to the .obj file.
    - tag (str): The tag corresponding to the .obj file.
    - parentID (str): The parent ID corresponding to the .obj file.
    - gmlID (str): The gml ID corresponding to the .obj file.
    - json_file_path (str): Path to the JSON file where identifier information will be stored.
    """

    # Prüfen, ob die JSON-Datei existiert und laden
    if os.path.exists(json_file_path):
        with open(json_file_path, 'r') as json_file:
            identifiers = json.load(json_file)
    else:
        identifiers = {}

    # Neuen Identifier hinzufügen
    identifiers[filename] = {
        'tag': tag,
        'parentID': parentID,
        'gmlID': gmlID
    }

    # Zuordnungen in JSON-Datei schreiben
    with open(json_file_path, 'w') as json_file:
        json.dump(identifiers, json_file, indent=4)

    print(f"Zuordnung für {filename} wurde gespeichert.")


def perturb_points(points, perturbation_scale=1e-6):
    """
    Perturb the points slightly to avoid degenerate cases.

    Parameters:
        points (list of tuple of floats): A list where each element is a tuple (x, y, z) representing a 3D point.
        perturbation_scale (float): The maximum magnitude of the perturbation applied to each coordinate.

    Returns:
        list of list of floats: Perturbed list of points.
    """
    points_array = np.array(points)
    perturbation = np.random.uniform(-perturbation_scale, perturbation_scale, points_array.shape)
    perturbed_points = points_array + perturbation
    return perturbed_points.tolist()


def write_obj_file(surfaces, filename, tag, parentid, gmlid, counter, path, tr_1, translation_parameters):
    for triangle in tr_1:
        triangle_local, _ = addTranslationParameters(triangle, [], trans_param=translation_parameters)
        surfaces.append(triangle_local)
    with open(filename, 'w') as file:
        vertex_index = 1
        for triangle in surfaces:
            for vertex in triangle:
                file.write(f"v {vertex[0]} {vertex[1]} {vertex[2]}\n")
            file.write(f"f {vertex_index} {vertex_index + 1} {vertex_index + 2}\n")
            vertex_index += 3
    add_identifier_to_json(filename, tag, parentid, gmlid, (path + "index.json"))


def remove_reccuring(list_vertices):
    """Removes recurring vertices, which messes up the triangulation.
    Inspired by http://stackoverflow.com/a/1143432"""
    # last_point = list_vertices[-1]
    list_vertices_without_last = list_vertices[:-1]
    found = set()
    for item in list_vertices_without_last:
        if str(item) not in found:
            yield item
            found.add(str(item))


def separate_string(s):
    # Define the regex pattern
    pattern = r'\{([^}]*)\}(.*)'
    # Search for the pattern in the input string
    match = re.search(pattern, s)
    if match:
        # Extract the parts
        inside_braces = match.group(1)
        outside_braces = match.group(2)
        return inside_braces, outside_braces
    else:
        return None, None


def specifyVersion():
    global ns_citygml
    global ns_gml
    global ns_bldg
    global ns_xsi
    global ns_xAL
    global ns_xlink
    global ns_dem
    global ns_con
    global ns_app
    global ns_pcl
    global ns_gen
    global ns_gss
    global ns_pfx0
    global ns_gsr
    global ns_tran
    global ns_gmd
    global ns_gts
    global ns_veg
    global ns_frn
    global ns_tun
    global ns_wtr
    global nsmap

    if config.getVersion() == 1:
        # -- Name spaces for CityGML 2.0
        ns_citygml = "http://www.opengis.net/citygml/1.0"
        ns_gml = "http://www.opengis.net/gml"
        ns_bldg = "http://www.opengis.net/citygml/building/1.0"
        ns_tran = "http://www.opengis.net/citygml/transportation/1.0"
        ns_veg = "http://www.opengis.net/citygml/vegetation/1.0"
        ns_gen = "http://www.opengis.net/citygml/generics/1.0"
        ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
        ns_xAL = "urn:oasis:names:tc:ciq:xsdschema:xAL:1.0"
        ns_xlink = "http://www.w3.org/1999/xlink"
        ns_dem = "http://www.opengis.net/citygml/relief/1.0"
        ns_frn = "http://www.opengis.net/citygml/cityfurniture/1.0"
        ns_tun = "http://www.opengis.net/citygml/tunnel/1.0"
        ns_wtr = "http://www.opengis.net/citygml/waterbody/1.0"
        ns_brid = "http://www.opengis.net/citygml/bridge/1.0"
        ns_app = "http://www.opengis.net/citygml/appearance/1.0"
    if config.getVersion() == 2:
        # -- Name spaces for CityGML 2.0
        ns_citygml = "http://www.opengis.net/citygml/2.0"
        ns_gml = "http://www.opengis.net/gml"
        ns_bldg = "http://www.opengis.net/citygml/building/2.0"
        ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
        ns_xAL = "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
        ns_xlink = "http://www.w3.org/1999/xlink"
        ns_dem = "http://www.opengis.net/citygml/relief/2.0"
    elif config.getVersion() == 3:
        # -- Name spaces for CityGML 3.0
        ns_citygml = "http://www.opengis.net/citygml/3.0"
        ns_con = "http://www.opengis.net/citygml/construction/3.0"
        ns_xlink = "http://www.w3.org/1999/xlink"
        ns_gml = "http://www.opengis.net/gml/3.2"
        ns_bldg = "http://www.opengis.net/citygml/building/3.0"
        ns_app = "http://www.opengis.net/citygml/appearance/3.0"
        ns_pcl = "http://www.opengis.net/citygml/pointcloud/3.0"
        ns_gen = "http://www.opengis.net/citygml/generics/3.0"
        ns_gss = "http://www.isotc211.org/2005/gss"
        ns_pfx0 = "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
        ns_gsr = "http://www.isotc211.org/2005/gsr"
        ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
        ns_tran = "http://www.opengis.net/citygml/transportation/3.0"
        ns_gmd = "http://www.isotc211.org/2005/gmd"
        ns_gts = "http://www.isotc211.org/2005/gts"
        ns_veg = "http://www.opengis.net/citygml/vegetation/3.0"
        ns_xAL = "urn:oasis:names:tc:ciq:xal:3"
        ns_dem = "http://www.opengis.net/citygml/relief/3.0"
        ns_frn = "http://www.opengis.net/citygml/cityfurniture/3.0"
        ns_tun = "http://www.opengis.net/citygml/tunnel/3.0"
        ns_wtr = "http://www.opengis.net/citygml/waterbody/3.0"

    nsmap = {
        None: ns_citygml,
        'gml': ns_gml,
        'bldg': ns_bldg,
        'xsi': ns_xsi,
        'xAL': ns_xAL,
        'xlink': ns_xlink,
        'dem': ns_dem
    }


def compute_convex_hull(points):
    """
    Computes the convex hull of a set of 3D points using Open3D, including triangulation of the hull's faces.

    Parameters:
        points (list of tuple of floats): A list where each element is a tuple (x, y, z) representing a 3D point.

    Returns:
        list: A list of faces, where each face is a list of vertex coordinates forming that face.
    """
    # Convert the list of points to a NumPy array
    points_array = np.array(points)
    perturbed_points = perturb_points(points_array)

    # Create an Open3D PointCloud object
    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(perturbed_points)

    # Compute the convex hull
    hull, triangles = pcd.compute_convex_hull()

    # Extract vertices and faces (triangles)
    hull_vertices = np.asarray(hull.vertices)
    hull_triangles = np.asarray(hull.triangles)

    # Prepare the faces in the desired format
    faces = []
    for triangle in hull_triangles:
        face = [list(hull_vertices[vertex]) for vertex in triangle]
        faces.append(face)
    return faces


def process_polygon(p, trans_param):
    e = p[0]
    i = p[1]
    e_trans, i_trans = addTranslationParameters(e, i, trans_param=trans_param)
    t = p3dm.triangulation(e_trans, i_trans)
    # print(f"t: {t}")
    return t


def process_polygons_parallel(polys, trans_param):
    data = []
    results = []
    for poly in polys:
        e, i = m3dm.polydecomposer(poly)
        epoints = m3dm.GMLpoints(e[0])
        # -- Clean recurring points, except the last one
        last_ep = epoints[-1]
        epoints_clean = list(remove_reccuring(epoints))
        epoints_clean.append(last_ep)
        # -- LinearRing(s) forming the interior
        irings = []
        for iring in i:
            ipoints = m3dm.GMLpoints(iring)
            # -- Clean them in the same manner as the exterior ring
            last_ip = ipoints[-1]
            ipoints_clean = list(remove_reccuring(ipoints))
            ipoints_clean.append(last_ip)
            irings.append(ipoints_clean)
        if len(epoints_clean) > 4:
            t = process_polygon([epoints_clean, irings], trans_param=trans_param)
            results.append(t)
            # data.append(poly_components)
        if len(epoints_clean) == 4:
            epoints_clean_translated, _ = addTranslationParameters(epoints_clean, [], trans_param=trans_param)
            results.append([epoints_clean_translated])

        # t = process_polygon(poly)
        # results.append(t)

    # cpu_cores = os.cpu_count()
    # print(f'Number of available CPU cores (using os): {cpu_cores}')
    # with ThreadPoolExecutor(max_workers=cpu_cores) as executor:
    #    # Submitting all tasks
    #    futures = [executor.submit(process_polygon, p) for p in data]
    #    # Collecting results
    #    for future in futures:
    #        result = future.result()  # This will re-raise any exception caught during the execution of the task
    #        results.append(result)
    return results


# this is an experimental method for parallelization
def processOpening(o, path, buildingid, overall_counter, tr_1, trans_param, b_counter):
    for child in o.getiterator():
        unique_identifier = child.xpath("@g:id", namespaces={'g': ns_gml})
        if child.tag == '{%s}Window' % ns_bldg or child.tag == '{%s}Door' % ns_bldg:
            polys = m3dm.polygonFinder(o)
            t = process_polygons_parallel(polys, trans_param=trans_param)
            triangles = []
            for poly in t:
                for tr in poly:
                    triangles.append(tr)
            filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
            write_obj_file(triangles, filename, str(child.tag), buildingid, unique_identifier, overall_counter, path,
                           tr_1, trans_param)


def getAllExteriorPoints(polys):
    data = []
    for poly in polys:
        e, i = m3dm.polydecomposer(poly)
        epoints = m3dm.GMLpoints(e[0])
        # -- Clean recurring points, except the last one
        last_ep = epoints[-1]
        epoints_clean = list(remove_reccuring(epoints))
        epoints_clean.append(last_ep)
        for point in epoints_clean:
            data.append(point)
    return data


def processWithApproximatedWindows(o, path, buildingid, overall_counter, tr_1, translation_parameters, b_counter):
    for child in o.getiterator():
        unique_identifier = child.xpath("@g:id", namespaces={'g': ns_gml})
        if child.tag == '{%s}Window' % ns_bldg or child.tag == '{%s}Door' % ns_bldg:
            polys = m3dm.polygonFinder(o)
            exterior_points = getAllExteriorPoints(polys)
            t_global = compute_convex_hull(exterior_points)
            _, t = addTranslationParameters(e=[], i=t_global, trans_param=translation_parameters)
            filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
            write_obj_file(t, filename, str(child.tag), buildingid, unique_identifier, overall_counter, path, tr_1,
                           translation_parameters=translation_parameters)


# This function is used to im port a bounding box that is associated to the corresponding building component
# It still has to be tested if it works
def importBoundingBox(pathToBoundingBoxFile, trans_param):
    print("Hier")
    keys = ["_xmin", "_xmax", "_ymin", "_ymax", "_zmin", "_zmax"]
    values = {}

    with open(pathToBoundingBoxFile, 'r') as file:
        for _ in range(15):
            line = file.readline()
            for key in keys:
                match = re.search(f'"{key}"\s*:\s*([-0-9.]+)', line)
                if match:
                    values[key] = float(match.group(1))

    if not all(k in values for k in keys):
        raise ValueError("Missing bounding box values in the first 15 lines")

    min_x, max_x = values["_xmin"], values["_xmax"]
    min_y, max_y = values["_ymin"], values["_ymax"]
    min_z, max_z = values["_zmin"], values["_zmax"]

    print(f"min x: {min_x} min y: {min_y} , min z: {min_z}")
    print(f"max x: {max_x} max y: {max_y} , max z: {max_z}")

    box_points = np.array([
        [min_x, min_y, min_z],
        [max_x, min_y, min_z],
        [min_x, max_y, min_z],
        [min_x, min_y, max_z],
        [max_x, max_y, max_z],
        [min_x, max_y, max_z],
        [max_x, min_y, max_z],
        [max_x, max_y, min_z]
    ])

    # Create triangles at the corners of the buffered bounding box
    corner_triangles = create_corner_triangles(box_points)

    # Convert the triangles to lists
    corner_triangles = [np.array(triangle).tolist() for triangle in corner_triangles]

    print("\nCorner Triangles:")
    for i, triangle in enumerate(corner_triangles):
        print(f"Triangle {i + 1}: {triangle}")

    return corner_triangles


def separateComponents(b, path, APPROXIMATEWINDOWS, ADDBOUNDINGBOX, ADDBOUNDINGBOXJSON, TRANSLATEBUILDINGS,
                       IMPORTBOUNDINGBOX, b_counter):
    if TRANSLATEBUILDINGS:
        # Step 1: Obtain the axis oriented bounding box of the building
        bounding_box_points = getBufferedBBoxPoints(b)

        # Step 2 calculate the mean value of the points that the bbox points
        translation_parameters = np.mean(bounding_box_points, axis=0)

    if not TRANSLATEBUILDINGS:  # todo: nocheinmal üerlegen ob man hier nicht vielleicht besser elif oder so nehmen sollte
        translation_parameters = []

    if IMPORTBOUNDINGBOX != None:
        ADDBOUNDINGBOX = False  # make sure that the bounding box is not calculated

    # Option to include the small triangles to mark the buffered bounding box
    if ADDBOUNDINGBOX:
        tr_1 = claculateCornerTriangles(b, trans_param=translation_parameters)
    elif not ADDBOUNDINGBOX:
        tr_1 = []
    global overall_counter
    overall_counter = 0
    output = {}
    specifyVersion()
    # comprehensive list of semantic surfaces
    semanticSurfaces = ['GroundSurface', 'WallSurface', 'RoofSurface', 'ClosureSurface', 'CeilingSurface',
                        'InteriorWallSurface', 'FloorSurface', 'OuterCeilingSurface', 'OuterFloorSurface', 'Door',
                        "outerBuildingInstallation",
                        'Window', "BuildingInstallation", "BuildingConstructiveElement"]

    for semanticSurface in semanticSurfaces:
        output[semanticSurface] = []

    # get the building id for the building
    buildingid = b.xpath("@g:id", namespaces={'g': ns_gml})

    # Handle the case when a building has no gml:id - This should however never occur...
    if not buildingid:
        buildingid = b_counter

    # Todo: Muss noch implementiert werden
    # Import the bounding box
    if IMPORTBOUNDINGBOX != None:
        pathToBoundingBoxFile = IMPORTBOUNDINGBOX + "/" + str(buildingid) + ".json"
        tr_1 = importBoundingBox(pathToBoundingBoxFile=pathToBoundingBoxFile, trans_param=translation_parameters)

    if config.getVersion() != 3:
        openings = []
        openingpolygons = []
        for child in b.getiterator():
            if child.tag == '{%s}opening' % ns_bldg:
                openings.append(child)
                for o in child.findall('.//{%s}Polygon' % ns_gml):
                    openingpolygons.append(o)

        for o in openings:
            # print("approximate windows: ", APPROXIMATEWINDOWS)
            if APPROXIMATEWINDOWS:
                processWithApproximatedWindows(o, path, buildingid, overall_counter, tr_1=tr_1,
                                               translation_parameters=translation_parameters, b_counter=b_counter)
            if not APPROXIMATEWINDOWS:
                processOpening(o, path, buildingid, overall_counter, tr_1, trans_param=translation_parameters,
                               b_counter=b_counter)
            if ADDBOUNDINGBOXJSON:
                writeBBOXJSON(b, overall_counter=overall_counter, path=path, b_counter=b_counter,
                              trans_param=translation_parameters)
            overall_counter += 1

    if config.getVersion() == 3:
        openingpolygons = []
        print("Component separation for CityGML 3.0 is not implemented yet.")
        # todo: muss noch implementiert werden

    # -- Process other thematic boundaries
    for cl in output:
        cls = []
        for child in b.getiterator():
            if child.tag == '{%s}%s' % (ns_bldg, cl):
                cls.append(child)

        for feature in cls:
            # -- If it is the first feature, print the object identifier
            unique_identifier = feature.xpath("@g:id", namespaces={
                'g': ns_gml})
            if str(unique_identifier) != "[]" or str(unique_identifier) == "[]":
                cleaned_filename = str(unique_identifier)
                # -- This is not supposed to happen, but just to be sure...
                if feature.tag == '{%s}Window' % ns_bldg or feature.tag == '{%s}Door' % ns_bldg:
                    continue
                tag = feature.tag
                _, cleaned_tag = separate_string(tag)
                # -- Find all polygons in this semantic boundary hierarchy
                poly_t = []
                t_ges = []
                number_of_polygons = len(feature.findall('.//{%s}Polygon' % ns_gml))
                pcounter = 0
                print(f"there are {number_of_polygons} polygons there!")
                for p in feature.findall('.//{%s}Polygon' % ns_gml):
                    found_opening = False
                    for optest in openingpolygons:
                        if p == optest:
                            found_opening = True
                            break
                    # -- If there is an opening skip it
                    if found_opening:
                        pass
                    else:
                        # -- Decompose the polygon into exterior and interior
                        e, i = m3dm.polydecomposer(p)
                        # -- Points forming the exterior LinearRing
                        epoints = m3dm.GMLpoints(e[0])
                        # -- Clean recurring points, except the last one
                        last_ep = epoints[-1]
                        epoints_clean = list(remove_reccuring(epoints))
                        epoints_clean.append(last_ep)

                        # -- LinearRing(s) forming the interior
                        irings = []
                        for iring in i:
                            ipoints = m3dm.GMLpoints(iring)
                            # -- Clean them in the same manner as the exterior ring
                            last_ip = ipoints[-1]
                            ipoints_clean = list(remove_reccuring(ipoints))
                            ipoints_clean.append(last_ip)
                            irings.append(ipoints_clean)

                        # Applying the translation parameters
                        e_trans, i_trans = addTranslationParameters(e=epoints_clean, i=irings,
                                                                    trans_param=translation_parameters)

                        try:
                            if len(epoints_clean) > 4:
                                t = p3dm.triangulation(e_trans, i_trans)
                                # print("Nur drei Punkte")
                                poly_t.append(t)
                            if len(epoints_clean) == 4:
                                t = e_trans[0:3]
                                poly_t.append([t])
                            if len(epoints_clean) < 3:
                                t = []
                                # print("Empty Surface!")
                        except:
                            t = []

                        for surfaces in poly_t:
                            t_ges = t_ges + surfaces

                    pcounter += 1
                if pcounter % 100 == 0:
                    print(pcounter)

                filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
                if ADDBOUNDINGBOXJSON:
                    writeBBOXJSON(b, overall_counter=overall_counter, path=path, b_counter=b_counter,
                                  trans_param=translation_parameters)
                write_obj_file(t_ges, filename, str(feature.tag), buildingid, cleaned_filename, overall_counter, path,
                               tr_1, translation_parameters=translation_parameters)
                overall_counter += 1

    print("Segmentation finished!")
    return 0