#!/usr/share/python2
# -*- coding: utf-8 -*-
#
# wpa_config - a config manager for wpa_supplicant
#
# Author: slowpoke <mail+git@slowpoke.io>
#
# This program is Free Software under the non-terms
# of the Anti-License. Do whatever the fuck you want.
#
# Github: https://github.com/proxypoke/wpa_config

"""wpa_config - a small helper for wpa_supplicant."""

from __future__ import print_function

import argparse
import subprocess
import os


CONFIG_ROOT = "/etc/wpa_config"
CONFIG_SELF = os.path.join(CONFIG_ROOT, "wpa_config.conf")
CONFIG_HEAD = os.path.join(CONFIG_ROOT, "wpa_supplicant.conf.head")
CONFIG_TAIL = os.path.join(CONFIG_ROOT, "wpa_supplicant.conf.tail")
NETWORK_DIR = os.path.join(CONFIG_ROOT, "networks.d")
WPA_SUPPLICANT_CONFIG = "/etc/wpa_supplicant/wpa_supplicant.conf"


def merge_files(network_dir=None):
    """Merge all files in the network_dir directory."""
    network_dir = NETWORK_DIR if network_dir is None else network_dir
    cfiles = [os.path.join(network_dir, cfile)
              for cfile in os.listdir(network_dir)
              if cfile.endswith(".conf")]
    merge = ""
    for cfile in cfiles:
        with open(cfile) as f:
            merge += f.read()
    return merge


def mkentry(ssid, **options):
    """Generic function for creating network entries in wpa_supplicant.conf.

    WARNING: Input is not checked in any way!

    IMPORTANT: If an option needs to be quoted, it needs to be given as such.
    An example would be the psk entry. You MUST use double-quotes,
    wpa_supplicant will fail to parse single-quotes.

    """
    entry = 'network={\n'
    entry += '\tssid="{}"\n'.format(ssid)
    for optname in options:
        entry += '\t{}={}\n'.format(optname, options[optname])
    entry += '}'
    return entry


def wpa_passphrase(ssid, passphrase):
    """Call wpa_passphrase to generate an entry for the config.

    Arguments:
        ssid       -- the name of the network
        passphrase -- the passphrase

    """
    return subprocess.check_output(["wpa_passphrase",
                                    ssid, '"{}"'.format(passphrase)])


def mkconfigfile(ssid, network_dir=None):
    """Create a config file in the config file directory.

    Returns:
        the created config file as a file object.

    """
    network_dir = NETWORK_DIR if network_dir is None else network_dir
    return open(os.path.join(network_dir, ssid + ".conf"), "w")


def parse_network_block(netblock):
    """Parse a network block, returning a dict of its options.

    The resulting dict can be fed back to mkentry after extracting the ssid.

    """
    lines = netblock.split("\n")
    # throw away the first and last lines, and remove indentation
    lines = [line.strip() for line in lines[1:-1]]
    options = {}
    for line in lines:
        split = line.split("=")
        option = split[0]
        if len(split) == 2:
            value = split[1]
        else:
            value = "=".join(split[1:])
        options[option] = value
    return options


def mkbackup(path):
    """Create a backup file of the given file, named ${FILE}.bkp."""
    with open(path + ".bkp", "w") as bkp_file, open(path) as orig_file:
        bkp_file.write(orig_file.read())


# Commands for the CLI frontend

def add(args):
    if args.open:
        entry = mkentry(args.ssid, key_mgmt="NONE")
    else:
        # check passphrase length
        l = len(args.passphrase)
        if l < 8 or l > 63:
            print("Passphrase must be 8..63 characters.")
            exit(1)
        entry = wpa_passphrase(args.ssid, args.passphrase)

    if args.print:
        print(entry)
        return

    try:
        cfile = mkconfigfile(args.ssid, args.directory)
        cfile.write(entry)
    except OSError, e:
        print("Couldn't write config file: {}".format(e))
        exit(1)
    cfile.close()


def make(args):
    with open(CONFIG_HEAD) as headfile:
        config_head = headfile.read()
    with open(CONFIG_TAIL) as tailfile:
        config_tail = tailfile.read()

    config = "# This file was generated by wpa_config. DO NOT EDIT."
    config += "\n\n"
    config += config_head
    config += "\n\n"
    config += merge_files()
    config += "\n\n"
    config += config_tail

    if args.print:
        print(config)
        exit(0)

    try:
        mkbackup(WPA_SUPPLICANT_CONFIG)
        print("Backup of wpa_supplicant.conf written to {}.".format(
            WPA_SUPPLICANT_CONFIG + ".bkp"))
        with open(WPA_SUPPLICANT_CONFIG, "w") as cfile:
            cfile.write(config)
    except OSError, e:
        print("Couldn't write config file: {}".format(e))
        exit(1)


def list_networks(_):
    for network in os.listdir(NETWORK_DIR):
        print(os.path.splitext(network)[0])


def delete(args):
    fullpath = os.path.join(NETWORK_DIR, args.ssid + ".conf")
    if not os.path.isfile(fullpath):
        print(
            "Network '{}' is not configured with wpa_config.".format(fullpath))
        exit(1)
    os.unlink(fullpath)


def show(args):
    fullpath = os.path.join(NETWORK_DIR, args.ssid + ".conf")
    if not os.path.isfile(fullpath):
        print(
            "Network '{}' is not configured with wpa_config.".format(fullpath))
        exit(1)
    with open(fullpath) as network:
        print(network.read())


def main():
    parser = argparse.ArgumentParser()
    commands = parser.add_subparsers()

    add_mode = commands.add_parser("add", help="add a network")
    add_mode.add_argument("ssid", type=str, help="network name")
    add_mode.add_argument("passphrase", type=str, help="wpa passphrase",
                          nargs="?", default="")
    add_mode.add_argument("-p", "--print", action="store_true",
                          help="print config instead of writing it")
    add_mode.add_argument("-d", "--directory", type=str,
                          help="directory to use as base (default: {})".format(
                              CONFIG_ROOT))
    add_mode.add_argument("-o", "--open", action="store_true",
                          help="configure for an open network")
    add_mode.set_defaults(func=add)

    delete_mode = commands.add_parser("del", help="remove a network")
    delete_mode.add_argument("ssid", type=str, help="network name")
    delete_mode.set_defaults(func=delete)

    make_mode = commands.add_parser("make", help="create the config")
    make_mode.add_argument("-p", "--print", action="store_true",
                           help="print config instead of writing it")
    make_mode.set_defaults(func=make)

    list_mode = commands.add_parser("list", help="list configured networks")
    list_mode.set_defaults(func=list_networks)

    show_mode = commands.add_parser("show", help="show network configuration")
    show_mode.add_argument("ssid", type=str, help="network name")
    show_mode.set_defaults(func=show)

    help_mode = commands.add_parser("help", help="print this help")
    help_mode.set_defaults(func=lambda _: parser.print_help())

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
