# pub_search.py: Search for available places in requested courses (via the Display Dynamic Schedule interface)
# This file is from Minervac, a command-line client for Minerva
# <http://npaun.ca/projects/minervac>
# (C) Copyright 2016-2017 Nicholas Paun
# (C) Copyright 2018-2019 Ryan B Au

import requests,urllib,StringIO,csv,sys
from minerva_common import *

def build_request(term,codes):
    """Builds the POST request that would pull relevant course information from Minerva. 
    Based on the given term code (201809) and course codes (COMP-202 or COMP-202-000 or just COMP)
    Example: build_request('201809', 'COMP-202')
    """

    term = get_term_code(term)

    req = [
    ('sel_crse',''),
    ('sel_title',''),
    ('begin_hh','0'),
    ('begin_mi','0'),
    ('begin_ap','a'),
    ('end_hh','0'),
    ('end_mi','0'),
    ('end_ap','a'),
    ('sel_dunt_code',''),
    ('sel_dunt_unit',''),
    ('sel_from_cred',''),
    ('sel_to_cred',''),
    ('sel_coll',''),
    ('call_value_in','UNSECURED'),
    ('display_mode_in','LIST'),
    ('search_mode_in',''),
    ('term_in',term),
    ('sel_subj','dummy'),
    ('sel_day','dummy'),
    ('sel_ptrm','dummy'),
    ('sel_ptrm','%'),
    ('sel_camp','dummy'),
    ('sel_schd','dummy'),
    ('sel_schd','%'),
    ('sel_sess','dummy'),
    ('sel_instr','dummy'),
    ('sel_instr','%'),
    ('sel_attr','dummy'),
    ('sel_attr','%'),
    ('crn','dummy'),
    ('rsts','dummy'),
    ('sel_levl','dummy'),
    ('sel_levl','%'),
    ('sel_insm','dummy'),
    ]

    for code in codes:
        req.append(('sel_subj',code.split("-")[0]))
    
    return urllib.urlencode(req)

def search(term,course_codes):
    """Performs the POST request that pulls course information from Minerva"""
    request = build_request(term,course_codes)
    sys.stderr.write("> bwckgens.csv\n")
    result = requests.post("https://horizon.mcgill.ca/rm-PBAN1/bwckgens.csv",request)
    return parse_results(result.text)

def quick_search(term, course_codes, course_type=""):
    """term is in the form of 201809 where 2018 is the year (2016, 2017...), and 09 is for Fall (01 for Winter, 05 for Summer)
    course_codes is a list and elements may come in the form of:
        general course code, like COMP-202, to retrieve all different sections of it (eg. COMP-202-001, COMP-202-002...)
            AND/OR
        a specific course code with its section number, can be given (eg. COMP-202-001)
    valid course_type values include Lecture, Tutorial, or any other similar type
    
    TODO: waitlist, availability, tutorials/lectures, 

    This returns a tuple of the relevant course codes (eg. COMP-200-001 CCOM-206-018 ...) and the courses object retrieved from Minerva
    the courses object contains all of the courses with the same subject (eg. COMP, ECSE, POLI) in a dictionary 
    with keys in the form of course codes
    Input: ('201809', ['COMP-200', 'CCOM-206-018'])
    Example: (['COMP-200-001', 'COMP-200-002', 'COMP-200-003', 'CCOM-206-018'], {all courses' data})
    {all courses' data}['COMP-200-002'] => {just the info for COMP-200-002}
    """
    # get the course data from Minerva. includes all of the courses that share the same subject(s) as the course_codes parameter
    courses_obj = search(term,course_codes)
    
    # find all of the full course codes that exist in the search query
    final_codes = []
    full_codes = []
    for course_code in course_codes:
        counter = 1
        if(course_code in courses_obj):
            full_codes.append(course_code)
            continue
        else:
            full_code = course_code[:8] + str(counter).join("-000".rsplit('0'*len(str(counter)),1))
        
        while (full_code in courses_obj) and (counter <= 999):
            full_codes.append(full_code)
            counter += 1 
            full_code = course_code[:8] + str(counter).join("-000".rsplit('0'*len(str(counter)),1))

    # only keep course codes of a specified type as defined by course_type, or leave it an empty string to get all
    for full_code in full_codes:
        aType = courses_obj[full_code]['type']
        if (course_type in aType):
            final_codes.append(full_code)

    # a tuple of the relevant course codes (eg. COMP-200-001 CCOM-206-018 ...) and the courses object retrieved from Minerva
    # the courses object contains all of the courses with the same subject (eg. COMP, ECSE, POLI) in a dictionary 
    # with keys in the form of course codes
    return (final_codes, courses_obj)

def print_search(term,course_codes, cType, avail=False, verbose=False, Debug=False):
    """Print out all of the courses and their variations really nicely for the command line interface"""
    full_codes, courses_obj = quick_search(term,course_codes, cType)
    if(Debug):
        print full_codes + "\n"
    full_codes.sort()

    for full_code in full_codes:
        course = courses_obj[full_code]
        if(avail and not (verbose or Debug)):
            print str(course['_code']),
            print " CRN: %-6s" % (str(course['crn'])),
            print " Seats Remaining: %-8s" % ( str(course['wait']['rem']) +"/" + str(course['wait']['cap']) ),
            print " Waitlist: %-8s" % ( str(course['wl_rem']) + "/" +str(course['wl_cap']) )
        else:
            print beautify_course_info(course, (verbose or Debug))

def beautify_course_info(e, Debug=False):
    # accept a specific course's information in the form of a dictionary and formats it to look nice for the command line interface. 
    # set Debug to True to see the all of the original keys and values paired together concatenated to the end of the original outpute
    result = [
        e['title'],
        e['type'] +" Instructor: "+ e['instructor'] +" | Credits: "+str( e['credits'] ) ,
        str(e['_code']) + " CRN: "+ str(e['crn']) + " Seats Remaining: " + str(e['wait']['rem']) +"/" + str(e['wait']['cap']) + " Waitlist: " + str(e['wl_rem']) + "/" +str(e['wl_cap']),
        e['location'] + " " + e['days'] + " " + e['time'] + " Period: " + e['date'],
        ""        
    ]
    result0 = ""
    for key,value in e.items():
        result0 += key + "=>" + str(value) + " | "
    # return "\n".join(result)
    return "\n".join(result) + ((result0) if Debug else "")


def parse_results(text):
    # converts the HTTP request data from Minerva into a logical format in a python dictionary

    stream = StringIO.StringIO(text.encode("ascii","ignore"))
    field_names = ['crn','subject','course','section','type','credits','title','days','time','cap','wl_cap','wl_act','wl_rem','instructor','date','location','status']
    file = csv.DictReader(stream,field_names)

    records = {}
    first = True
    for row in file:
        if row['subject'] is None or row['subject'] == 'Subject':
            continue

        if row['cap'] == '':
            continue

        if row['wl_rem'] == '':
            row['wl_rem'] = -1000

        row['_code'] = "-".join([row['subject'],row['course'],row['section']])
        row['select'] = MinervaState.only_waitlist_known

        row['reg'] = {}
        row['reg']['cap'] = int(row['cap'])
        
        row['wait'] = {}
        row['wait']['cap'] = int(row['wl_cap'])
        row['wait']['act'] = int(row['wl_act'])
        row['wait']['rem'] = int(row['wl_rem'])

        if row['wait']['rem'] > 0:
            row['_state'] = MinervaState.wait
        else:
            row['_state'] = MinervaState.unknown

        records[row['_code']] = row

    
    return records
