#!/usr/bin/env python3
# Python 2
from __future__ import print_function

import os
import json
import sys
import subprocess
import time

# NOTE: Use click instead of argparse?
from argparse import ArgumentParser, FileType

try:
    # Python 3
    from subprocess import getstatusoutput
except ImportError:
    # Python 2
    from commands import getstatusoutput

# FIXME: Use the library instead of a call to `upload-to-codePost`?
try:
    import codePost_api as cP
except ImportError:
    print("ERROR: Cannot import 'codePost_api' package. Maybe the package is not installed?")
    print("  Try:   pip install --user codePost-api")
    sys.exit(95)

from yaml import load, dump
try:
    from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
    from yaml import Loader, Dumper

GROUPLISTER_CMD = "ls-tigerfile-groups"


class _Color:
    PURPLE = '\033[95m'
    CYAN = '\033[96m'
    DARKCYAN = '\033[36m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    END = '\033[0m'


_TERM_INFO = "{END}[{BOLD}INFO{END}]{END}".format(**_Color.__dict__)
_TERM_ERROR = "{END}[{BOLD}{RED}ERROR{END}]{END}".format(**_Color.__dict__)
_TERM_OK = "{END}[{BOLD}{GREEN}OK{END}]{END}".format(**_Color.__dict__)
_TERM_WARN = "{END}[{BOLD}{BLUE}INFO{END}]{END}".format(**_Color.__dict__)


verbose = True


def _print_err(msg, fatal=None):
    print(_TERM_ERROR + " " + msg, file=sys.stderr)
    # fatal contains an error number; if non-empty, exit
    if fatal != None:
        sys.exit(fatal)


def _print_warn(msg):
    print(_TERM_WARN + " " + msg, file=sys.stderr)


def _print_info(msg):
    if verbose:
        print(_TERM_INFO + " " + msg, file=sys.stderr)


def _print_ok(msg):
    print(_TERM_OK + " " + msg)


def getPartnerships(assignment_name=None):
    if not "tigerfile_path" in config:
        _print_err(
            "Group detection: 'tigerfile_path' setting not defined in configuration file. ")
        _print_err("No group detection.")
        return {}

    path = None
    if assignment_name != None and not "assignment_name" in config:
        escaped_assignment_name = assignment_name
        if "tigerfile_path_space" in config:
            escaped_assignment_name = escaped_assignment_name.replace(
                " ", config['tigerfile_path_space']
            )
            _print_info("Group detection: Using space substitution '{}'".format(
                escaped_assignment_name))
        path = config['tigerfile_path'].format(
            assignment_name=escaped_assignment_name, **config)
    else:
        path = config['tigerfile_path'].format(**config)

    _print_info("Group detection: Using path '{}'".format(path))

    partnerships = callGroupLister(path)
    return partnerships


def callGroupLister(path):

    start = time.time()

    # Retrieve group partnership from the shell script resolving inodes
    if not os.path.exists(path):
        _print_warn(
            "Group detection: The filepath provided does not seem valid.")
        return {}

    out = None
    try:
        p = subprocess.Popen(
            "{} {}".format(GROUPLISTER_CMD, path),
            shell=True,
            executable='/bin/bash',
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            close_fds=True)

        (out, err) = p.communicate()
        if err != None and len(err) > 0:
            _print_warn(
                "Group detection: Calling process returned some error: '{}'".format(err))

    except:
        _print_err("Group detection: Calling process failed. No group detection.")

    if out == None:
        _print_err("Group detection: Failed, no output.")
        return {}

    partnerships = {}
    try:
        # Post-process output of that script
        s = out.decode("ascii").strip()
        lines = s.split()

        for line in lines:
            (groupId, studentsStr) = line.split(",")
            students = studentsStr.split("-")
            students.sort()
            partnerships[groupId] = students
    except:
        _print_err("Group detection: Calling process failed. No group detection.")
        return {}

    end = time.time()

    # Providing status update
    _print_info("Group detection: Detected {} partnerships from {}, in {} seconds".format(
        len(partnerships), path, (end-start)))

    return partnerships


parser = ArgumentParser()

parser.add_argument('-a',
                    help='The name of the assignment for which to synchronize partnerships (e.g. Loops)')
parser.add_argument('--verbose', action='store_true',
                    help='Display informational messages.')
parser.add_argument('--simulate', action='store_true',
                    help='Only simulate actions.')
parser.add_argument('--use-cache', action='store_true',
                    help='Allow for caching mechanism (i.e., for groups).')
args, unknown = parser.parse_known_args()

# Decide if we need to look for YAML config file. We only need to do this if an argument isn't
# specified on the command line
params = vars(args)

if params["a"] == None:
    _print_err(
        "Command line parameters: Missing assignment name, specified with '-a', exiting.", fatal=2)

if params["verbose"] == None or params["verbose"] == False:
    verbose = False

outparams = params.__repr__()
if len(outparams) > 500:
    outparams = outparams[:500] + "..."
_print_info("Command line parameters: {}".format(outparams))

# Load YAML configuration file
possible_locations = ["codepost-config.yaml", ".codepost-config.yaml",
                      "~/codepost-config.yaml", "~/.codepost-config.yaml"]
location = None
for p in possible_locations:
    path = os.path.abspath(os.path.expanduser(p))
    if os.path.exists(path):
        location = path
        break

if location == None:
    _print_err(
        "No codepost-config.yaml configuration file detected. Exiting.", fatal=8)
else:
    _print_info("Configuration path: {}".format(location))

config = load(open(location), Loader=Loader)
_print_info("Configuration content: {}".format(config))

partnerships = {}
partnershipsByNetid = {}
pCacheFile = os.path.expanduser(
    "~/.{a}.{course_name}.{course_period}".format(a=params["a"], **config))
if params["use_cache"] and os.path.exists(pCacheFile):
    partnerships = json.loads(open(pCacheFile).read())
else:
    partnerships = getPartnerships(params["a"])
    try:
        open(pCacheFile, "w").write(json.dumps(partnerships))
    except:
        pass


def get_assignment_info(assignment_name):
    # fields: id, name, points, isReleased, course, rubricCategories
    #   mean, median, sortKey
    try:
        info = cP.get_assignment_info_by_name(
            config["api_key"],
            config["course_name"],
            config["course_period"],
            params["a"])
    except Exception as e:
        _print_err(e)
        return None

    return info


info = get_assignment_info(params["a"])
if info == None:
    _print_err(
        "Assignment: Could not find assignment '{}'".format(params["a"]))
    sys.exit(1)

_print_info("Assignment: Processing assignment '{}' with id {}".format(
    params["a"], info.get("id", -1)))

r = cP.get_assignment_submissions(config["api_key"], info["id"])
if r == None:
    _print_err(
        "Submissions: Could not retrieve submissions for assignment '{}'".format(params["a"]))
    sys.exit(2)

_print_info("Submissions: Retrieved {} submissions".format(len(r)))

studentPattern = "{}"
if "user_pattern" in config:
    studentPattern = config["user_pattern"]

mappings = {}
for pair in partnerships.values():
    studentPair = list(map(studentPattern.format, pair))
    for student in studentPair:
        mappings[student] = studentPair

# submissions
# {'grade': 40.0, 'grader': 'dtodd@princeton.edu', 'isFinalized': True,
# 'files': [45125, 45126, 45127, 45128],
# 'students': ['anhein@princeton.edu', 'aayin@princeton.edu'],
# 'assignment': 63, 'queueOrderKey': 0,
# 'dateEdited': '2019-03-06T15:37:30.605502-05:00', 'id': 10307}

for submission in r:
    if not 'id' in submission:
        continue
    submission_id = submission["id"]
    submission_students = set(map(str.lower, submission["students"]))
    new_students = submission_students.copy()

    for student in submission_students:
        student_set = mappings.get(student, None)
        if student_set == None:
            continue

        for x in student_set:
            new_students.add(x)

    if len(submission_students) < len(new_students):
        _print_info("Submission: Adding {} students to submission {} ({} -> {})".format(
            len(new_students) - len(submission_students),
            submission_id,
            submission_students,
            new_students
        ))

        if not params["simulate"]:
            successful = cP.set_submission_students(
                config["api_key"],
                submission_id,
                list(new_students)
            )
            if not successful:
                _print_warn("Submission: Unsuccessful in update of submission {}".format(
                    submission_id
                ))
