#!/usr/bin/env python

# kismet2earth 1.0
# Author: Andrea Grandi <a.grandi AT gmail com>
# License: GPL2
# Copyright: Andrea Grandi 2010 - This cose is partially based on PyKismetKml (http://code.google.com/p/pykismetkml)

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

from __future__ import annotations

import argparse
import sqlite3
from pathlib import Path
import xml.dom.minidom
from xml.sax.saxutils import escape


def ext_data(nodelist, tagname: str, index: int = 0, branch: bool = False):
    """
    If branch=True: returns a NodeList of children with tagname
    Else: returns the text content for the first matching child tag.
    """
    if branch:
        return nodelist.item(index).getElementsByTagName(tagname)

    try:
        node = nodelist.item(index).getElementsByTagName(tagname).item(0)
        if node is None or node.firstChild is None:
            raise AttributeError
        return node.firstChild.data
    except AttributeError:
        if tagname == "essid":
            return "?Cloaked Network?"
        return None


def get_enc(encryption_nodes) -> str:
    full = ""
    for each in encryption_nodes:
        if each.firstChild is not None:
            full += each.firstChild.data + " "
    if "WPA" in full:
        return "WPA"
    if "WEP" in full:
        return "WEP"
    return "Open"


def parse_netxml(filename: Path) -> list[tuple[str, str, str, str, str, str, str]]:
    document = xml.dom.minidom.parse(str(filename))
    networks = document.getElementsByTagName("wireless-network")

    wifi: list[tuple[str, str, str, str, str, str, str]] = []

    for index in range(networks.length):
        net = networks.item(index)
        if net.getAttribute("type") != "infrastructure":
            continue

        gpsinfo = ext_data(networks, "gps-info", index, True)
        plotlat = ext_data(gpsinfo, "avg-lat")
        plotlon = ext_data(gpsinfo, "avg-lon")
        if plotlat is None or plotlon is None:
            continue

        essid = escape(ext_data(networks, "essid", index) or "")
        encryption = net.getElementsByTagName("encryption")
        enc = get_enc(encryption)

        bssid = ext_data(networks, "BSSID", index) or ""
        manuf = ext_data(networks, "manuf", index) or ""
        channel = ext_data(networks, "channel", index) or ""

        wifi.append((bssid, essid, str(plotlat), str(plotlon), enc, manuf, channel))

    return wifi


def save_db(db_path: Path, networks: list[tuple[str, str, str, str, str, str, str]]) -> None:
    with sqlite3.connect(str(db_path)) as conn:
        cur = conn.cursor()

        for bssid, essid, lat, lon, enc, manuf, channel in networks:
            exists = cur.execute(
                "SELECT 1 FROM networks WHERE bssid = ? LIMIT 1",
                (bssid,),
            ).fetchone()

            if not exists:
                cur.execute(
                    """INSERT INTO networks(bssid, essid, lat, lon, encryption, manufacturer, channel)
                       VALUES(?, ?, ?, ?, ?, ?, ?)""",
                    (bssid, essid, lat, lon, enc, manuf, channel),
                )
            else:
                cur.execute(
                    """UPDATE networks
                       SET essid = ?, lat = ?, lon = ?, encryption = ?, manufacturer = ?, channel = ?
                       WHERE bssid = ?""",
                    (essid, lat, lon, enc, manuf, channel, bssid),
                )

        conn.commit()


def build_argparser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Import Kismet .netxml into a SQLite database.")
    p.add_argument("-i", "--input", dest="filename", required=True, type=Path, help="Path to netxml input file.")
    p.add_argument("-o", "--output", dest="database", required=True, type=Path, help="Path to SQLite database file.")
    return p


def main() -> int:
    args = build_argparser().parse_args()
    networks = parse_netxml(args.filename)
    save_db(args.database, networks)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())