# [SKRIP 7.3: Uji kesiapan otomatis: sepuluh keterampilan yang dipakai Masterclass Seri 1 (M1)]
# Penulis: Badar Mubarok Yogaswara
# Tiap pemeriksaan mencetak LULUS atau GAGAL beserta alasannya.
# Skor akhir muncul setelah tugas latar belakang (pemeriksaan 10) selesai.
import os
import re
import time
import processing
from qgis.core import (QgsApplication, QgsTask, QgsProject, QgsVectorLayer, QgsRasterLayer,
                       QgsExpression, QgsExpressionContext, QgsExpressionContextUtils,
                       QgsProcessingException)

DATA = "D:/Latihan/paket-i4"
HASIL = "D:/Latihan/hasil_i4"
os.makedirs(HASIL, exist_ok=True)

proyek = QgsProject.instance()
proyek.removeAllMapLayers()
catatan = []                                     # daftar (nama, lulus?, alasan)
NDVI = '("Citra_KPH@4" - "Citra_KPH@1") / ("Citra_KPH@4" + "Citra_KPH@1")'
CACAH = ("aggregate('Hotspot_Nasional', 'count', \"ID_Titik\", "
         "intersects($geometry, geometry(@parent)))")


def periksa(nama, fungsi):
    try:
        alasan = fungsi()
        catatan.append((nama, True, alasan or ""))
    except Exception as e:           # pemeriksaan yang gagal dicatat; skrip tidak berhenti
        catatan.append((nama, False, "%s: %s" % (type(e).__name__, e)))
    n, ok, alasan = catatan[-1]
    print("%-46s %s  %s" % (n, "LULUS" if ok else "GAGAL", alasan))


def tegaskan(syarat, pesan):
    if not syarat:
        raise AssertionError(pesan)


batas = QgsVectorLayer(DATA + "/Batas_KPH.gpkg", "Batas_KPH", "ogr")
hotspot = QgsVectorLayer(DATA + "/Hotspot_Nasional.gpkg", "Hotspot_Nasional", "ogr")
citra = QgsRasterLayer(DATA + "/Citra_KPH.tif", "Citra_KPH", "gdal")


def konteks(fitur):
    c = QgsExpressionContext()
    c.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(batas))
    c.setFeature(fitur)
    return c


def k1():
    for lyr in (batas, hotspot, citra):
        tegaskan(lyr.isValid(), lyr.name() + " tidak sah")
        proyek.addMapLayer(lyr)
    tegaskan(len(proyek.mapLayers()) == 3, "jumlah layer tidak 3")


def k2():
    tegaskan(proyek.mapLayersByName("Batas_KPH")[0].featureCount() == 3, "Batas_KPH bukan 3 fitur")
    r = proyek.mapLayersByName("Citra_KPH")[0]
    tegaskan(r.bandCount() == 4 and r.rasterUnitsPerPixelX() == 2.0, "raster tidak sesuai")
    tegaskan(proyek.mapLayersByName("Tidak_Ada") == [], "nama tak ada harus memberi daftar kosong")
    return "band=%d, piksel=%.0f m" % (r.bandCount(), r.rasterUnitsPerPixelX())


def k3():
    v = processing.run("native:buffer", {"INPUT": batas, "DISTANCE": 10,
                                         "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
    tegaskan(isinstance(v, QgsVectorLayer) and v.featureCount() == 3, "bukan layer vektor")
    r = processing.run("native:rastercalc", {"EXPRESSION": '"Citra_KPH@4" - "Citra_KPH@1"',
                                             "LAYERS": [citra], "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
    tegaskan(isinstance(r, str) and os.path.exists(r), "keluaran raster bukan jalur berkas")
    return "vektor -> layer, raster -> jalur teks"


def k4():
    jalur = processing.run("native:rastercalc", {"EXPRESSION": NDVI, "LAYERS": [citra],
                                                 "OUTPUT": "TEMPORARY_OUTPUT"})["OUTPUT"]
    st = processing.run("native:rasterlayerstatistics",
                        {"INPUT": QgsRasterLayer(jalur, "ndvi", "gdal"), "BAND": 1})
    tegaskan(-1 <= st["MIN"] <= st["MAX"] <= 1, "NDVI di luar -1..1")
    return "NDVI %.2f sampai %.2f" % (st["MIN"], st["MAX"])


def k5():
    e = QgsExpression(CACAH)
    hasil = {f["NAMA_KPH"]: e.evaluate(konteks(f)) for f in batas.getFeatures()}
    benar = {f["NAMA_KPH"]: sum(1 for h in hotspot.getFeatures()
                                if h.geometry().intersects(f.geometry())) for f in batas.getFeatures()}
    tegaskan(len(hasil) == 3, "agregasi tidak menghasilkan 3 nilai: %s" % hasil)
    tegaskan(hasil == benar, "agregasi %s beda dari hitungan manual %s" % (hasil, benar))
    return str(hasil)


def k6():
    e = QgsExpression("'Titik api: ' || " + CACAH + " || ' titik'")
    beta = [x for x in batas.getFeatures() if x["NAMA_KPH"] == "KPH Beta"][0]
    teks = e.evaluate(konteks(beta))
    tegaskan(not e.hasEvalError() and teks == "Titik api: 5 titik", "teks label salah: %s" % teks)
    return teks


def k7():
    try:
        processing.run("native:buffer", {"INPUT": DATA + "/tidak_ada.gpkg", "DISTANCE": 5,
                                         "OUTPUT": "TEMPORARY_OUTPUT"})
    except QgsProcessingException:
        return "galat alat tertangkap"
    raise AssertionError("seharusnya gagal")


def k8():
    lyr = QgsVectorLayer(DATA + "/Kiriman_Lapangan/tim_e.gpkg", "rusak", "ogr")
    tegaskan(not lyr.isValid(), "berkas rusak dianggap sah")
    return "isValid() = False untuk berkas rusak"


def k9():
    berkas = HASIL + "/uji_kesiapan.qgz"
    tegaskan(proyek.write(berkas), "gagal menulis proyek")
    proyek.clear()
    tegaskan(len(proyek.mapLayers()) == 0, "proyek belum kosong")
    tegaskan(proyek.read(berkas) and len(proyek.mapLayers()) == 3, "proyek tidak terbaca utuh")
    return "tulis, kosongkan, baca ulang: 3 layer"


daftar = [("1. Muat vektor dan raster [Bab 1]", k1), ("2. Ambil layer dengan nama [Bab 1]", k2),
          ("3. processing.run sementara [Bab 2]", k3), ("4. Raster calculator NDVI [Bab 2]", k4),
          ("5. Agregasi dan @parent [Bab 5]", k5), ("6. Teks label dari ekspresi [Bab 5]", k6),
          ("7. Menangkap galat alat [Bab 4]", k7), ("8. Mendeteksi berkas rusak [Bab 4]", k8),
          ("9. Simpan dan buka proyek [Bab 1]", k9)]
for nama, fungsi in daftar:
    periksa(nama, fungsi)


# 10. Tugas latar belakang. Hasilnya tiba belakangan, jadi skor akhir dicetak di selesai().
def kerja(task):
    total = 0
    for i in range(1, 6):
        if task.isCanceled():
            return None
        time.sleep(0.05)
        total += i
        task.setProgress(i * 20)
    return total


def selesai(exception, nilai=None):
    ok = exception is None and nilai == 15
    catatan.append(("10. QgsTask sederhana [Bab 3]", ok, "hasil tugas = %s" % nilai))
    print("%-46s %s  hasil tugas = %s" % (catatan[-1][0], "LULUS" if ok else "GAGAL", nilai))
    lulus = sum(1 for _, o, _ in catatan if o)
    gagal = sorted({re.search(r"\[(Bab \d)\]", n).group(1) for n, o, _ in catatan if not o})
    akhir = "Siap masuk M1." if not gagal else "Ulangi: " + ", ".join(gagal) + "."
    print("SKOR: %d dari %d. %s" % (lulus, len(catatan), akhir))


tugas_uji = QgsTask.fromFunction("Uji tugas latar belakang", kerja, on_finished=selesai)
QgsApplication.taskManager().addTask(tugas_uji)
