import math

from qgis.core import QgsCoordinateReferenceSystem, QgsLineString, QgsPoint
from xplordb.datamodel.collar import Collar
from xplordb.datamodel.survey import Survey

from openlog.datamodel.connection.spatialite.database_object import convert_to_db_crs


def minimum_curvature_method(
    surveys: [Survey], collar: Collar, display_geom: bool
) -> QgsLineString:
    """
    Define QgsLineString vertex with Minimum curvature method implementation from https://www.drillingformulas.com/minimum-curvature-method/

        Args:
            surveys: list of surveys. If None, it's considered as planned trace.
            collar: collar

    """
    result = QgsLineString()
    current_x = collar.x
    current_y = collar.y
    current_z = collar.z

    eoh = collar.eoh
    planned = False
    # planned survey : dip and azimuth are as Collar attributes
    if surveys is None:
        surveys = [
            Survey(
                hole_id=collar.hole_id,
                data_set=collar.data_set,
                loaded_by=collar.loaded_by,
                depth=0.0,
                dip=collar.dip,
                azimuth=collar.azimuth,
            )
        ]
        planned = True
        eoh = collar.planned_eoh
        current_x = collar.planned_x
        current_y = collar.planned_y
        current_z = collar.planned_z

    is_geographic = QgsCoordinateReferenceSystem(collar.srid).isGeographic()
    point = QgsPoint(x=current_x, y=current_y, z=current_z)

    # transform to 3857 is srid is not planar
    if is_geographic:
        point = convert_to_db_crs(collar.srid, point)
        current_x = point.x()
        current_y = point.y()
        current_z = point.z()

    result.addVertex(point)

    # for geometry displaying (effective_geom and planned_geom), we take max(eoh, planned_eoh)
    if display_geom:
        eohs = [
            value for value in [collar.eoh, collar.planned_eoh] if value is not None
        ]
        if len(eohs) == 0:
            return result
        eoh = max(eohs)

    if not planned and (collar.eoh is None or collar.eoh == 0):
        return result
    elif planned and (collar.planned_eoh is None or collar.planned_eoh == 0):
        return result

    surveys = add_collar_start_if_needed(surveys)

    surveys.sort(key=lambda x: x.depth)

    surveys = add_collar_end_if_needed(surveys, collar, eoh)

    surveys.sort(key=lambda x: x.depth)

    if eoh is None:
        return result

    for i in range(0, len(surveys) - 1):
        s2 = surveys[i + 1]
        s1 = surveys[i]
        # In xplordb dip is changed (for convention ?)
        i2 = math.radians(s2.dip + 90.0)
        i1 = math.radians(s1.dip + 90.0)
        a2 = math.radians(s2.azimuth)
        a1 = math.radians(s1.azimuth)
        CL = s2.depth - s1.depth
        DL = math.acos(
            math.cos(i2 - i1) - (math.sin(i1) * math.sin(i2)) * (1 - math.cos(a2 - a1))
        )

        # Formula with different https//directionaldrillingart.blogspot.com/2015/09/directional-surveying-calculations.html
        # Produce same results
        # DL = math.acos((math.sin(i1) * math.sin(i2) * math.cos(a2 - a1)) + (math.cos(i1) * math.cos(i2)))
        RF = 1.0
        if DL != 0.0:
            # Formula with different method https//directionaldrillingart.blogspot.com/2015/09/directional-surveying-calculations.html
            # RF = math.tan(DL / 2) * (180 / math.pi) * (2 / DL)
            RF = math.tan(DL / 2) * (2 / DL)

        current_x = ((math.sin(i1) * math.sin(a1)) + (math.sin(i2) * math.sin(a2))) * (
            RF * (CL / 2)
        ) + current_x
        current_y = ((math.sin(i1) * math.cos(a1)) + (math.sin(i2) * math.cos(a2))) * (
            RF * (CL / 2)
        ) + current_y
        current_z = -(math.cos(i1) + math.cos(i2)) * (CL / 2) * RF + current_z
        point = QgsPoint(x=current_x, y=current_y, z=current_z)
        result.addVertex(point)
    return result


def add_collar_start_if_needed(surveys) -> [Survey]:
    """
    Insert a survey at 0.0 depth if not available

    Args:
        surveys: list of available survey

    Returns: list of survey with value at 0.0 depth if not defined

    """
    res = surveys
    if len(res):
        res.sort(key=lambda x: x.depth)
        if res[0].depth != 0.0:
            res.append(
                Survey(
                    hole_id=res[0].hole_id,
                    data_set=res[0].data_set,
                    depth=0,
                    azimuth=res[0].azimuth,
                    dip=res[0].dip,
                    loaded_by=res[0].loaded_by,
                )
            )

    return res


def add_collar_end_if_needed(surveys, collar, eoh: float) -> [Survey]:
    """
    Insert a survey at eoh depth

    Args:
        surveys: list of available survey

    Returns: list of survey with value at eoh depth

    """

    res = surveys

    if eoh is None:
        return res
    if len(res):
        res.sort(key=lambda x: x.depth)
        if res[-1].depth != eoh:
            res.append(
                Survey(
                    hole_id=res[0].hole_id,
                    data_set=res[0].data_set,
                    depth=eoh,
                    azimuth=res[-1].azimuth,
                    dip=res[-1].dip,
                    loaded_by=res[0].loaded_by,
                )
            )

    return res
