#!/usr/bin/python3

import subprocess
import sys

# guard against humans running this
if len(sys.argv) == 1 or sys.argv[1] == "-h" or sys.argv[1] == "--help":
    print("This script should not be run by hand. It manages project status for")
    print("Homefarm. Change node settings and run the playbook instead.")
    sys.exit(0)

# handle --status check runs
checkonly = False
if sys.argv[1] == "--status":
    checkonly = True
    sys.argv.pop(0)
sys.argv.pop(0)

# assign our arguments to variables:
#   * the BOINC work directory
#   * the URL of the project we're examining
#   * the email address associated with the project
#   * the password of the user for this project
#   * the desired state of the project
boinc_dir, proj_url, proj_user, proj_mail, proj_passwd, proj_state = sys.argv

# slurp the gui access key and shorten the boinccmd command
result = subprocess.run(["sudo", "cat", "{}/gui_rpc_auth.cfg".format(boinc_dir)], stdout=subprocess.PIPE, encoding="utf-8")
pw = result.stdout
boinc = "/usr/bin/boinccmd"
pwf = "--passwd"



############################################################ utility functions
def scan_output_for(cmd, key):
    result = subprocess.run(cmd, stdout=subprocess.PIPE, encoding="utf-8")
    for line in result.stdout.split('\n'):
        line.lstrip()
        if line.startswith(key):
            k, v = line.split(": ")
            return v
    return None

def get_access_key(proj_url, proj_user, proj_email, proj_password):
    # first we have to find out if we need to pass email or username
    # in order to attach. this varies by project.
    use_name = scan_output_for([boinc, pwf, pw, "--get_project_config", proj_url], "uses_username")
    if use_name is None:
        print("couldn't get config for project '{}'".format(proj_url))
        sys.exit(1)
    # knowing that, we can get the access key
    key = None
    if use_name == "1":
        key = scan_output_for([boinc, pwf, pw, "--lookup_account", proj_url, proj_user, proj_passwd], "account key")
    else:
        key = scan_output_for([boinc, pwf, pw, "--lookup_account", proj_url, proj_mail, proj_passwd], "account key")
    if key is None:
        print("couldn't get access key for '{}'; user: '{}' mail '{}' passwd '{}' use_name '{}' key '{}'".format(proj_url, proj_user, proj_mail, proj_passwd, use_name, key))
        sys.exit(1)
    return key

def get_project_state(proj_url):
    pstate = {}
    in_project = False
    result = subprocess.run([boinc, pwf, pw, '--get_project_status'], stdout=subprocess.PIPE, encoding="utf-8")
    for line in result.stdout.split('\n'):
        line = line.lstrip()
        try:
            k, v = line.split(": ")
        except:
            continue
        if in_project:
            if k == "suspended via GUI":
                if v == "yes":
                    pstate["suspended"] = True
                else:
                    pstate["suspended"] = False
            elif k == "don't request more work":
                if v == "yes":
                    pstate["nomorework"] = True
                else:
                    pstate["nomorework"] = False
            elif k == "last RPC":
                break
        else:
            if k == "master URL":
                if v.find(proj_url) != -1:
                    in_project = True
    return pstate

def change_project_state(proj_url, newstate):
    r = subprocess.run([boinc, pwf, pw, "--project", proj_url, newstate], stdout=subprocess.PIPE)
    if r.returncode != 0:
        print("error on state change '{}'".format(r.stderr))
        sys.exit(1)

def state_attach(proj_url, proj_user, proj_email, proj_password):
    # need account access key to attach
    key = get_access_key(proj_url, proj_user, proj_email, proj_password)
    # now we can send the attach request
    subprocess.run([boinc, pwf, pw, "--project_attach", proj_url, key], stdout=subprocess.PIPE)


######################################################################### main

# collect current project state
cstate = get_project_state(proj_url)
if checkonly:
    print(cstate)
    sys.exit(0)

# if we got a zero-length dict back, we're not attached to this
# project. the only valid new states here are 'active', which will
# attach, and "detached", which will do nothing
if len(cstate) == 0:
    if proj_state == "active":
        state_attach(proj_url, proj_user, proj_mail, proj_passwd)
        sys.exit(0)
    elif proj_state == "detached":
        sys.exit(0)
    else:
        print("asked to set state to '{}' for project '{}', but am not attached to that project".format(proj_state, proj_url))
        sys.exit(1)

if proj_state == "active":
    # we can transition from suspended or nomorework to active.
    if cstate["suspended"] is True:
        change_project_state(proj_url, "resume")
    if cstate["nomorework"] is True:
        change_project_state(proj_url, "allowmorework")
elif proj_state == "nomorework":
    if cstate["nomorework"] is False:
        change_project_state(proj_url, "nomorework")
elif proj_state == "suspended":
    if cstate["suspended"] is False:
        change_project_state(proj_url, "suspend")
elif proj_state == "detached":
    change_project_state(proj_url, "detach")
elif proj_state == "update":
    change_project_state(proj_url, "update")
else:
    print("requested project state '{}' is not valid".format(proj_state))
    sys.exit(1)
