import numpy as np
from qgis.core import QgsCoordinateReferenceSystem

from openlog.core.affine_transform import make_affine


def create_derived_projection_wkt(
    name: str, base_crs: QgsCoordinateReferenceSystem, src: np.array, dst: np.array
) -> str:
    """
    Create a derived projection wkt from a base crs and references points.
    Affine transformation is calculated from references points and added as a deriving conversion.

    Args:
        name: (str) name of projection in wkt
        base_crs: (QgsCoordinateReferenceSystem) projection to be used as a base (must be a projected CRS)
        src: (np.array) array with source points coordinates [[x,y,z], [x1,y1,z2], ... , [xn, yn, zn]]
        dst: (np.array) array with destination points coordinates [[x,y,z], [x1,y1,z2], ... , [xn, yn, zn]]

    Returns: (str) derived projection wkt

    """
    base, cartesian = _create_wkt_base_and_cartesian(base_crs)
    deriving_conversion = _create_deriving_conversion(src, dst)
    wkt = f'DERIVEDPROJCRS["{name}",BASE{base}],{deriving_conversion},{cartesian}]'
    return wkt


def _create_affine_length_parameter(label: str, value: float, epsg: int) -> str:
    return f'PARAMETER["{label}",{value:.10f},LENGTHUNIT["metre",1],ID["EPSG",{epsg}]]'


def _create_affine_scale_parameter(label: str, value: float, epsg: int) -> str:
    return f'PARAMETER["{label}",{value:.10f},SCALEUNIT["coefficient",1],ID["EPSG",{epsg}]]'


def _create_wkt_base_and_cartesian(crs: QgsCoordinateReferenceSystem) -> (str, str):
    base = crs.toWkt(
        variant=QgsCoordinateReferenceSystem.WktVariant.WKT2_2018, multiline=False
    )

    # Trim the Usage paramter if it exists
    pos = base.find(",USAGE[")
    if pos >= 0:
        base = base[:pos] + "]"

    pos = base.find(",CS[Cartesian")
    return base[:pos], base[pos + 1 : -1]


def _create_deriving_conversion(src: np.array, dst: np.array) -> str:
    affine_param, affine_param_explicit = make_affine(src, dst)
    deriving_projection = (
        'DERIVINGCONVERSION["Affine",'
        'METHOD["Affine parametric transformation",ID["EPSG",9624]'
        "]"
    )
    parameters = [
        _create_affine_length_parameter("A0", affine_param[0], 8623),
        _create_affine_scale_parameter("A1", affine_param[1], 8624),
        _create_affine_scale_parameter("A2", affine_param[2], 8625),
        _create_affine_length_parameter("B0", affine_param[4], 8639),
        _create_affine_scale_parameter("B1", affine_param[5], 8640),
        _create_affine_scale_parameter("B2", affine_param[6], 8641),
    ]
    deriving_projection += f',{",".join(parameters)}'
    deriving_projection += "]"
    return deriving_projection
