#!/usr/bin/env python

import argparse
import collections
import getpass
import os
import signal
import subprocess
import sys
import urllib

import requests
from requests.exceptions import ConnectionError, ProxyError
import termcolor

from bs4 import BeautifulSoup, SoupStrainer
from datetime import datetime

# Constants
NAME = ""
PASSWORD = ""
PERIOD = ""
STUDENT_ID = ""

BASE_URL = "http://bert.stuy.edu"
SUBMIT = "submit_homework2"
SUBMIT_ASSIGNMENT = "Submit this assignment"
GRADES = "grades2"
PROFILE = "do_profile"
UPDATE_PROFILE = "do_profile2"
VIEW = "homework_view2"
STORE_HOMEWORK = "store_homework"
URL = ""
PROXIES = {"http": "http://filtr.nycboe.org:8002",
           "https": "http://filtr.nycboe.org:8002"
           }

use_proxy = False


def file_stdin():
    r"""Read user input from stdin

    Returns
    -------
    list
        A list where the the first index contains the string "thisisarandomfilename" and the second
        index contains the contents of the user input.
    """

    print "Press Ctrl-d when finished"
    file_contents = sys.stdin.read()
    return ["thisisarandomfilename", file_contents]


def signal_handler(signal, frame):
    r""" Intercept signal and print out a message """

    print error_msg("\nExiting...")
    sys.exit(signal)


def success_msg(msg):
    r"""Format a string to indicate success

    Parameters
    ----------
    msg : str
        String to format

    Returns
    -------
    str
        Formatted string that will be green when printed

    Example
    -------
    >>> print success_msg("Success!")
    """

    text = termcolor.colored(msg, "green", attrs=["bold"])
    return text


def error_msg(msg):
    r"""Format a string to indicate an error

    Parameters
    ----------
    msg : str
        String to format

    Returns
    -------
    str
        Formatted string that will be red when printed

    Example
    -------
    >>> print error_msg("Error!")
    """

    text = termcolor.colored(msg, "red", attrs=["bold"])
    return text


def init_settings():
    r"""Initialize variables based on settings configuration"""

    global NAME, PASSWORD, PERIOD, STUDENT_ID, TEACHER, SEMESTER, URL
    path = os.path.join(os.path.dirname(__file__), "settings.conf")
    config = {}

    execfile(path, config)
    NAME = config["name"]
    PASSWORD = config["password"]
    PERIOD = config["period"]
    STUDENT_ID = config["id"]
    TEACHER = config["teacher"]
    SEMESTER = config["semester"]

    # 4 digit id's have been deprecated for 2016 and beyond
    if int(SEMESTER[-4:]) > 2015:
        STUDENT_ID = config["osis"]

    NAME = "%s;%s" % (STUDENT_ID, NAME)
    URL = "%s/%s/%s/pages.py" % (BASE_URL, TEACHER, SEMESTER)


def correct_date(date):
    r"""Correct date to comply with datetime format strings


    Parameters
    ----------
    date : string
        Date in the format "M/D/Y h:mm[a/p]"

    Returns
    -------
    str
        Formatted date with padding zeroes and in military time

    Examples
    -------
    >>> correct_date("1/1/16 8:00a")
    "01/01/2016 8:00"
    >>> correct_date("03/07/2016 3:00p")
    "03/07/2016 15:00"
    """

    date = date.split("/")
    month = date[0]
    day = date[1]
    year = date[2].split(" ")[0]
    if len(date) < 2:  # Fix month by appending 0
        date[0] = "0" + date[0]
    if len(month) < 2:  # Fix day by appending 0
        date[1] = "0" + date[1]
    if len(year) != 4:  # Fix year by appending 20
        date[2] = "20" + date[2]

    # Fix hour by adding 12 if it is PM
    temp = date[2].split(" ")
    hour = temp[1]
    if "p" in hour:
        hour = hour.split(":")
        hour = str(int(hour[0]) + 12) + ":" + hour[1]
    hour = hour[:-1]
    temp[1] = hour
    date[2] = " ".join(temp)

    return "/".join(date)


def is_late(due_date):
    r"""Determine if deadline for homework has passed

    Parameters
    ----------
    due_date : str
        String representing the date

    Returns
    -------
    str
        error_msg("- Expired") if the due_date has passed the current date
        "" if the due date has not expired
    """

    due_date = correct_date(due_date)
    try:
        due_date = datetime.strptime(due_date, "%m/%d/%Y %H")
    except ValueError:
        due_date = datetime.strptime(due_date, "%m/%d/%Y %H:%M")
    now = datetime.now()
    if due_date < now:
        return error_msg("- Expired")
    return ""


def get_page(page, proxy=False):
    r"""Returns html of the requested page

    Parameters
    ----------
    page : str
        Page on the homework server to request
    proxy : boolean, optional
        Use nyc doe proxy to request page (defaults to False)

    Returns
    -------
    str
        String containing the html of the requested page

    Notes
    -----
    Will try to automatically use the nyc doe if necessary
    """

    global use_proxy
    data = {"classes": PERIOD, "students": NAME, "password": PASSWORD, "Submit": "Submit", "page": page}
    try:
        if proxy:
            request = requests.post(URL, data=data, proxies=PROXIES)
            use_proxy = True
        else:
            request = requests.post(URL, data=data)
    except ProxyError:
        print error_msg("Error requesting page. There's an error with the following proxies:")
        print urllib.getproxies()
        sys.exit(1)
    except ConnectionError:
        print error_msg("Error requesting page. Are you connected to the internet?")
        sys.exit(1)

    page = request.text

    if "not found" in page:
        print error_msg("Page not found")
        return None
    elif "Incorrect password" in page:
        print error_msg("Invalid credentials")
        return None
    elif "Cannot find class" in page:
        print error_msg("Invalid period")
        return None
    elif "Access to this site is blocked" in page:  # School proxy :(
        page = get_page(page, True)

    return page


def download_file(url, name=None):
    r"""Download file from url

    Parameters
    ----------
    url : str
        Url to download
    name : str, optional
        Name to save the file as (defaults to the basename of the url)
    """

    if name:
        file_name = name
    else:
        file_name = url.split("/")[-1]  # Retrieve file name from url
    with open(file_name, "wb") as output:
        try:
            if use_proxy:
                response = requests.get(url, stream=True, proxies=PROXIES)
            else:
                response = requests.get(url, stream=True)
        except ProxyError:
            print error_msg("Error downloading file. There's an error with the following proxies:")
            print urllib.getproxies()
            sys.exit(1)
        except ConnectionError:
            print error_msg("Error downloading file. Are you connected to the internet?")
            sys.exit(1)

        if not response.ok:
            print error_msg("Could not fetch file")
            return None

        for block in response.iter_content(1024):
            output.write(block)
    if not url:
        print error_msg("File downloaded as %s" % (file_name))


def parse_homeworks(html):
    r"""Get a list of homeworks that have been submitted with their links and submission time
    from the homework submission html

    Parameters
    ----------
    html : str
        Html of the view homeworks page, containing the submissions to be parsed

    Returns
    -------
    dict
        A dictionary containing the homework title as the key and a list containing the
        link, submission time, and another list of the student/teacher comments about that homework

    Example
    -------
    >>> print parse_homeworks(html)
    {"HW01": ["www.bert.stuy.edu/homework.txt", "03/01/2015 8:00a", ["my comment", "teacher comment"]]}
    """

    soup = BeautifulSoup(html, "lxml", parse_only=SoupStrainer("table"))

    homeworks = {}
    for table in soup:
        time = table.find("td", attrs={"style": ["color: green;", "color: red;"]})
        comments = table.findAll("td")
        is_student_comment = "Comment to teacher:" in str(comments)
        is_teacher_comment = "Teacher's comments:" in str(comments)
        student_comment = ""
        teacher_comment = ""
        comments = table.findAll("pre")
        if is_student_comment:
            student_comment = str(comments[0].contents[0])
            if is_teacher_comment:
                teacher_comment = str(comments[1].contents[0])
        elif is_teacher_comment:
            teacher_comment = str(comments[0].contents[0])
        try:
            time = time.text.encode("ascii", "ignore")
        except AttributeError:
            time = "Not submitted yet"

        link = table.find("a", attrs={"class": "", "href": True})
        try:
            link = link["href"]
        except TypeError:
            link = None

        label = table.find("label", attrs={"class": ""})
        try:
            label = label.text.encode("ascii", "ignore")
        except AttributeError:
            label = None
        if label is not None:
            homeworks[label] = [link, time, [student_comment, teacher_comment]]

    homeworks = collections.OrderedDict(sorted(homeworks.items()))  # Sort homeworks in order
    return homeworks


def parse_assignments(html):
    r"""Get all homeworks that may be submitted and their due date from the assignment
    html page

    Parameters
    ----------
    html : str
        Html of the homework assignment page, containing the homeworks to be parsed

    Returns
    -------
    dict
        A dictionary where each key is the homework name and each value is the
        due date and whether its expired or not

    Examples
    -------
    >>> print parse_assignments(html)
    {"HW01": "01/10/3000 8:00a"}
    >>> print parse_assignments(html)
    {"HW01": "02/30/1970 8:00a - Expired"}
    """

    soup = BeautifulSoup(html, "lxml")
    titles = []

    # Find all assignment options and get its title
    for option in soup.find_all("option"):
        title = str(option.text)  # Remove unicode
        titles.append(title)

    if len(titles) < 0:
        print error_msg("Could not fetch assignments")
        return None

    i = 0
    assignments = {}
    # Link titles with due date + status
    while i < len(titles):
        split = titles[i].strip(")").split(" (due: ")
        status = ""
        assignment = split[0]
        due_date = split[1]
        status = is_late(due_date)
        assignments[assignment] = "%s %s" % (due_date, status)
        i += 1

    # Sort assignments in order
    assignments = collections.OrderedDict(sorted(assignments.items()))
    return assignments


def parse_grades(html):
    r"""Get grades from the server from the grades html page

    Parameters
    ----------
    html : str
        Html of the grades page, containing the grades to be parsed

    Returns
    -------
    dict
        A dictionary where each key is the assignment name, with the grade as the key
    """

    soup = BeautifulSoup(html, "lxml")
    grades = {}

    # Find all assignment options and get its title
    table = soup.find_all("table", attrs={"border": "1"})
    for tr in table[0].find_all("tr")[1:]:
        text = tr.text.encode("ascii", "ignore")
        data = text.split("\n")
        grades[data[0]] = data[1:]

    return grades


def view_homework():
    """
    View homeworks from the server

    Will show the contents of the file (if it's readable) before prompting a download
    """

    page = get_page(VIEW)

    if page is None:
        return None

    homeworks = parse_homeworks(page)
    if len(homeworks) == 0:
        print error_msg("Could not fetch homeworks")
        return None

    # Print out homework and submission status
    for homework in homeworks.keys():
        status = homeworks[homework][1]
        comment = homeworks[homework][2]
        if "early" in status or "On time" in status:
            status = success_msg(status)
        else:
            status = error_msg(status)
        print "[%s]: %s" % (homework, status)
        if comment[0]:
            print "Comment to teacher: %s" % (comment[0])
        if comment[1]:
            print "Comment to you: %s" % (comment[1])

    while True:
        option = raw_input("Which homework would you like to view? ")
        if (option not in homeworks.keys()):
            print error_msg("Invalid choice")
            continue
        else:
            break

    link = homeworks[option][0]
    if link is None:
        print error_msg("This homework has not been submitted yet!")
        return None

    url = URL[:-8] + link
    download_file(url, "/tmp/homeworkserver")  # Download file temporarily so we can let the user preview

    file_type = subprocess.check_output("file /tmp/homeworkserver", shell=True)
    # Determine whether or not to preview file based on what the file is
    if ("text" in file_type):
        print open("/tmp/homeworkserver", "r").read()
    elif ("empty" in file_type):
        print "The file is empty..."
    else:
        print error_msg("Preview not available for non-text files")

    option = str(raw_input("Would you like to download this file? [y/n] "))
    if option.lower() == "y":
        download_file(url)
    os.system("rm /tmp/homeworkserver")  # Remove temporary file
    return None


def submit_homework(homework, stdin=False):
    r"""Submit homework to the server

    Parameters
    ----------
    homework : str
        File to submit

    stdin : boolean, optional
        Submit homework from stdin (defaults to False)
    """

    # Check if we using stdin
    if stdin:
        pass
    elif not os.path.isfile(homework):
        print error_msg("File does not exist")
        return None

    page = get_page(SUBMIT)

    if page is None:
        return None

    assignments = parse_assignments(page)

    # Print assignment and due date
    for assignment in assignments:
        due_date = assignments[assignment]
        print "[%s] %s" % (assignment, due_date)

    while True:
        option = str(raw_input("Please select a homework to submit: "))
        if (option not in assignments.keys()):
            print error_msg("Invalid choice")
            continue
        else:
            # Protect against users accidentally submitting to an old server slot
            if "Expired" in assignments[option]:
                print error_msg("You are about to submit a file past its due date. Are you sure you want to continue? [y/n]"),
                response = raw_input()
                if response.lower() == "y":
                    break
                else:
                    sys.exit(0)
            else:
                break

    comment = ""
    answer = str(raw_input("Would you like to write a comment to the teacher? [y/n] "))
    if answer.lower() == "y":
        comment = str(raw_input("Please enter your comment: "))

    data = {"page": STORE_HOMEWORK, "id4": STUDENT_ID, "classid": PERIOD,
            "assignmentid": str(option), "teacher_comment": comment, "submit": SUBMIT_ASSIGNMENT}

    # If we are taking the file in from stdin, set the file_name and contents accordingly
    if stdin:
        file_name = homework[0]
        file_contents = homework[1]
    else:
        file_name = homework
        file_contents = open(homework, "r")

    try:
        if use_proxy:
            request = requests.post(URL, data=data, proxies=PROXIES, files={"filecontents": (file_name, file_contents)})
        else:
            request = requests.post(URL, data=data, files={"filecontents": (file_name, file_contents)})
    except ProxyError:
        print error_msg("Error posting file. There's an error with the following proxies:")
        print urllib.getproxies()
        sys.exit(1)
    except ConnectionError:
        print error_msg("Error posting file. Are you connected to the internet?")
        sys.exit(1)

    if request.status_code == 200:
        print success_msg("Homework successfully submitted")
    else:
        print error_msg("Failed to submit file")


def update_credentials():
    r"""Update settings file"""

    # Get actual settings file from where the script is located
    path = os.path.join(os.path.dirname(__file__), "settings.conf")
    while True:
        f = open(path, "r")
        settings = f.readlines()
        print "================================================================="
        print "Current config:"
        print "".join(settings)
        print "1)  Update username"
        print "2)  Update password"
        print "3)  Update period"
        print "4)  Update id"
        print "5)  Update teacher"
        print "6)  Update semester"
        print "7)  Update OSIS"
        print "8)  Done"
        option = str(raw_input("\nWhat would you like to do? "))
        if option == "1":
            name = str(raw_input("Please enter your new username: (Last, First) "))
            settings[0] = "name = \"%s\"\n" % (name)
        elif option == "2":
            password = getpass.getpass(prompt="Please enter your new password: ")
            settings[1] = "password = \"%s\"\n" % (password)
        elif option == "3":
            period = str(raw_input("Please enter your new period: (p<1-10>) "))
            settings[2] = "period = \"%s\"\n" % (period)
        elif option == "4":
            sid = str(raw_input("Please enter your new Id: (####) "))
            settings[3] = "id = \"%s\"\n" % (sid)
        elif option == "5":
            teacher = str(raw_input("Please enter the name of your new teacher: (first[0]+last) "))
            settings[4] = "teacher = \"%s\"\n" % (teacher)
        elif option == "6":
            semester = str(raw_input("Please enter your new semester: (<fall/spring>+year) "))
            settings[5] = "semester = \"%s\"\n" % (semester)
        elif option == "7":
            osis = str(raw_input("Please enter your osis: ([\d]{9}) "))
            settings[6] = "osis = \"%s\"" % (osis)
        elif option == "8":
            return
        else:
            continue

        f.close()
        f = open(path, "w")
        f.write("".join(settings))
        f.close()


def view_grades():
    r"""Print each graded assignment along with its score"""

    page = get_page(GRADES)

    if page is None:
        return None

    grades = parse_grades(page)
    for assignment in grades:
        scores = grades[assignment]
        my_score = int(scores[0])
        max_score = int(scores[1])
        if my_score / (max_score * 1.0) <= .65:
            my_score = error_msg(my_score)
        else:
            my_score = success_msg(my_score)
        print "[%s] - %s/%s points" % (assignment, my_score, max_score)


def parse_profile(html):
    r"""Parse profile information from the server from its html

    Parameters
    ----------
    html : str
        Html from the profile page

    Returns
    -------
    dict
        Dictionary containing the email, phone number, and preferred first name as set
        on the profile page
    """

    soup = BeautifulSoup(html, "lxml")
    data = {}

    data["email"] = soup.find("input", {"name":"email"})["value"]
    data["phone"] = soup.find("input", {"name":"phone"})["value"]
    data["prefname"] = soup.find("input", {"name":"prefname"})["value"]
    return data


def view_profile():
    r"""Print profile information"""

    page = get_page(PROFILE)

    data = parse_profile(page)
    for key in data.keys():
        print "%s: %s" % (key, data[key])

    return data


def update_profile():
    r"""Update profile information on the server"""

    while True:
        print "================================================================"
        data = view_profile()
        if data is None:
            return
        data["id4"] = STUDENT_ID
        data["page"] = UPDATE_PROFILE
        print "================================================================"
        print "1) Update email"
        print "2) Update phone number"
        print "3) Update preferred first name"
        print "4) Update password"
        print "5) Exit"
        choice = raw_input("What would you like to do? ")
        if choice == "1":
            email = raw_input("What is your email? ")
            data["email"] = email
            data["submit"] = "Change profile"
        elif choice == "2":
            phone_number = raw_input("What is your phone number? ")
            data["phone"] = phone_number
            data["submit"] = "Change profile"
        elif choice == "3":
            name = raw_input("What is your preferred first name? ")
            data["prefname"] = name
            data["submit"] = "Change profile"
        elif choice == "4":
            password = getpass.getpass(prompt="What is your new password? ")
            password_confirm = getpass.getpass(prompt="Confirm your password: ")
            if password != password_confirm:
                print "Passwords do not match"
            if password == "":
                print "Password cannot be empty"
            data["password"] = password
            data["password2"] = password_confirm
            data["submit"] = "Change password"
        elif choice == "5":
            return
        else:
            continue

        response = requests.post(URL, data=data).text
        if "Password updated" in response:
            print success_msg("Password updated")
            print success_msg("Remember to change your settings using --update-settings")
            continue
        if "Profile modified" in response:
            print success_msg("Profile modified")
        else:
            print error_msg("Could not update profile")


def main():
    signal.signal(signal.SIGINT, signal_handler)
    init_settings()
    parser = argparse.ArgumentParser(
        prog='homeworkserver', formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=30)
        )
    parser.add_argument(
        "-s",
        "--submit",
        nargs=1,
        metavar=("FILE"),
        help="submit homework"
        )
    parser.add_argument(
        "-v",
        "--view",
        help="view homework",
        action="store_true"
        )
    parser.add_argument(
        "-g",
        "--grades",
        help="view grades",
        action="store_true"
        )
    parser.add_argument(
        "-u",
        "--update-settings",
        help="update settings file",
        action="store_true"
        )
    parser.add_argument(
        "--stdin",
        help="submit file from stdin",
        action="store_true"
        )
    parser.add_argument(
        "--view-profile",
        help="view profile",
        action="store_true"
    )
    parser.add_argument(
        "--update-profile",
        help="update profile on the server",
        action="store_true"
    )
    args = parser.parse_args()
    if args.view:
        view_homework()
    elif args.submit:
        submit_homework(args.submit[0])
    elif args.grades:
        view_grades()
    elif args.update_settings:
        update_credentials()
    elif args.stdin:
        result = file_stdin()
        submit_homework(result, True)
    elif args.view_profile:
        view_profile()
    elif args.update_profile:
        update_profile()
    else:
        parser.parse_args(["-h"])

if __name__ == "__main__":
    main()
