# -*- coding: utf-8 -*-
"""
Cleans up the html generated by the Dossier Etudiant of Polytechnique
Depends on BeautifulSoup for xml tree manipulation
"""

from bs4 import BeautifulSoup
import colorsys
import re
import sys
import os

COURSES = set()
COURSE_REGEX = '([A-Z]{1,4}-?([A-Z]{3})?' \
               '[0-9]{3,4}[A-Z]?)'


def get_spaced_colors(number_colors, lih=0.7, sat=0.8):
    """Returns an array of rgb colors each coded as a tuple of 3 numbers"""
    ret = []
    for n in xrange(number_colors):
        h = float(n)/number_colors
        ret.append(tuple(int(c*255) for c in colorsys.hls_to_rgb(h, lih, sat)))
    return ret


def insert_break(match_obj):
    match_text = match_obj.group(1)
    # Detect if the match is a room number
    if match_text[0] in 'ABCML' and match_text[1] == '-':
        return match_text
    return match_text + '<br>'


def remove_tags(root, tag_name):
    for tag in root.find_all(tag_name):
        tag.extract()


def table_from_divs(wrapper_div):
    div_table = wrapper_div
    # Extract rows
    rows = div_table.findAll(recursive=False)

    # Create new table tag and fill it
    soup = BeautifulSoup()
    table = soup.new_tag('table')
    for row in rows:
        tr = soup.new_tag('tr')
        for elem in row:
            te = soup.new_tag('td')
            for desc in list(elem.descendants):
                te.append(desc)
            tr.append(te)
        table.append(tr)

    # Remove any child divs copied in the new table
    remove_tags(table, 'div')
    return table


def add_header_to_table(strings, table):
    soup = BeautifulSoup()
    header = soup.new_tag('tr')
    for s in strings:
        cell = soup.new_tag('th')
        cell.append(s)
        header.append(cell)
    table.insert(0, header)


def get_schedule_path():
    # File to be formatted
    # Parse command arguments for the file to be formatted
    if len(sys.argv) == 1:
        default_path = 'Horaire.html'
        if os.path.isfile(default_path):
            schedule_path = default_path
        else:
            schedule_path = raw_input('Horaire path = ')
    elif len(sys.argv) == 2:
        schedule_path = sys.argv[1]
    else:
        sys.stderr.write("Too many arguments passed")
        sys.exit(2)

    if not os.path.isfile(schedule_path):
        sys.stderr.write("Invalid file passed as parameter")
        sys.exit(2)

    print 'Converting file :', schedule_path
    return schedule_path


def preprocess(schedule_html):
    """Modify the html before it is parsed"""
    # Insert new line after course name
    return re.sub(COURSE_REGEX, insert_break, schedule_html)


def check_format(soup):
    """Basic check to detect if the dom is in the expected format"""
    label_count = len(soup.find_all('label'))
    div_count = len(soup.find_all('div'))
    assert label_count > 50 and div_count > 100, "Unexpected html format : html is not full of divs and labels"


def reformat(soup):
    """Convert crappy dom to less crappy dom"""
    base_template = '''<html>
<head>
<link rel="stylesheet" type="text/css" href="result.css" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Horaire Personnel</title>
</head>
<center></center>
</html>'''
    new_soup = BeautifulSoup(base_template)
    center = new_soup.find('center')

    # No need for the labels
    remove_tags(soup, 'label')
    course_table = table_from_divs(soup.find(class_='wrapperPourListeCoursResume'))
    add_header_to_table((u'Sigle', u'Intitulé', u'Groupe théorique', u'Groupe laboratoire', u'Crédits'), course_table)
    center.append(course_table)
    center.append(new_soup.new_tag('br'))

    schedule_table = table_from_divs(soup.find(class_='wrapperPourListeCoursActuels'))
    add_header_to_table((u'Période', u'Lundi', u'Mardi', u'Mercredi', u'Jeudi', u'Vendredi'), schedule_table)
    center.append(schedule_table)

    # Format text from schedule
    for txt in schedule_table.findAll(text=True):
        # Remove group numbers
        s = re.sub('\([0-9]{2}\)', '', txt)
        s = s.replace(' Hebdo.', '').replace('Lab. ', 'Lab').replace('Lab.', 'Lab').replace('2 sem. ', '')
        txt.replaceWith(s)

    # Convert schedule DOM to 2D array
    schedule = schedule_table
    rows = schedule.findAll('tr')
    arr = []
    for i, tr in enumerate(rows):
        arr.append([])
        cols = tr.findAll('td')
        for td in cols:
            arr[i].append(td)

    # Remove empty dimension
    arr.pop(0)
    # Transpose schedule
    arr = zip(*arr)

    # Merge cells corresponding to the same course
    # Add rowspan attributes and mark cells to remove
    for i in range(0, len(arr)):  # row
        for j in xrange(0, len(arr[i])-1):  # col index of first cell
            for k in xrange(j+1, len(arr[i])):  # col index of second cell
                if len(arr[i][j].text) > 1 and arr[i][j].text == arr[i][k].text:
                    arr[i][k]['dirt'] = True
                    if arr[i][j].get('rowspan') is None:
                        arr[i][j]['rowspan'] = 2
                    else:
                        arr[i][j]['rowspan'] += 1
                else:
                    break  # No match, exit current second cell loop

    # Find and remove marked cells
    dirty = schedule.findAll(dirt=True)
    for d in dirty:
        d.extract()

    # Add class Course to cells
    tds = set(t.parent for t in new_soup.findAll(text=re.compile(COURSE_REGEX)))  # Get td with sigle

    for td in tds:
        course = str(td.contents[0]).strip()
        COURSES.add(course)
        td['class'] = course

    # Add class Conflict to cells with multiple courses
    courses_regex = '|'.join(COURSES)
    for td in tds:
        td_courses = re.findall(courses_regex, td.text, re.MULTILINE)
        if len(td_courses) > 1:
            td['class'] += ' Conflict'
    return new_soup


def save_new_schedule(new_soup):
    with open('result.html', 'w') as output:
        pretty_html = new_soup.prettify()
        output.write(pretty_html.encode("utf-8"))


def save_css():
    # Generate css rules for each course
    cols = get_spaced_colors(len(COURSES))
    format_str = '.{} {{ background-color:rgb{};}}'
    css_colors = [format_str.format(course, str(col)) for course, col in zip(COURSES, cols)]

    # Add css rule for conflicts
    css_colors.append('.Conflict {font-weight:bold;}')
    css_colors_str = '\n'.join(css_colors)

    # Generate CSS
    css = '''td,th
{
border:1px solid black;
text-align:center;
padding: 5px;
font-family:Arial,Helvetica,sans-serif;
/*Make chrome print the background color*/
-webkit-print-color-adjust:exact;
}

table
{
border:1px solid black;
border-collapse:collapse;
width: 815px;
}

''' + css_colors_str

    with open('result.css', 'w') as out_file:
        out_file.write(css)


def main():
    schedule_path = get_schedule_path()
    schedule_html = open(schedule_path).read()
    schedule_html = preprocess(schedule_html)
    soup = BeautifulSoup(schedule_html)  # Parse html
    check_format(soup)
    new_soup = reformat(soup)
    save_new_schedule(new_soup)
    save_css()

if __name__ == '__main__':
    main()