# -*- coding: utf-8 -*-
"""Camera Trap Grid Generator — Core Engine.

Generates camera trap placement grids using 4 placement strategies:
1. Regular Grid — systematic grid over area
2. Intensive Grid — denser grid with sub-cameras at radius
3. Targeted Radial — concentric rings from a detection point
4. Point-based — single point with satellite cameras

Scientific basis:
    - Systematic sampling (Karanth & Nichols, 2002)
    - Camera trap density: 1 per 2-4 km² (tiger) to 1 per 0.25 km² (small mammals)
    - Concentric ring spacing follows golden ratio for optimal coverage

Author: Loc Vu Trung tong hop vi dam me cung ban!
"""

import math

from qgis.core import (
    QgsPointXY, QgsGeometry, QgsFeature, QgsField,
    QgsVectorFileWriter, QgsCoordinateTransformContext, QgsFields, QgsWkbTypes
)
from qgis.PyQt.QtCore import QVariant


def _create_fields():
    """Create attribute fields for camera trap points."""
    fields = QgsFields()
    fields.append(QgsField("trap_id", QVariant.String, "varchar", 20))
    fields.append(QgsField("trap_type", QVariant.String, "varchar", 20))
    fields.append(QgsField("row", QVariant.Int))
    fields.append(QgsField("col", QVariant.Int))
    fields.append(QgsField("ring", QVariant.Int))
    fields.append(QgsField("x", QVariant.Double, "double", 15, 6))
    fields.append(QgsField("y", QVariant.Double, "double", 15, 6))
    fields.append(QgsField("notes", QVariant.String, "varchar", 200))
    return fields


def generate_regular_grid(extent, spacing_x, spacing_y, prefix="CT"):
    """Generate a regular grid of camera traps within extent.

    Args:
        extent: QgsRectangle defining the area.
        spacing_x: Grid spacing in X direction (map units).
        spacing_y: Grid spacing in Y direction (map units).
        prefix: Trap ID prefix.

    Returns:
        list of QgsFeature with point geometries.
    """
    features = []
    x_min = extent.xMinimum()
    y_min = extent.yMinimum()
    x_max = extent.xMaximum()
    y_max = extent.yMaximum()

    fields = _create_fields()
    row = 0
    y = y_min
    trap_num = 1
    while y <= y_max:
        col = 0
        x = x_min
        while x <= x_max:
            feat = QgsFeature(fields)
            feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
            feat.setAttributes([
                f"{prefix}{trap_num:04d}", "primary",
                row, col, 0,
                round(x, 6), round(y, 6),
                "Regular grid"
            ])
            features.append(feat)
            trap_num += 1
            col += 1
            x += spacing_x
        row += 1
        y += spacing_y

    return features, fields


def generate_intensive_grid(extent, spacing_x, spacing_y,
                            sub_radius, sub_count=4, prefix="CT"):
    """Generate grid with main + sub cameras at each position.

    Each main camera gets `sub_count` satellite cameras placed
    at `sub_radius` distance in cardinal / ordinal directions.

    Args:
        extent: QgsRectangle.
        spacing_x, spacing_y: Main grid spacing.
        sub_radius: Distance of sub cameras from main.
        sub_count: Number of sub cameras (4 or 8).
        prefix: Trap ID prefix.

    Returns:
        list of QgsFeature, QgsFields.
    """
    main_features, fields = generate_regular_grid(
        extent, spacing_x, spacing_y, prefix)

    all_features = []
    trap_num = 1

    for mf in main_features:
        # Main camera
        pt = mf.geometry().asPoint()
        feat = QgsFeature(fields)
        feat.setGeometry(QgsGeometry.fromPointXY(pt))
        feat.setAttributes([
            f"{prefix}{trap_num:04d}", "main",
            mf["row"], mf["col"], 0,
            round(pt.x(), 6), round(pt.y(), 6),
            "Main camera"
        ])
        all_features.append(feat)
        trap_num += 1

        # Sub cameras in cardinal/ordinal directions
        if sub_count == 4:
            angles = [0, 90, 180, 270]
        else:
            angles = [0, 45, 90, 135, 180, 225, 270, 315]

        for i, angle_deg in enumerate(angles):
            angle_rad = math.radians(angle_deg)
            sx = pt.x() + sub_radius * math.cos(angle_rad)
            sy = pt.y() + sub_radius * math.sin(angle_rad)

            sf = QgsFeature(fields)
            sf.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(sx, sy)))
            sf.setAttributes([
                f"{prefix}{trap_num:04d}", "sub",
                mf["row"], mf["col"], 0,
                round(sx, 6), round(sy, 6),
                f"Sub camera #{i+1} ({angle_deg}°)"  # noqa: E226
            ])
            all_features.append(sf)
            trap_num += 1

    return all_features, fields


def generate_radial_rings(center_x, center_y, ring_spacing,
                          ring_count, points_per_ring=8,
                          prefix="CT"):
    """Generate concentric rings of cameras from a detection point.

    Used when an animal is detected at a location and cameras need
    to be deployed in expanding rings around it.

    Args:
        center_x, center_y: Detection point coordinates.
        ring_spacing: Distance between rings (map units).
        ring_count: Number of rings.
        points_per_ring: Cameras per ring.
        prefix: Trap ID prefix.

    Returns:
        list of QgsFeature, QgsFields.
    """
    fields = _create_fields()
    features = []
    trap_num = 1

    # Center point (detection location)
    cf = QgsFeature(fields)
    cf.setGeometry(QgsGeometry.fromPointXY(
        QgsPointXY(center_x, center_y)))
    cf.setAttributes([
        f"{prefix}{trap_num:04d}", "center",
        0, 0, 0,
        round(center_x, 6), round(center_y, 6),
        "Detection point (center)"
    ])
    features.append(cf)
    trap_num += 1

    for ring in range(1, ring_count + 1):
        radius = ring * ring_spacing
        for i in range(points_per_ring):
            angle_deg = (360.0 / points_per_ring) * i
            angle_rad = math.radians(angle_deg)
            px = center_x + radius * math.cos(angle_rad)
            py = center_y + radius * math.sin(angle_rad)

            feat = QgsFeature(fields)
            feat.setGeometry(
                QgsGeometry.fromPointXY(QgsPointXY(px, py)))
            feat.setAttributes([
                f"{prefix}{trap_num:04d}", "ring",
                0, i, ring,
                round(px, 6), round(py, 6),
                f"Ring {ring}, pos {i+1}/{points_per_ring}"  # noqa: E226
            ])
            features.append(feat)
            trap_num += 1

    return features, fields


def generate_targeted_point(center_x, center_y,
                            sub_radius, sub_count=4,
                            prefix="CT"):
    """Generate cameras around a single targeted location.

    Args:
        center_x, center_y: Target point.
        sub_radius: Satellite camera radius.
        sub_count: Number of satellite cameras.
        prefix: Trap ID prefix.

    Returns:
        list of QgsFeature, QgsFields.
    """
    fields = _create_fields()
    features = []
    trap_num = 1

    # Center
    cf = QgsFeature(fields)
    cf.setGeometry(QgsGeometry.fromPointXY(
        QgsPointXY(center_x, center_y)))
    cf.setAttributes([
        f"{prefix}{trap_num:04d}", "target",
        0, 0, 0,
        round(center_x, 6), round(center_y, 6),
        "Targeted location"
    ])
    features.append(cf)
    trap_num += 1

    angles = [(360.0 / sub_count) * i for i in range(sub_count)]
    for i, angle_deg in enumerate(angles):
        angle_rad = math.radians(angle_deg)
        sx = center_x + sub_radius * math.cos(angle_rad)
        sy = center_y + sub_radius * math.sin(angle_rad)

        sf = QgsFeature(fields)
        sf.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(sx, sy)))
        sf.setAttributes([
            f"{prefix}{trap_num:04d}", "satellite",
            0, i, 1,
            round(sx, 6), round(sy, 6),
            f"Satellite #{i+1} ({angle_deg:.0f}°)"  # noqa: E226
        ])
        features.append(sf)
        trap_num += 1

    return features, fields


def export_features(features, fields, crs, output_path, driver="GPKG"):
    """Export features to file.

    Args:
        features: list of QgsFeature.
        fields: QgsFields.
        crs: QgsCoordinateReferenceSystem.
        output_path: Output file path.
        driver: OGR driver name (GPKG, ESRI Shapefile, CSV,
                GeoJSON, KML).

    Returns:
        output_path on success, None on error.
    """
    opts = QgsVectorFileWriter.SaveVectorOptions()
    opts.driverName = driver
    opts.fileEncoding = "UTF-8"

    writer = QgsVectorFileWriter.create(
        output_path, fields,
        QgsWkbTypes.Point, crs,
        QgsCoordinateTransformContext(),
        opts)

    if writer.hasError() != QgsVectorFileWriter.NoError:
        return None

    for feat in features:
        writer.addFeature(feat)

    del writer
    return output_path
