import math
from ctypes.wintypes import PINT
from operator import attrgetter, itemgetter

from qgis.core import (
    QgsExpression,
    QgsFeature,
    QgsFeatureRequest,
    QgsGeometry,
    QgsProject,
    QgsVectorLayerUtils,
    edit,
)
from qgis.gui import QgisInterface
from qgis.PyQt.QtCore import QRegularExpression, QRegularExpressionMatch

from topaze.topaze_config import TopazeConfig

from .file_utils import FileUtils
from .observation import Observation
from .ptopo import Ptopo, PtopoConst
from .station import Station
from .topaze_layers import TopazeLayers


class TopazeUtils:
    @staticmethod
    def find_file_encoding(fullfilepath):
        import codecs

        encodings = [
            "windows-1250",
            "iso-8859-1",
            "iso-8859-15",
            "windows-1252",
            "utf-8",
        ]
        for e in encodings:
            try:
                fh = codecs.open(fullfilepath, "r", encoding=e)
                fh.read()
                fh.seek(0)
            except UnicodeDecodeError:
                fh.close()
                return None
            else:
                fh.close()
                return e
        return None

    @staticmethod
    def get_field_data_type(fullfilepath):
        try:
            with open(
                fullfilepath, "r", encoding=TopazeUtils.find_file_encoding(fullfilepath)
            ) as field_file:
                buffer = field_file.read()
                regex = QRegularExpression(
                    r"(^\*11[.0-9]{4}[-+][-0-9_A-Za-z.]{16} )|"
                    r"(^\*41[0-9]{4}+[-0-9_A-Za-z.?]{16} )"
                )
                if regex.match(buffer[0:32]).hasMatch():
                    return "LEICA"
                regex = QRegularExpression(
                    "(^11([0-9]|[.]){4}[-+][-0-9a-zA-Z_.]{8} )|"
                    "(^41[0-9]{4}[-+][-0-9a-zA-Z_.?]{8} )"
                )
                if regex.match(buffer[0:32]).hasMatch():
                    return "LEICA"
                regex = QRegularExpression(
                    r"^[\s\S]*?<(?:[A-Za-z_][\w.-]*:)?LandXML(?:\s|>)"
                )
                if regex.match(buffer.lstrip("\ufeff")[0:4096]).hasMatch():
                    return "landxml"
                regex = QRegularExpression(
                    "(^2=|^0=|^50=)|(^4=)|(^5=)|(^7=)|(^8=)|(^9=)|"
                    "(\n2=|\n0=\n50=)|(\n4=)|(\n5=)|(\n7=)|(\n8=)|(\n9=)|"
                    "( 4=)|( 5=)|( 7=)|( 8=)|( 9=)"
                )
                if regex.match(buffer[0:32]).hasMatch():
                    return "TRIMBLE"
                regex = QRegularExpression(
                    "(^st=|^0=|^pnt=)|(^ref=)|(^infos=)|(^ch=)|(^da=)|(^code=)"
                )
                if regex.match(buffer[0:32]).hasMatch():
                    return "AZIMUT"
                return None
        except Exception as e:
            print(str(e))
            return None

    @staticmethod
    def load_obs_array(mode: str = "db"):
        obs_array = []
        if mode == "file":
            fullfilepath = (
                QgsProject.instance().homePath()
                + "/db/"
                + QgsProject.instance().baseName()
                + ".obs"
            )
            obs_array = TopazeUtils.load_obs_array_from_obsfile(fullfilepath)
        elif mode == "db":
            observations_layer = TopazeLayers.field_layer()
            obs_array = TopazeUtils.load_obs_array_from_db(observations_layer)
        return obs_array

    @staticmethod
    def load_obs_array_from_obsfile(fullfilepath):
        obs_array = []
        try:
            with open(fullfilepath, "r", encoding="utf-8") as obs_file:
                field_data = obs_file.read()

        except Exception as e:
            s = str(e)
            return obs_array

        else:
            lines, obs_array = TopazeUtils.parse_field_rows(field_data.splitlines())
        return obs_array

    @staticmethod
    def load_obs_array_from_db(observations_layer):
        obs_array = []
        try:
            field_data = ""
            if observations_layer:
                idx_obs_column = observations_layer.fields().indexFromName("obs")
                obs_iter = observations_layer.getFeatures()
                if obs_iter.isValid():
                    feature = QgsFeature()
                    if obs_iter.nextFeature(feature):
                        if idx_obs_column >= 0:
                            field_data = feature.attribute("obs")
        except Exception as e:
            print(str(e))
        else:
            lines, obs_array = TopazeUtils.parse_field_rows(field_data.splitlines())
        return obs_array

    @staticmethod
    def type_obs(obs):
        """Return type of observation in string or dict.

        Keyword arguments:
            obs -- string containing observation (ex: 'st=S1 hi=1.65' or {'st': 'S1', 'hi': 1.65})

        Returns:
            type as first string before '=' ('st', 'ref', 'pnt', 'hp', 'hpp', 'code', 'xyz')
        """
        type_obs = None
        if isinstance(obs, str):
            idx = obs.index("=")
            if idx > 0:
                type_obs = obs[:idx]
                if "x=" in obs and "y=" in obs:
                    type_obs = "xyz"
        elif isinstance(obs, dict):
            type_obs = obs.keys[0]

        return type_obs

    @staticmethod
    def types_obs(obs):
        """Return types of observation and values in string or dict.

        Keyword arguments:
            obs -- string containing observation (ex: 'st=S1 hi=1.65' or {'st': 'S1', 'hi': 1.65})

        Returns:
            type_obs as first string before '=' ('st', 'ref', 'pnt', 'hp', 'hpp',
            'code'
            type_pt if values are x, y and z
        """
        type_obs = None
        type_pt = None
        if isinstance(obs, str) and "=" in obs:
            idx = obs.index("=")
            if idx > 0:
                type_obs = obs[:idx]
                if "x=" in obs and "y=" in obs:
                    if type_obs == "st":
                        type_pt = "S"
                    elif type_obs == "ref":
                        type_pt = "R"
                    elif type_obs == "pnt":
                        type_pt = "P"
                    type_obs = "xyz"

        elif isinstance(obs, dict) and len(obs.keys()):
            type_obs = obs.keys[0]
            if "x" in obs.keys() and "y" in obs.keys():
                if type_obs == "st":
                    type_pt = "S"
                elif type_obs == "ref":
                    type_pt = "R"
                elif type_obs == "pnt":
                    type_pt = "P"
                type_obs = "xyz"

        return type_obs, type_pt

    @staticmethod
    def dict_to_obs_str(dico):
        rows = []
        if "st" in dico.keys():
            if "x" in dico.keys() and "y" in dico.keys():
                if "z" in dico.keys():
                    rows.append(
                        f"st={dico['st']} x={dico['x']} y={dico['y']} z={dico['z']}"
                    )
                else:
                    rows.append(f"st={dico['st']} x={dico['x']} y={dico['y']}")
            elif "hi" in dico.keys() and dico["hi"]:
                if "v0" in dico.keys():
                    rows.append(f"st={dico['st']} hi={dico['hi']} v0={dico['v0']}")
                else:
                    rows.append(f"st={dico['st']} hi={dico['hi']}")
            elif "hi" in dico.keys():
                rows.append(f"st={dico['st']} hi=0")
        elif "pnt" in dico.keys() or "ref" in dico.keys():
            if "pnt" in dico.keys():
                key = "pnt"
            elif "ref" in dico.keys():
                key = "ref"
            if "x" in dico.keys() and "y" in dico.keys():
                if "z" in dico.keys():
                    rows.append(
                        f"{key}={dico[key]} x={dico['x']} y={dico['y']} z={dico['z']}"
                    )
                else:
                    rows.append(f"{key}={dico[key]} x={dico['x']} y={dico['y']}")
            row = f"{key}={dico[key]}"
            if "ah" in dico.keys():
                row = row + f" ah={dico['ah']}"
            if "av" in dico.keys():
                row = row + f" av={dico['av']}"
            if "di" in dico.keys():
                row = row + f" di={dico['di']}"
            elif "dh" in dico.keys():
                row = row + f" dh={dico['dh']}"
            if "dz" in dico.keys():
                row = row + f" dz={dico['dz']}"
            if "hp" in dico.keys():
                row = row + f" hp={dico['hp']}"
            rows.append(row)
        elif "hp" in dico.keys():
            rows.append(f"hp={dico['hp']}")
        elif "code" in dico.keys():
            rows.append(f"code={dico['code']}")
        elif "rem" in dico.keys():
            rows.append(f"rem={dico['rem']}")
        elif "infos" in dico.keys():
            rows.append(f"infos={dico['infos']}")
        else:
            print(dico)
        return rows

    @staticmethod
    def obs_str_to_dict(row):
        """Return dict from observation row

        Keyword arguments:
            row -- string containing observation (ex: 'st=S1 hi=1.65')

        Returns:
            dict - with values from row (ex: {'st': S1, 'hi': 1.65})
        """
        dico = dict()
        if row.startswith("code="):
            str_array = [f"code={row[5:]}"]
        elif row.startswith("infos="):
            str_array = [f"infos={row[6:]}"]
        else:
            str_array = row.split()
        for s in str_array:
            field = s.split("=")
            dico[field[0]] = field[1]

        return dico

    @staticmethod
    def distance2d(p1, p2):
        return math.hypot(p2.x - p1.x, p2.y - p1.y)

    @staticmethod
    def squared_distance2d(p1, p2):
        return math.hypot(p2.x - p1.x, p2.y - p1.y)

    @staticmethod
    def find_references_in_obs_array(
        obs_array, start_index: int = 0, end_index: int = 1024000
    ):
        references = []
        idx = start_index - 1
        for obs in obs_array[start_index:end_index]:
            idx += 1
            if idx > end_index or obs.type == "st":
                return references, idx - 1
            if obs.type == "ref":
                references.append(obs)
        return references, idx - 1

    @staticmethod
    def find_station_in_obs_array(
        obs_array, start_index: int = 0, end_index: int = 1024000
    ):
        station = None
        idx = start_index - 1
        for obs in obs_array[start_index:end_index]:
            idx += 1
            if idx > end_index:
                return station, -1
            if obs.type == "st":
                if station is None:
                    station = Station(obs.matricule)
                if obs.hi is not None:
                    station.hi = obs.hi
                if obs.v0 is not None:
                    station.v0 = obs.v0
            if obs.type == "xyz" and station and obs.matricule == station.matricule:
                if obs.x is not None:
                    station.x = obs.x
                if obs.y is not None:
                    station.y = obs.y
                if obs.z is not None:
                    station.z = obs.z
            if obs.type not in ["st", "xyz"] and station:
                break
        if station:
            return station, idx
        return station, -1

    @staticmethod
    def find_station_matricule_in_obs_array(
        obs_array, start_index: int = 0, end_index: int = 1024000
    ):
        station_matricule = None
        idx = start_index - 1
        for obs in obs_array[start_index:end_index]:
            idx += 1
            if idx > end_index:
                return station_matricule, -1
            if obs.type == "st":
                return obs.matricule, idx
        return station_matricule, -1

    @staticmethod
    def find_all_stations_in_obs_array(
        obs_array, start_index: int = -1, end_index: int = 1024000
    ):
        stations = []
        nb_stations = 0
        station_index = start_index
        while station_index < end_index:
            station, station_index = TopazeUtils.find_station_in_obs_array(
                obs_array, station_index + 1
            )
            if station is None:
                break
            else:
                stations.append(station)
        return stations

    @staticmethod
    def find_reference_matricule_in_obs_array(
        obs_array, start_index: int = 0, end_index: int = 1024000
    ):
        reference_matricule = None
        idx = start_index - 1
        for obs in obs_array[start_index:end_index]:
            idx += 1
            if idx > end_index:
                return reference_matricule, -1
            if obs.type == "ref":
                return obs.matricule, idx
        return reference_matricule, -1

    @staticmethod
    def find_all_station_matricules_in_obs_array(
        obs_array, start_index: int = 0, end_index: int = 1024000
    ):
        station_matricules = []
        for obs in obs_array[start_index:end_index]:
            if obs.type == "st":
                station_matricules.append(obs.matricule)
        return station_matricules

    @staticmethod
    def find_all_station_matricules_aiming_reference_in_obs_array(
        obs_array,
        reference_matricule: str = None,
        start_index: int = 0,
        end_index: int = -1,
    ):
        if end_index == -1:
            end_index = len(obs_array)
        station_matricules = []
        station_matricule = None
        for obs in obs_array[start_index:end_index]:
            if obs.type == "st":
                station_matricule = obs.matricule
            elif obs.type == "ref" and obs.matricule == reference_matricule:
                station_matricules.append(station_matricule)
            elif (
                obs.type == "ref"
                and reference_matricule
                and obs.matricule != reference_matricule
            ):
                print(obs.matricule)
            elif obs.type == "ref" and not reference_matricule:
                station_matricules.append(obs.matricule)
        return station_matricules

    @staticmethod
    def find_all_reference_matricules_in_obs_array(
        obs_array,
        station_matricule: str = None,
        start_index: int = 0,
        end_index: int = 1024000,
    ):
        reference_matricules = []
        from_station = True
        if station_matricule:
            from_station = False
        for obs in obs_array[start_index:end_index]:
            if obs.type == "st" and obs.matricule == station_matricule:
                from_station = True
            elif (
                obs.type == "st"
                and station_matricule
                and obs.matricule != station_matricule
            ):
                from_station = False
            elif obs.type == "ref" and from_station:
                reference_matricules.append(obs.matricule)
        return reference_matricules

    @staticmethod
    def find_all_references_in_obs_array(
        obs_array,
        station_matricule: str = None,
        start_index: int = 0,
        end_index: int = 1024000,
    ):
        references = []
        from_station = True
        if station_matricule:
            from_station = False
        hp = None
        for obs in obs_array[start_index:end_index]:
            if obs.type == "st" and obs.matricule == station_matricule:
                from_station = True
            elif (
                obs.type == "st"
                and station_matricule
                and obs.matricule != station_matricule
            ):
                from_station = False
            elif obs.type == "hp":
                hp = obs.hp
            elif obs.type == "ref" and from_station:
                if hp and not obs.hp:
                    obs.hp = hp
                references.append(obs)
        return references

    @staticmethod
    def get_coordinates_from_references(references, ptopo_array, ptopo_layer):
        for ref in references:
            pt = TopazeUtils.get_ptopo_by_matricule(
                ref.matricule, ptopo_array, ptopo_layer
            )
            if pt:
                ref.x = pt.x
                ref.y = pt.y
                ref.z = pt.z

    @staticmethod
    def process_duplicated(old_pt, new_pt, tol_xy, tolerance_z, **options):
        d = TopazeUtils.squared_distance2d(old_pt, new_pt)
        z = dz = None
        if new_pt.z and old_pt.z:
            dz = abs(new_pt.z - old_pt.z)
        elif old_pt.z:
            z = old_pt.z
        elif new_pt.z:
            z = new_pt.z
        tolerance_xy = tol_xy * tol_xy
        if not tolerance_z:
            tolerance_z = tol_xy
        in_tolerance_xy = "average"
        in_tolerance_z = "average"
        out_tolerance_xy = "replace"
        out_tolerance_z = "replace"
        if "in_tolerance_xy" in options:
            in_tolerance_z = in_tolerance_xy = options["in_tolerance_xy"]
        if "in_tolerance_z" in options:
            in_tolerance_z = options["in_tolerance_z"]
        if "out_tolerance_xy" in options:
            out_tolerance_z = out_tolerance_xy = options["out_tolerance_xy"]
        if "out_tolerance_z" in options:
            out_tolerance_z = options["out_tolerance_z"]
        if d <= tolerance_xy:
            if dz:
                if dz < tolerance_z:
                    if in_tolerance_z == "average":
                        z = (old_pt.z + new_pt.z) / 2.0
                    elif in_tolerance_z == "keep":
                        z = old_pt.z
                    elif in_tolerance_z == "replace":
                        z = new_pt.z
                else:
                    if out_tolerance_z == "average":
                        z = (old_pt.z + new_pt.z) / 2.0
                    elif out_tolerance_z == "keep":
                        z = old_pt.z
                    elif out_tolerance_z == "replace":
                        z = new_pt.z
            if in_tolerance_xy == "average":
                x = (old_pt.x + new_pt.x) / 2.0
                y = (old_pt.y + new_pt.y) / 2.0
            elif in_tolerance_xy == "keep":
                x = old_pt.x
                y = old_pt.y
            elif in_tolerance_xy == "replace":
                x = new_pt.x
                y = new_pt.y
        else:
            if dz:
                if dz < tolerance_z:
                    if in_tolerance_z == "average":
                        z = (old_pt.z + new_pt.z) / 2.0
                    elif in_tolerance_z == "keep":
                        z = old_pt.z
                    elif in_tolerance_z == "replace":
                        z = new_pt.z
                else:
                    if out_tolerance_z == "average":
                        z = (old_pt.z + new_pt.z) / 2.0
                    elif out_tolerance_z == "keep":
                        z = old_pt.z
                    elif out_tolerance_z == "replace":
                        z = new_pt.z
            if out_tolerance_xy == "average":
                x = (old_pt.x + new_pt.x) / 2.0
                y = (old_pt.y + new_pt.y) / 2.0
            elif out_tolerance_xy == "keep":
                x = old_pt.x
                y = old_pt.y
            elif out_tolerance_xy == "replace":
                x = new_pt.x
                y = new_pt.y
        return x, y, z

    @staticmethod
    def ptopo_exists_in_array(matricule, ptopo_array):
        """
        Return True if Ptopo with matricule exists in array

        Keyword arguments:
        matricule -- ptopo matricule (ex: 'P1')
        ptopo_array -- array Ptopo objects

        Returns:
        True - if exists False in others cases
        """
        if ptopo_array:
            for ptopo in ptopo_array:
                if matricule == ptopo.matricule and ptopo.x and ptopo.y:
                    return True
        return False

    @staticmethod
    def ptopo_exists_in_layer(matricule, ptopo_layer):
        """
        Return True if Ptopo with matricule exists in ptopo_layer

        Keyword arguments:
        matricule -- ptopo matricule (ex: 'P1')
        ptopo_layer -- layer containing Ptopo objects

        Returns:
        True - if exists False in others cases
        """
        if ptopo_layer:
            selection = ptopo_layer.getFeatures(
                QgsFeatureRequest(QgsExpression(f"\"matricule\" = '{matricule}'"))
            )
            for feature in selection:
                return True
        return False

    @staticmethod
    def get_ptopo_by_matricule(matricule, ptopo_array=None, ptopo_layer=None):
        """Return object if Ptopo with matricule exists in layer

        Keyword arguments:
        matricule -- ptopo matricule (ex: 'P1')
        ptopo_array -- array of Ptopo objects (None by default)
        layer -- layer containing Ptopo objects (None by default)

        Returns:
        ptopo - Ptopo object if found else None

        If ptopo_array and ptopo_layer are provided, function looks for Ptopo
        in ptopo_layer firstly
        """
        if ptopo_layer:
            selection = ptopo_layer.getFeatures(
                QgsFeatureRequest(QgsExpression(f"\"matricule\" = '{matricule}'"))
            )
            for feature in selection:
                ptopo = Ptopo(matricule)
                ptopo.type = feature.attribute("type_pt")
                geom = feature.geometry()
                pt = geom.constGet()
                ptopo.x = pt.x()
                ptopo.y = pt.y()
                ptopo.z = pt.z()
                ptopo.prec_xy = feature.attribute("prec_xy")
                ptopo.prec_z = feature.attribute("prec_z")
                ptopo.codes = feature.attribute("codes")
                return ptopo
        if ptopo_array:
            for ptopo in ptopo_array:
                if matricule == ptopo.matricule:
                    return ptopo
        return None

    @staticmethod
    def get_ptopo_by_matricule_in_obs_array(matricule, obs_array):
        """Return object if Ptopo with matricule exists in obs_array

        Keyword arguments:
        matricule -- ptopo matricule (ex: 'P1')
        obs_array -- array of observations

        Returns:
        ptopo - Ptopo object if found else None

        """
        if obs_array:
            for obs in obs_array:
                if obs.type == "xyz" and obs.matricule == matricule:
                    ptopo = Ptopo(matricule, obs.type, obs.x, obs.y, obs.z)
                    return ptopo
        return None

    @staticmethod
    def get_feature_by_matricule(matricule, ptopo_layer):
        """Return feature if Ptopo with matricule exists in layer

        Keyword arguments:
        matricule -- ptopo matricule (ex: 'P1')
        layer -- layer containing Ptopo objects

        Returns:
        feature - Ptopo feature if found else None

        """
        if ptopo_layer:
            selection = ptopo_layer.getFeatures(
                QgsFeatureRequest(QgsExpression(f"\"matricule\" = '{matricule}'"))
            )
            for feature in selection:
                return feature
        return None

    @staticmethod
    def find_ptopo_in_array(ptopo_array, matricule):
        """Return array index if Ptopo with matricule exists in Ptopo array

        Keyword arguments:
        ptopo_array -- array of Ptopo objects
        matricule -- ptopo matricule (ex: 'P1')

        Returns:
        idx - Ptopo index in array if found else -1

        Keyword arguments:
        ptopo_array -- array of Ptopo objects
        matricule -- ptopo matricule (ex: 'P1')

        Returns:
        idx - Ptopo index in array if found else -1
        """
        idx = -1
        for ptopo in ptopo_array:
            idx = idx + 1
            if matricule == ptopo.matricule:
                return idx
        return idx

    @staticmethod
    def update_xyz_in_array(ptopo_array, matricule, x, y, z):
        """
        Update ptopo if matricule exists in Ptopo array

        Keyword arguments:
        ptopo_array -- array of Ptopo objects
        matricule -- ptopo matricule (ex: 'P1')
        x,y,z -- new coordinates

        Returns:
        True - if ptopo has been updated else False
        """
        status = False
        idx = -1
        for ptopo in ptopo_array:
            idx = idx + 1
            if matricule == ptopo.matricule:
                ptopo_array[idx].x = x
                ptopo_array[idx].y = y
                ptopo_array[idx].z = z
                return True
        return status

    @staticmethod
    def sort_measure_list_by_value(
        measure_list, attr_name: str = "value", reverse_it: bool = False
    ):
        return sorted(measure_list, key=attrgetter(attr_name), reverse=reverse_it)

    @staticmethod
    def parse_field_rows(field_rows):
        """_summary_

        :param field_data: list of observations lines
        :type field_data: str
        :return:  list of obs as liste of str and list of dict
        :rtype: tuple
        """
        obs_array = []
        # obs_array = [
        #    Observation(*TopazeUtils.types_obs(row), TopazeUtils.obs_str_to_dict(row))
        #    for row in field_rows
        # ]
        for row in field_rows:
            type_obs, type_pt = TopazeUtils.types_obs(row)
            obs_dict = TopazeUtils.obs_str_to_dict(row)
            obs = Observation(type_obs, obs_dict)
            obs_array.append(obs)
        return (field_rows, obs_array)

    @staticmethod
    def upsert_ptopo_in_layer(ptopo, refresh: bool = True, iface: QgisInterface = None):
        """Create point"""
        # print("upsert_ptopo_in_layer)
        try:
            fid = None
            created = False
            ptopo_layer = TopazeLayers.ptopo_layer()
            config = TopazeConfig.config_dict()
            tol_xy = config["duplicated"]["xy_treatment"]["dup_tolerance_xy"]
            tol_z = config["duplicated"]["z_treatment"]["dup_tolerance_z"]
            with edit(ptopo_layer):
                feature = None
                if TopazeUtils.ptopo_exists_in_layer(ptopo.matricule, ptopo_layer):
                    feature = TopazeUtils.get_feature_by_matricule(
                        ptopo.matricule, ptopo_layer=ptopo_layer
                    )
                    old_ptopo = TopazeUtils.get_ptopo_by_matricule(
                        ptopo.matricule, ptopo_layer=ptopo_layer
                    )
                    x, y, z = TopazeUtils.process_duplicated(
                        old_ptopo, ptopo, tol_xy, tol_z
                    )
                    ptopo.x, ptopo.y, ptopo.z = x, y, z
                if ptopo.z is not None:
                    wkt = f"Point({ptopo.x} {ptopo.y} {ptopo.z})"
                else:
                    wkt = f"Point({ptopo.x} {ptopo.y} {PtopoConst.UNKNOWN_Z})"
                geom = QgsGeometry.fromWkt(wkt)
                if feature:
                    fid = feature.id()
                    feature["type_pt"] = ptopo.type
                    feature["prec_xy"] = ptopo.prec_xy
                    feature["prec_z"] = ptopo.prec_z
                    # codes has been added after first publishing
                    if ptopo.codes:
                        i = ptopo_layer.fields().indexFromName("codes")
                        if i >= 0:
                            feature["codes"] = ptopo.codes
                    ptopo_layer.updateFeature(feature)
                else:
                    feature_dict = {}
                    i = ptopo_layer.fields().indexFromName("matricule")
                    if i >= 0:
                        feature_dict.update({i: ptopo.matricule})
                    i = ptopo_layer.fields().indexFromName("type_pt")
                    if i >= 0:
                        feature_dict.update({i: ptopo.type})
                    i = ptopo_layer.fields().indexFromName("prec_xy")
                    if i >= 0:
                        feature_dict.update({i: ptopo.prec_xy})
                    i = ptopo_layer.fields().indexFromName("prec_z")
                    if i >= 0:
                        feature_dict.update({i: ptopo.prec_z})
                    i = ptopo_layer.fields().indexFromName("codes")
                    if i >= 0:
                        if ptopo.codes:
                            feature_dict.update({i: ptopo.codes})

                    feature = QgsVectorLayerUtils.createFeature(
                        ptopo_layer, geom, feature_dict
                    )
                    ptopo_layer.addFeature(feature)
                    created = True

                    fid = feature.id()

                if refresh and iface:
                    # If caching is enabled, a simple canvas refresh might not be sufficient
                    # to trigger a redraw and you must clear the cached image for the layer
                    if iface.mapCanvas().isCachingEnabled():
                        ptopo_layer.triggerRepaint()
                    else:
                        iface.mapCanvas().refresh()

        except Exception as e:
            print(str(e))

        else:
            if TopazeUtils.ptopo_exists_in_layer(ptopo.matricule, ptopo_layer):
                feature = TopazeUtils.get_feature_by_matricule(
                    ptopo.matricule, ptopo_layer=ptopo_layer
                )
                if feature:
                    fid = feature.id()

        return fid, created
