"""
=============================================================================
第 11 讲 · 工具模块：一个虚构的冲突事件数据库（仿 ACLED）+ 几篇"网上的报道"
=============================================================================

对应讲义：lectures/lecture11_datastorm.md 第 0.4 节

-----------------------------------------------------------------------------
【这个模块做什么？】
-----------------------------------------------------------------------------
DataSTORM 论文的新数据集基于 ACLED（Armed Conflict Location & Event Data，一个由非营利组织维护的
冲突事件数据库：自 2005 年以来全世界的暴力冲突和抗议，每一件都人工标注）。论文用的部分有 163 万行、31 列。

这里生成一个虚构的、小得多的版本：四个虚构的国家，2020 年 1 月到 2024 年 12 月，约 1.4 万件事件，
每件事件一行：

    event_id, event_date, year, month, country, admin1（省）, event_type, disorder_type,
    actor1, assoc_actor_1, actor2, fatalities, notes

数据里**埋进了 7 条"标准洞见"**（研究者应该发现的模式），见 GOLD；另有 6 篇虚构的"网上的报道"（WEB），
提供背景（例如某项政策从哪个月开始），其中一篇的说法是**误导性的**（"自从和平政策以来，暴力全面下降"——
数据显示针对平民的暴力其实上升了）。

    G1 Veloria：2022 年 8 月"全面和平"政策之后，战斗（Battles）每月减少约 40%
    G2 Veloria：同一时期，针对平民的暴力（Violence against civilians）每月增加约 35%
    G3 Kestavia：2023 年 3 月抗议（Protests）激增（取消燃油补贴之后）
    G4 Orundi：约 70% 的死亡集中在 Northam 省
    G5 Kestavia：教师工会参与的抗议，大多数情况下它是"关联行为者"（assoc_actor_1），
       只看 actor1 会少算一半以上
    G6 Veloria：遥控爆炸 / 远程暴力（Explosions/Remote violence）2020→2024 年增加到两倍多
    G7 Pallasca：战斗集中在旱季（12 月到次年 3 月）

国家、省、行为者、报道全部是虚构的。

-----------------------------------------------------------------------------
【用法】
-----------------------------------------------------------------------------
    from conflict_world import make_world
    w = make_world(seed=0)
    w.query("SELECT country, COUNT(*) FROM events GROUP BY country")
"""

import random
import sqlite3
from dataclasses import dataclass

COUNTRIES = {
    "Veloria": ["Ardent", "Brisa", "Coral", "Dunes"],
    "Kestavia": ["Eastmarch", "Fenwick", "Glade", "Harbor"],
    "Orundi": ["Northam", "Ostend", "Pike", "Quell"],
    "Pallasca": ["Riverside", "Saltmere", "Tarn", "Upland"],
}
EVENT_TYPES = ["Battles", "Violence against civilians", "Explosions/Remote violence", "Protests", "Riots",
               "Strategic developments"]
DISORDER = {"Battles": "Political violence", "Violence against civilians": "Political violence",
            "Explosions/Remote violence": "Political violence", "Protests": "Demonstrations", "Riots": "Demonstrations",
            "Strategic developments": "Strategic developments"}
# 每个国家每种事件的基础月发生数
BASE = {
    "Veloria": {"Battles": 30, "Violence against civilians": 20, "Explosions/Remote violence": 6, "Protests": 12,
                "Riots": 4, "Strategic developments": 3},
    "Kestavia": {"Battles": 4, "Violence against civilians": 5, "Explosions/Remote violence": 1, "Protests": 30,
                 "Riots": 8, "Strategic developments": 2},
    "Orundi": {"Battles": 22, "Violence against civilians": 15, "Explosions/Remote violence": 5, "Protests": 8,
               "Riots": 3, "Strategic developments": 2},
    "Pallasca": {"Battles": 14, "Violence against civilians": 8, "Explosions/Remote violence": 2, "Protests": 16,
                 "Riots": 5, "Strategic developments": 2},
}
ARMED = {"Veloria": ["Velorian Liberation Front", "Coral Militia"], "Kestavia": ["Kestavian Defense Group"],
         "Orundi": ["Northam Insurgents", "Ostend Brigade"], "Pallasca": ["Upland Rebels"]}
UNION = "Teachers' Union of Kestavia"
POLICY = "2022-08"           # Veloria 的"全面和平"政策开始的月份


@dataclass
class Insight:
    """
    一条标准洞见。

    【字段】
        gid    : 编号（"G1"）
        text   : 一句话
        key    : 用来判断"系统有没有发现它"的结构化描述：分析类型和参数
        value  : 关键的数值（变化的百分比、比例、月份……）
    """
    gid: str
    text: str
    key: tuple
    value: object


GOLD = [
    Insight("G1", "After the total peace policy (Aug 2022), monthly battles in Veloria fell by about 40%.",
            ("prepost", "Veloria", "Battles"), -0.40),
    Insight("G2", "Over the same period, violence against civilians in Veloria rose by about 35%.",
            ("prepost", "Veloria", "Violence against civilians"), 0.35),
    Insight("G3", "Protests in Kestavia spiked in March 2023, after fuel subsidies were removed.",
            ("spike", "Kestavia", "Protests"), "2023-03"),
    Insight("G4", "About 70% of fatalities in Orundi occurred in Northam province.",
            ("concentration", "Orundi", "fatalities"), ("Northam", 0.70)),
    Insight("G5", "The Teachers' Union of Kestavia took part in many protests, mostly as an associated actor.",
            ("actor", "Kestavia", UNION), None),
    Insight("G6", "Explosions/remote violence in Veloria more than doubled from 2020 to 2024.",
            ("trend", "Veloria", "Explosions/Remote violence"), 1.0),
    Insight("G7", "Battles in Pallasca are concentrated in the dry season (December to March).",
            ("season", "Pallasca", "Battles"), 0.55),
]

WEB = [
    ("2022-08-07", "Veloria's new government announced a 'total peace' policy, opening talks with armed groups.",
     {"country": "Veloria", "date": POLICY}),
    ("2023-03-02", "Kestavia removed fuel subsidies; teachers and transport unions called for nationwide demonstrations.",
     {"country": "Kestavia", "date": "2023-03"}),
    ("2024-05-11", "Observers report increasing use of drones and explosives by armed groups in Veloria.",
     {"country": "Veloria"}),
    ("2021-06-20", "Orundi's Northam province remains the epicentre of the insurgency.", {"country": "Orundi"}),
    ("2023-11-30", "Commentators claim violence in Veloria has fallen across the board since the peace policy.",
     {"country": "Veloria", "misleading": True}),
    ("2022-12-15", "The dry season traditionally brings an uptick in fighting in Pallasca.", {"country": "Pallasca"}),
]


class World:
    """数据库 + 标准洞见 + 网上的报道。world.query(sql) 执行一条只读的 SQL。"""

    def __init__(self, rows):
        self.db = sqlite3.connect(":memory:")
        self.db.execute("CREATE TABLE events (event_id INTEGER, event_date TEXT, year INTEGER, month TEXT, country TEXT, "
                        "admin1 TEXT, event_type TEXT, disorder_type TEXT, actor1 TEXT, assoc_actor_1 TEXT, actor2 TEXT, "
                        "fatalities INTEGER, notes TEXT)")
        self.db.executemany("INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", rows)
        self.n = len(rows)
        self.gold, self.web = GOLD, WEB
        self.calls = 0

    def query(self, sql):
        """执行一条 SQL，返回结果行。"""
        self.calls += 1
        return self.db.execute(sql).fetchall()


def _poisson(rng, lam):
    """泊松分布的随机数（Knuth 的方法；lam 不大时足够快）。"""
    if lam <= 0:
        return 0
    if lam > 30:                                                # 大的时候用正态近似
        return max(0, round(rng.gauss(lam, lam ** 0.5)))
    import math
    L, k, p = math.exp(-lam), 0, 1.0
    while True:
        k += 1
        p *= rng.random()
        if p <= L:
            return k - 1


def make_world(seed=0):
    """
    生成数据库。同一个 seed 总是生成同一个世界。

    【思路】对每个国家、每个月、每种事件，按基础发生率乘以"埋进去的效应"得到当月的期望次数，
        用泊松分布抽出实际次数；每件事件随机分配省份、行为者、死亡人数。
    【返回】World
    """
    rng = random.Random(f"conflict|{seed}")
    rows, eid = [], 0
    months = [f"{y}-{m:02d}" for y in range(2020, 2025) for m in range(1, 13)]
    for country, provinces in COUNTRIES.items():
        for i, month in enumerate(months):
            y, m = int(month[:4]), int(month[5:])
            for et in EVENT_TYPES:
                lam = BASE[country][et] * rng.uniform(0.85, 1.15)
                if country == "Veloria" and month >= POLICY:
                    lam *= {"Battles": 0.6, "Violence against civilians": 1.35}.get(et, 1.0)       # G1、G2
                if country == "Veloria" and et == "Explosions/Remote violence":
                    lam *= 1 + 0.35 * (y - 2020)                                                   # G6
                if country == "Kestavia" and et == "Protests" and month == "2023-03":
                    lam *= 5                                                                       # G3
                if country == "Pallasca" and et == "Battles" and m in (12, 1, 2, 3):
                    lam *= 2.5                                                                     # G7
                for _ in range(_poisson(rng, lam)):
                    eid += 1
                    if country == "Orundi" and et in ("Battles", "Violence against civilians", "Explosions/Remote violence"):
                        prov = "Northam" if rng.random() < 0.45 else rng.choice(provinces[1:])   # G4（加上更高的死亡数）
                    else:
                        prov = rng.choice(provinces)
                    a1, assoc, a2 = _actors(country, et, rng)
                    fat = _fatalities(et, rng) * (3 if country == "Orundi" and prov == "Northam" else 1)
                    day = rng.randint(1, 28)
                    rows.append((eid, f"{month}-{day:02d}", y, month, country, prov, et, DISORDER[et], a1, assoc, a2, fat,
                                 f"{a1} {'clashed with' if a2 else 'gathered in'} {a2 or prov}."))
    return World(rows)


def _actors(country, et, rng):
    """一件事件的行为者：(actor1, assoc_actor_1, actor2)。教师工会大多出现在 assoc_actor_1 里（G5）。"""
    state = f"Military Forces of {country}"
    if et in ("Protests", "Riots"):
        crowd = f"{'Protesters' if et == 'Protests' else 'Rioters'} ({country})"
        if country == "Kestavia" and et == "Protests" and rng.random() < 0.3:
            if rng.random() < 0.7:
                return crowd, UNION, None                       # 工会是关联行为者
            return UNION, None, None                            # 工会是主要行为者
        return crowd, None, (f"Police Forces of {country}" if rng.random() < 0.3 else None)
    if et == "Violence against civilians":
        return rng.choice(ARMED[country] + [state]), None, f"Civilians ({country})"
    if et == "Strategic developments":
        return rng.choice(ARMED[country] + [state]), None, None
    return rng.choice(ARMED[country]), None, state


def _fatalities(et, rng):
    """死亡人数（战斗和爆炸多一些，抗议几乎为零）。"""
    return {"Battles": _poisson(rng, 3), "Violence against civilians": _poisson(rng, 1.5),
            "Explosions/Remote violence": _poisson(rng, 2), "Riots": _poisson(rng, 0.2)}.get(et, 0)


if __name__ == "__main__":
    w = make_world()
    print(w.n, "行")
    for row in w.query("SELECT country, COUNT(*), SUM(fatalities) FROM events GROUP BY country"):
        print(row)
