import qgis.processing as processing
from qgis.core import (
    QgsProcessing,
    QgsProcessingAlgorithm,
    QgsProcessingException,
    QgsProcessingMultiStepFeedback,
    QgsProcessingParameterCrs,
    QgsProcessingParameterExtent,
    QgsProcessingParameterFeatureSink,
    QgsProcessingParameterNumber,
    QgsRectangle,
)


class RotatedGridPointCreationAlgorihtm(QgsProcessingAlgorithm):
    EXTENT = "EXTENT"
    CRS = "CRS"
    NB_ROW = "NB_ROW"
    NB_COL = "NB_COL"
    HSPACING = "HSPACING"
    VSPACING = "VSPACING"
    ANGLE = "ANGLE"

    OUTPUT = "OUTPUT"

    def initAlgorithm(self, config=None):

        self.addParameter(
            QgsProcessingParameterExtent(self.EXTENT, "grid_extent", defaultValue=None)
        )

        self.addParameter(
            QgsProcessingParameterNumber(
                self.NB_ROW, "nb_row", defaultValue=None, minValue=2, optional=True
            )
        )
        self.addParameter(
            QgsProcessingParameterNumber(
                self.NB_COL, "nb_col", defaultValue=None, minValue=2, optional=True
            )
        )

        self.addParameter(
            QgsProcessingParameterNumber(
                self.HSPACING,
                "horizontal spacing",
                defaultValue=None,
                minValue=1,
                type=QgsProcessingParameterNumber.Double,
                optional=True,
            )
        )

        self.addParameter(
            QgsProcessingParameterNumber(
                self.VSPACING,
                "vertical spacing",
                defaultValue=None,
                minValue=1,
                type=QgsProcessingParameterNumber.Double,
                optional=True,
            )
        )

        self.addParameter(
            QgsProcessingParameterNumber(
                self.ANGLE,
                "rotation",
                type=QgsProcessingParameterNumber.Double,
                defaultValue=0.0,
            )
        )

        self.addParameter(
            QgsProcessingParameterCrs(self.CRS, "CRS", defaultValue="EPSG:3857")
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                "result",
                type=QgsProcessing.TypeVectorPoint,
                createByDefault=True,
                supportsAppend=True,
                defaultValue=None,
            )
        )

    @staticmethod
    def horizontal_spacing_to_nb_col(
        extent: QgsRectangle, horizontal_spacing: float
    ) -> int:
        nb_col = int(extent.width() / horizontal_spacing) + 1
        return nb_col

    @staticmethod
    def vertical_spacing_to_nb_row(
        extent: QgsRectangle, vertical_spacing: float
    ) -> int:
        nb_row = int(extent.height() / vertical_spacing) + 1
        return nb_row

    @staticmethod
    def nb_col_to_horizontal_spacing(extent: QgsRectangle, nb_col: int) -> float:
        # Remove 0.0000000001 meters to be sure to have last point
        return extent.width() / (nb_col - 1) - 0.0000000001

    @staticmethod
    def nb_row_to_vertical_spacing(extent: QgsRectangle, nb_row: int) -> float:
        # Remove 0.0000000001 meters to be sure to have last point
        return extent.height() / (nb_row - 1) - 0.0000000001

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(2, model_feedback)
        results = {}
        outputs = {}

        extent = self.parameterAsExtent(
            parameters, self.EXTENT, context, parameters[self.CRS]
        )

        if self.HSPACING in parameters:
            horizontal_spacing = parameters[self.HSPACING]
        elif self.NB_COL in parameters:
            horizontal_spacing = self.nb_col_to_horizontal_spacing(
                extent, parameters[self.NB_COL]
            )
        else:
            raise QgsProcessingException(
                self.tr(
                    "{0} and {1] not defined. Can't define horizontal spacing for grid"
                )
                .format(self.HSPACING)
                .format(self.NB_ROW)
            )

        if self.VSPACING in parameters:
            vertical_spacing = parameters[self.VSPACING]
        elif self.NB_ROW in parameters:
            vertical_spacing = self.nb_row_to_vertical_spacing(
                extent, parameters[self.NB_ROW]
            )
        else:
            raise QgsProcessingException(
                self.tr(
                    "{0} and {1] not defined. Can't define vertical spacing for grid"
                )
                .format(self.VSPACING)
                .format(self.NB_ROW)
            )
        # Create grid
        alg_params = {
            "CRS": parameters[self.CRS],
            "EXTENT": extent,
            "TYPE": 0,  # Point
            "HSPACING": horizontal_spacing,
            "VSPACING": vertical_spacing,
            # Remove 0.1 meters to be sure to have last point
            "HOVERLAY": 0,
            "VOVERLAY": 0,
            "OUTPUT": "TEMPORARY_OUTPUT",
        }
        outputs["CreateGrid"] = processing.run(
            "native:creategrid",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )
        results["Grid"] = outputs["CreateGrid"]["OUTPUT"]

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Rotate
        alg_params = {
            "ANCHOR": extent.center(),
            "ANGLE": parameters[self.ANGLE],
            "INPUT": outputs["CreateGrid"]["OUTPUT"],
            "OUTPUT": parameters[self.OUTPUT],
        }
        outputs["Rotate"] = processing.run(
            "native:rotatefeatures",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )
        results[self.OUTPUT] = outputs["Rotate"]["OUTPUT"]

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        return results

    def name(self):
        return "rotated_grid_point_creation"

    def displayName(self):
        return "Create point on grid with rotation"

    def group(self):
        return ""

    def groupId(self):
        return ""

    def createInstance(self):
        return RotatedGridPointCreationAlgorihtm()
