#!/usr/bin/python
from strings import name, title, time
import  sys,re,csv

doc=""" 

<h3> Documentation for tt.py</h3> 

<p> 
This program examines a csv file ????TT.csv (e.g. 0910TT.csv -- see below) 
which holds the 'local' version of the Math Department Timetable for 
an academic year.  It checks for certain errors (e.g. the same 
instructor teaching two different courses at the same time) and 
produces various html files (see below) which show different views of the 
timetable. A companion program <b>cmpTT-TD.py</b> compares the local 
version of the timetable with the 'official' version from the UW Data 
Warehouse. The local version changes frequently, but the official 
version should only be updated during certain 'windows'. The associate chair 
uses the local version to record and display changes; the timetable 
secretary updates the official version as allowed. 
The departmental administrator will use the html output to ascertain that
each instructor is teaching his/her contractual load. 
The documentation (including these very words)  is written into
the program itself and is output as <b>tt_doc.html</b> every time
the program is executed.
</p>\n

<h4> Auxiliary files</h4> 
<p>
Two auxiliary files are used: 
<ol>  
<li> 
A .csv file named ????FAC.csv (for example 0910FAC.csv--see below) 
with one row for each instructor showing how many courses that 
instructor will teach  for the math department during the year together 
with a maximum for the fall and a maximum for the  spring. 
A typical line is 'viaclovsky,3,2,2'
</li>
<li>
 A file <b>strings.py</b> which defines three associative arrays:
   <ul>
   <li><b>name[]</b>: The key is the instructor's last name in lower case, 
    possibly extended to avoid clashes, e.g. name['robbin']='Robbin,Joel',
    name['miller_a']='Miller,Arnold', name['miller_j']='Miller,Joe'. See 
    get_key() below. This array should be updated to enter new faculty 
    members. The array name[] may also contain fictitious instructors who 
    will be assigned courses outside the math department so that the 
    program can catch time conflicts. For example, the instructor for math 709 is 'statistics' (taught as an 
    overload -- see ! below) and the program will issue a warning if math 709 
    has a time conflict with math 721. (See check_for_time_clash_miscellaneous() below.)
   </li>
   <li> <b>title[]</b>: The key is a three digit course number, and the 
    value is the title of the course, e.g. 
    title['112']='College  Algebra'.
   </li>
   <li> <b>time[]</b>: These are the standard  times. The value is a pair
    (two element array) consisting of  the begin time and the end time,
    and the key follows the naming convention 50_xx (for a 50 minute period)
    or 75_xx (for a 75 minute period) where xx is the hour (24 hour clock) 
    when the period begins. For example, time['75_09']=['09:30','10:45']
    and time['50_13']=['13:20','14:10']. If a course is to be taught at
    an unusual time, a corresponding entry should appear in this array.
    Be sure to type  '09:30' not '9:30'.
    </li>
    </ul>
</li>
</ol>
    The function get_key() in strings.py assumes that name clashes can be 
    resolved by  adjoining '_'+ch where ch is the first character of the 
    first name. Thus get_key("Miller,Arnold")="miller_a". Itis possible 
    (but unlikely) that this function will have to be modified 
    if a new faculty member has the same name as an existing faculty member.
</p>\n

<h4> Invocation</h4> 
<p>
The invocation  'tt.py 0910'  will define the names of the two csv 
files to be used in the program as '0910TT.csv' and '0910FAC.csv' It 
will also define names 'FALL=1102' and 'SPRING=1104'. These are
the names of the fall and spring semesters in the UW data warehouse
cyyt format (see below). For the academic year 2010-2011 the invocation 
will be 'tt.py 1011', the csv files will be 1011TT.csv and 1011FAC.csv
and FALL and SPRING will have the values 1112 and 1114 etc. In this 
documentation we refer to these files as '????TT.csv' and '????TT.csv'; 
the '????' is intended to emphasize that the
files get a different name each year. 
</p>\n
<p> The invocation 'tt.py -h 0910' prints the first (header) line  of 
0910TT.csv and exits. The purpose is so that the user can ascertain
that 0910TT.csv has a heading and that it is in the proper format.
The -h option is the only recognized option.  
</p>\n"""

if len(sys.argv)==1:
   print "\nusage: tt.py [-h] academic_year\n"
   sys.exit(0)
elif len(sys.argv)==3:
   options=sys.argv[1]
   academic_year=sys.argv[2]
else:
   options=""
   academic_year=sys.argv[1]
if len(academic_year)!=4 or not re.match('\d\d\d\d', academic_year):
   print "\nusage: tt.py  academic_year"
   print "**academic_year should be 4 digits like 0910 for 2009-10**\n"
   sys.exit(1)

INSTRUCTOR_FILE=academic_year+'FAC.csv'
COURSE_FILE=academic_year+'TT.csv'
FALL='1'+academic_year[2:4]+'2'
SPRING='1'+academic_year[2:4]+'4'


doc=doc+"""
<h4> Reading the .csv files</h4> 
<p> 
The function <b>csv2rows(file_name)</b> reads a .csv file and returns 
an array of arrays (rows).  It assumes that fields are separated
by a comma and that there are no quote characters.
Empty lines are discarded.
All fields are read as strings. There are two calls:<br>\n
<br>instructor_table=csv2rows(INSTRUCTOR_FILE)\n
<br>course_table=csv2rows(COURSE_FILE)\n
<br>
</p>\n"""

def csv2rows(file_name):
   print file_name
   infile=open(file_name,"r")
   rows=[]
   for line in infile:
     if len(line)>0:
        row = line.split(",") 
        for x in range(0,len(row)): row[x]=row[x].strip()
        rows.append(row)
   return rows 
instructor_table=csv2rows(INSTRUCTOR_FILE)
course_table=csv2rows(COURSE_FILE)

doc=doc+"""
<h4> Format of instructor_table</h4> 
<p> 
The function normalize_instructor_table below will convert columns 1,2,3 in 
instructor_table to int and add two more columns so that
instructor_table has six columns:<br>\n
<br>NAME=0\n
<br>NCOURSES=1\n
<br>MAXFALL=2\n
<br>MAXSPRING=3\n
<br>ASSIGNEDFALL=4 \n
<br>ASSIGNEDSPRING=5 \n
<br><br>\n
The NAME field contains a key which must appear in the aformentionned 
associative array name[] from strings.py. The fields NCOURSES, MAXFALL, 
MAXSPRING, represent respectively the number of courses the instructor 
will teach in the academic year (for the math department), 
the maximum number in the fall, and the maximum number in the spring. 
The function extend_instructor_table()  will fill in the values for 
fields 4 and 5 using information in course_table. Fields 1-5 will 
converted to  <b>int</b>s. 
</p>\n"""

#columns in instructor_table
NAME=0
NCOURSES=1
MAXFALL=2
MAXSPRING=3
ASSIGNEDFALL   =4 # These two columns will be added by below by
ASSIGNEDSPRING =5 # extend_instructor_table and will hold ints

doc=doc+"""<p> 
<h4> Format of course_table </h4> 
Here are the fields which (should)  appear in course_table:<br>\n
<br>TERM=0\n
<br>NUMBER=1\n
<br>SECTION=2\n
<br>FLAGS=3\n
<br>DAYS=4\n
<br>TIME=5\n
<br>INSTRUCTOR=6\n
<br>ROOM=7\n
<br>CAP=8\n
<br>NOTES=9\n
<br><br> \n

The INSTRUCTOR field in course_table should be a key in the associative 
array name[] in strings.py. It will be matched with the NAME field in 
instructor_table. The NUMBER field should be a key in title[] and the 
FIELD should be a key in time[]. The DAYS field should be a set of 
letters chosen from MTWRF. The four fields TERM, NUMBER, SECTION, FLAGS 
determine the row uniquely. A digit in the FLAGS field is used to 
distinguish multiple instructors for the same seminar. A ! in the FLAGS 
field indicates that the course is an overload, i.e. is not part of the 
instructor's required teaching load in the math dept. An instructor
with a joint appointment teaching a course in another department should
show a reduced teaching load in  ????FAC.csv and, if that other
course shows in our timetable, there should be a ! in the FLAGS field.
It is desirable to show the other course in our timetable so that the
program can check for time conflicts and so that it will show up in
the html files.
</p>\n"""

#columns in course_table
TERM=0
NUMBER=1
SECTION=2
FLAGS=3
DAYS=4
TIME=5
INSTRUCTOR=6
ROOM=7
CAP=8
NOTES=9

#------------------ Utility Functions -----------------------------
doc=doc+"<h4>Utility Functions</h4>\n"
doc=doc+"""<p>
The function <b> error_in_file(msg)</b> reports an error 
(to the terminal) which must be fixed to ensure the integrity of the 
data. It reports the error as on line cx+2 since the header line was 
deleted and the first line of an array has index 0.
</p>\n"""

def error_in_file(msg):
  #in instructor table line number is cx+2 as header was deleted
  # and array[0] is first entry in array
  print "*********Error: "
  print msg,
  raw_input("------------Fix this! ") 
  
doc=doc+"""<p>
The function <b> time_clash(cx1,cx2)</b> tests whether 
course_table[cx1] and  course_table[cx2] ever meet
at the same time. It depends on strings.py\n
</p>\n"""
def time_clash(cx1,cx2):
 t1=course_table[cx1][TERM]
 t2=course_table[cx2][TERM]
 d1=course_table[cx1][DAYS]
 d2=course_table[cx2][DAYS]
 h1=course_table[cx1][TIME]
 h2=course_table[cx2][TIME]
 b1=time[h1][0]
 b2=time[h2][0]
 e1=time[h1][1]
 e2=time[h2][1]
 for d in["M","T","W","R","F"]:
   if  t1 == t2  and \
       d1.find(d)>=0 and d2.find(d)>=0 and\
       ((b1 <= b2 and b2 <= e1)  or  (b2 <= b1 and b1 <= e2)):
      return 1
 return 0

doc=doc+"""<p> 
The routine <b>cyyt2semester(cyyt)</b> translates
the arcane UW code for the semester into normal language.
Here cyyt denotes a semester in the UW numbering system
c = century (0=1900 or 1=2000),
yy = academic year (09 represents 08-09),
t = term (2=Fall, 4=Spring, 6=Summer).
For example ccyt2semester(1102) returns 'Fall 2009'.
</p>\n"""

def cyyt2semester(cyyt):
   if   cyyt[3]=='2': season="Fall   "; off=1
   elif cyyt[3]=='4': season="Spring "; off=0
   elif cyyt[3]=='6': season="Summer "; off=0
   else: season="?????? "
   if cyyt[0]=='0':
     return season+repr(1900+int(cyyt[1:3]) -off)
   else:
     return season+repr(2000+int(cyyt[1:3]) -off)


#--------- Check Header ------------------------
   
doc=doc+"""<p> 
The function <b>show_header()</b> displays the header row in the 
course_table on the terminal as reassurance that the file 
????TT.csv is in the correct format. It is only executed if the user 
chooses the 'h' option and types an academic year ???? in the invocation.  
The header row is then deleted from course_table[].
(The program tt.py does not modify the .csv files.)
</p>\n"""

header = course_table[0]   
del course_table[0] 

if len(header)<9: 
     msg =  "Only "+repr(len(header))+ " fields "+ " in first line of "+ \
           COURSE_FILE+". (There should be 10.)\n"+','.join(header)  
     error_in_file(msg)
if re.match("^\s*\d\d\d\d",header[TERM]):
     msg ="First line of "+COURSE_FILE+" should be a header line.\n"+','.join(header)
     error_in_file(msg)
     
def show_header():  # for debugging
  print "show_header (see tt_doc.html for help)"
  print "  Fields in header line in "+COURSE_FILE
  print "    TERM          =", TERM,      ":  header[TERM]        =", header[TERM]       
  print "    NUMBER        =", NUMBER,    ":  header[NUMBER]      =", header[NUMBER]      
  print "    SECTION       =", SECTION,   ":  header[SECTION]     =", header[SECTION]    
  print "    FLAGS         =", FLAGS,     ":  header[FLAGS]       =", header[FLAGS]         
  print "    DAYS          =", DAYS,      ":  header[DAYS]        =", header[DAYS]      
  print "    TIME          =", TIME,      ":  header[TIME]        =", header[TIME]     
  print "    INSTRUCTOR    =", INSTRUCTOR,":  header[INSTRUCTOR]  =", header[INSTRUCTOR] 
  print "    ROOM          =", ROOM,      ":  header[ROOM]        =", header[ROOM]     
  print "    CAP           =", CAP,       ":  header[CAP]         =", header[CAP]     
  print "    NOTES         =", NOTES,     ":  header[NOTES]       =", header[NOTES]     
if options.find('h') >=0 : show_header(); exit(0)     

#--------------- Integrity Checks ------------------------------
doc=doc+"<h4>Integrity Checks</h4>\n"
doc=doc+"""<p>
The routines
<b>normalize_instructor_table()</b>,
<b>normalize_course_table()</b>,
<b>extend_instructor_table()</b>,
ensure that instructor_table and course_table
have predictable form. The function extend_instructor_table()
also incorporates information from course_table into instructor_table
as explained above.
</p>\n"""

def normalize_instructor_table():
  print "normalize_instructor_table"
  for ix in range(0,len(instructor_table)):
    row = instructor_table[ix]
    row.append(0) # to prevent range error
    row.append(0)
    if len(row)<6: 
        msg ="Only "+repr(len(row)-2)+" fields in line "+ \
             repr(ix+1)+ " of "+INSTRUCTOR_FILE+ \
             ". (four expected). "
        error_in_file(msg)
    for x in[NCOURSES,MAXFALL,MAXSPRING]:
       if not re.match("^\d$",row[x]):
          msg ="Three digits expected in line "+ \
             repr(ix+1)+ " of "+INSTRUCTOR_FILE
          error_in_file(msg)
       else:
          instructor_table[ix][x]=int(row[x])
    nm=row[NAME]
    if not nm in name.keys():
      msg= "Instructor "+nm+" on line "+repr(ix+1)+ " of " +INSTRUCTOR_FILE+\
        " not found in strings.py.name.keys()"
      error_in_file(msg)
normalize_instructor_table() 


def normalize_course_table():
  print "normalize_course_table" 
  for cx in range(len(course_table)):
    while len(course_table[cx])<10: course_table[cx].append("")
    row = course_table[cx]
    for x in range(len(row)): row[x]=row[x].strip()
#   if len(row)<10: 
#      msg  =COURSE_FILE+repr(cx+2)+"Only "+repr(len(row))+ " fields "+\
#       ','.join(row)
#      error_in_file(msg)
    if not (row[TERM]==FALL or row[TERM]==SPRING):
       msg="Error in "+COURSE_FILE+ " at line "+repr(cx+2)+\
          "Term should be "+FALL+" or "+SPRING+"\n"+ ','.join(row)
       error_in_file(msg)
    row[SECTION] = row[SECTION].zfill(3)
    if not(row[TIME] in time.keys()):
       msg = "time not in strings.py.time.keys(). See line "+\
            repr(cx+2)+" of "+ COURSE_FILE+"\n"+",".join(row)
       error_in_file(msg)
    if len(row[INSTRUCTOR])>0 and not row[INSTRUCTOR] in name.keys():
       msg = "Instructor not in name.py.time.keys(). See line "+\
            repr(cx+2)+" of "+ COURSE_FILE+"\n"+",".join(row)
       error_in_file(msg)
    course_table[cx][ROOM]=  row[ROOM].upper() 
    if len(row[ROOM])>0 and not row[ROOM] in ['B102','B130','B239','B107']:
       msg = "The math dept doesn't control this room. See line "+\
            repr(cx+2)+" of "+ COURSE_FILE+"\n"+",".join(row)
       error_in_file(msg)
    if x>NOTES and len(row[x])>0: row[NOTES]=row[NOTES]+"-"+row[x]
normalize_course_table() 


def extend_instructor_table():
  print "extend_instructor_table" 
  for irow in instructor_table:
     nm = irow[NAME]
     for crow in course_table:
       if  crow[INSTRUCTOR]==nm and crow[FLAGS].find('!')==-1:
         if crow[TERM] == FALL   : irow[ASSIGNEDFALL]+=1
         if crow[TERM] == SPRING : irow[ASSIGNEDSPRING]+=1
extend_instructor_table()

# for row in instructor_table:print row

#------------ More Error Checks --------------------
doc=doc+"<h4>More Error Chacks</h4>\n"

doc=doc+"""<p>
The function <b>check_for_duplicate_keys()</b> checks that distinct rows 
in course_table have distinct keys, i.e. that they differ in at least 
one of the four fields TERM, NUMBER, SECTION, FLAGS. The data returned 
from the UW data warehouse does not include a FLAGS field and the three 
values TERM, NUMBER, SECTION do NOT determine a row uniquely, e.g. if 
the same section has multiple instructors. 
</p>\n"""

def check_for_duplicate_keys():
  print "check_for_duplicate_keys"
  for cx1 in range(len(course_table)):
    row1=course_table[cx1]
    for cx2 in range(cx1+1,len(course_table)):
      row2=course_table[cx2]
      if row1[TERM]==row2[TERM] and  row1[NUMBER]==row2[NUMBER] and  \
         row1[SECTION]==row2[SECTION] and row1[FLAGS]==row2[FLAGS]:
         msg = "Duplicate keys in " +INSTRUCTOR_FILE+"\n"+\
         ','.join(row1) +"\n"+','.join(row2)
         error_in_file(msg)
check_for_duplicate_keys()

doc=doc+"""<p>
The function <b>check_for_time_clash_by_instructor()</b>
ensures that an instructor does not have to be in two different
places at the same time.
</p>\n"""


def check_for_time_clash_by_instructor(): 
 print "check_for_time_clash_by_instructor"
 for irow in instructor_table:
      nm =  irow[NAME].lower()
      for cx1 in range(len(course_table)):
        for cx2  in range(cx1+1,len(course_table)):         
          if course_table[cx1][INSTRUCTOR]==nm and\
             course_table[cx2][INSTRUCTOR]==nm and\
             time_clash(cx1,cx2):
            msg= "Instructor has time conflict. See " + COURSE_FILE+"\n"+\
            ','.join(course_table[cx1])+" line "+repr(cx1+2)+"\n"+\
            ','.join(course_table[cx2])+" line "+repr(cx2+2)+"\n"
            error_in_file(msg)
check_for_time_clash_by_instructor()

doc=doc+"""<p> 
The function <b>check_for_time_clash_in_our_rooms()</b> assures that two 
classes do not meet in one of the rooms
\n<br>
our_rooms=['B102','B130','B239','B107']
<br>\n
at the same time. The  three lecture rooms B102, B130, B239 are the only 
large rooms in Van Vleck and the Math Dept has first dibs on them. 
The room B107 is a special computer room which is sometimes used
as a class room. The program cmpTT-TD.py 
mentioned above does NOT attempt to ascertain that there is agreement
in the ROOM field.
</p>\n"""


def check_for_time_clash_in_our_rooms():
 print "check_for_time_clash_in_our_rooms\n"
 our_rooms=['B102','B130','B239','B107']
 for b in our_rooms:
    for cx1 in range(len(course_table)):
      for cx2  in range(cx1+1,len(course_table)):
        if course_table[cx1][ROOM]==b  and course_table[cx2][ROOM]==b \
               and time_clash(cx1,cx2):
            msg ="Room "+b+" has time conflict. See " + COURSE_FILE+"\n"+\
            ','.join(course_table[cx1])+" line "+repr(cx1+2)+"\n"+\
            ','.join(course_table[cx2])+" line "+repr(cx2+2)+"\n"
            error_in_file(msg)
check_for_time_clash_in_our_rooms()

#-------------------- Warnings ------------------------------------------

doc=doc+"<h4>Warnings</h4>\n"

warnings=[] #  list of info to be displayed 


doc=doc+"""<p> The function 
<b>check_for_time_clash_in_qualifying_exam_courses()</b>
warns if the times of two qualifying exams conflict. These
courses are<br>\n

\n<br>qualifying_exam_courses = ['703','704','714', '715', 
  '721','722','725','741','742','751', '752','761','770','771','773','776']  

</p>\n"""


def check_for_time_clash_in_qualifying_exam_courses():
  print "check_for_time_clash_in_qualifying_exam_courses"
  qualifying_exam_courses = ['703','704','714', '715', 
  '721','722','725','741','742','751', '752','761','770','771','773','776']  
  warnings.append("<h4> check_for_time_clash_in_qualifying_exam_courses</h4>")
  count=0
  for cx1 in range(len(course_table)):
    for cx2  in range(cx1+1,len(course_table)):
      [q1,q2]=[  course_table[cx1][NUMBER], course_table[cx2][NUMBER] ]
      if q1 in qualifying_exam_courses and q2 in qualifying_exam_courses \
           and time_clash(cx1,cx2):
        count=count+1
        print course_table[cx1]
        print course_table[cx2]
        msg= "These two qualifying exam courses have a time conflict:<br>\n"
        msg=msg+','.join(course_table[cx1])+"<br>\n"
        msg=msg+','.join(course_table[cx2])+"<br>\n"
        warnings.append(msg)
  print count, "warnings from check_for_time_clash_in_qualifying_exam_courses"
check_for_time_clash_in_qualifying_exam_courses()

doc=doc+"""<p>  
The function <b>check_for_time_clash_miscellaneous()</b> warns
against certain undesirable time conflicts in various courses.
These are\n

\n<br> required_math_ed_courses=["371","441","461","475"]
\n<br> fall_statistics=['709','721','831']
\n<br> spring_statistics=['710','629','832']
\n<br> advanced_undergraduate=["551","552","522","542","561","567"]
\n<br>
Also the Math Ed course should be offered at the following times:

\n<br>ok_math_ed_times=[["TR",'75_08'],["TR",'75_09'],["TR",'75_11'],["MWF",'50_12'],["MWF",'50_11']]

\n<br>Click here: <a href=
"http://www.education.wisc.edu/eas/programs/MathMajandMinor.asp">
here</a> for info on the math ed program.
</p>\n"""

def check_for_time_clash_miscellaneous():
  print "check_for_time_clash_miscellaneous"
  warnings.append("<h4> check_for_time_clash_miscellaneous</h4>")
  required_math_ed_courses=["371","441","461","475"]
  fall_statistics=['709','721','831']
  spring_statistics=['710','629','832']
  advanced_undergraduate=["551","552","522","542","561","567"]
  required_math_ed_courses=["371","441","461","475"]
  ok_math_ed_times=[\
    ["TR",'75_08'],["TR",'75_09'],["TR",'75_11'],\
    ["MWF",'50_12'],["MWF",'50_11']]
  count=0
  for cx1 in range(len(course_table)):
    q1=course_table[cx1][NUMBER]
    if q1 in required_math_ed_courses and \
       not [course_table[cx1][DAYS],course_table[cx1][TIME]] in ok_math_ed_times:
       msg= "bad time for a required math ed course\n<br>"
       msg= msg+','.join(course_table[cx1])
       count=count+1
       warnings.append("<p>"+msg+"\n</p>")  
    for cx2  in range(cx1+1,len(course_table)):
      q2=course_table[cx2][NUMBER] 
      if ((q1 in fall_statistics and q2 in fall_statistics) or\
          (q1 in spring_statistics and q2 in spring_statistics) or\
          (q1 in advanced_undergraduate and q2 in advanced_undergraduate) or\
          (q1 in required_math_ed_courses and q2 in required_math_ed_courses))\
        and  time_clash(cx1,cx2):
        msg= "These courses should not conflict\n<br>" +\
           ','.join(course_table[cx1])+"\n<br>"+','.join(course_table[cx2])
        count=count+1
        warnings.append("<p>"+msg+"\n</p>\n")   
  print count, "warnings from check_for_time_clash_miscellaneous"
check_for_time_clash_miscellaneous()

doc=doc+"""<p>
The function <b>check_for_too_many_days()</b>  warns 
if an instructor has a teaching schedule of more than three
days per week.
<p>\n"""
def check_for_too_many_days():   
  print "check_for_too_many_days"
  warnings.append("<h4>check_for_too_many_days</h4>")
  count=0
  for irow in instructor_table:
     nm = irow[NAME]
     for tm in [FALL,SPRING]:
       ds=""  
       for crow in course_table:
          if crow[TERM]==tm and crow[INSTRUCTOR]==nm: 
             ds += crow[DAYS]   
       c=0            
       for d in ['M','T', 'W','R','F']: 
          if ds.find(d)>=0: c+=1
       if  c>3  : 
           count=count+1
           msg= nm+" teaches more than three days per week in "+tm
           warnings.append("<p>"+msg+"\n</p>\n")
  print count, "warnings from check_for_too_many_days"
check_for_too_many_days()

doc=doc+"""<p>
The function <b>check_instructors_have_correct_number_of_courses()</b>
warns if an instructor's teaching load does not agree with
the information in instructor_table (i.e. in the file ????FAC.csv'). 
Note that a (!) in the FLAGS field in course_table means that the course
is an overload, i.e. is not part of the instructor's required
teaching as indicated in the file ????FAC.csv.
</p>\n"""

def check_instructors_have_correct_number_of_courses():
   print "check_instructors_have_correct_number_of_courses"
   warnings.append("<h4> check_instructors_have_correct_number_of_courses</h4>")
   count=0
   for irow in instructor_table:
     nm=irow[NAME]
     mf,hnf =  irow[MAXFALL], irow[ASSIGNEDFALL]
     ms,hns =  irow[MAXSPRING], irow[ASSIGNEDSPRING]
     nc,hnc =  irow[NCOURSES], hnf+hns
     if hnc !=nc or hnf>mf or hns>ms:  
         msg= nm+" needs "+repr(nc)+" courses, at most "+\
            repr(mf)+ " in fall," +repr(ms)+ " in spring,\n"+\
            "   but has "+repr(hnc)+"  courses, "+\
            repr(hnf)+" in fall, " + repr(hns)+" in spring.\n"
         count=count+1
         warnings.append("<p>"+msg+"\n</p>\n")
#         print msg
         if  hnf>mf or hns>ms:  error_in_file(msg)
   print count, " warnings from check_instructors_have_correct_number_of_courses" 
check_instructors_have_correct_number_of_courses()

doc=doc+"""<p>
The function <b>show_still_must_be_assigned_fall()</b> is used to 
ascertain that before the spring semester begins, every instructor has 
been assigned enough courses in the fall semester so that it will be 
possible to complete his/her required teaching in the spring. 
</p>\n"""

def show_still_must_be_assigned_fall():
   print "show_still_must_be_assigned_fall"
   warnings.append("<h4> show_still_must_be_assigned_fall</h4>")
   count=0
   for irow in instructor_table:
     nm=irow[NAME]
     mf,hnf =  irow[MAXFALL], irow[ASSIGNEDFALL]
     ms,hns =  irow[MAXSPRING], irow[ASSIGNEDSPRING]
     nc,hnc =  irow[NCOURSES], hnf+hns
     if hnf+ms<nc:  
         msg= nm+" needs "+repr(nc)+" courses, at most "+\
            repr(mf)+ " in fall," +repr(ms)+ " in spring,\n"+\
            "   but has "+repr(hnc)+"  courses, "+\
            repr(hnf)+" in fall, " + repr(hns)+"in spring.\n"
         count=count+1
         warnings.append("<p>"+msg+"\n</p>\n")
         if  hnf>mf or hns>ms:  error_in_file(msg)
   print count, " warnings from show_still_must_be_assigned_fall" 
show_still_must_be_assigned_fall()


doc=doc+"""<p>
Unlike the previous error checks and warnings,
the function <b>count_courses()</b> produces only
terminal output (not html). It counts the number
of courses in in course_table[] and examines
instructor_table[] to report how many
instructors are available.
</p>\n"""

def  count_courses():
   print "count_courses ", COURSE_FILE
   # warnings.append("<h4> count_courses</h4>")
   nf,ns,mf,ms,gc=0,0,0,0,0
   for row in course_table:
       if 700<int(row[NUMBER]) and int(row[NUMBER])<900 \
          and not row[NUMBER] in ["709","710"]: 
          gc+=1
       if row[TERM]==FALL:
          if row[FLAGS].find('!')==-1:
             nf+=1
             if re.match('\w',row[INSTRUCTOR]): #\w=[a-zA-Z0-9_]
                mf+=1
       else : # row[TERM]==SPRING
          if row[FLAGS].find('!')==-1:
             ns+=1
             if re.match('\w',row[INSTRUCTOR]): 
                ms+=1
   print nf," ordinary courses in fall:   ",mf, " assigned, ",nf-mf," unnasigned."
   print ns," ordinary courses in spring: ",ms, " assigned, ",ns-ms," unnasigned."
   print nf,"+",ns,"=",nf+ns
   print gc," graduate courses in all"
   ni, mf, ms=0,0,0
   for row in instructor_table:
      ni += row[NCOURSES]    
      mf += row[MAXFALL]
      ms += row[MAXSPRING]    
   print "Faculty for ",ni," courses: at most ",mf," in fall,", ms," in spring."
count_courses()

print"\n***************************************"

#---------Write html files ---------------
doc=doc+"<h4>HTML files</h4>\n"
doc=doc+"""<p>
The following html files are produced as output:<br>
\n<br>TT_timetable_by_course_(term).html
\n<br>TT_timetable_by_instructor.html
#\n<br>TT_abridged_timetable_by_course_(term).html
\n<br>TT_sorted_by_time.html
\n<br>TT_grad_courses_sorted_by_time_(term).html
\n<br>TT_timetable_by_grad_course_(term).html
\n<br>TT_our_rooms_usage.html
\n<br>TT_warnings.html
\n<br>tt_doc.html
</p>\n"""


def write_COURSE_FILE_html_sorted_by_course(term):
  nm = "TT_timetable_by_course_"+term+".html"
  print nm
  outfile=open(nm,"w")
  fields=[TERM,NUMBER,SECTION,FLAGS,DAYS,TIME,INSTRUCTOR,ROOM,CAP,NOTES]
  outfile.write("<html><h2>Timetable "+cyyt2semester(term)+"</h2>\n")
  outfile.write( "\n<table CELLPADDING=3 BORDER=\"1\">\n")
  outfile.write("<tr>")
  for x in fields: 
     if x==FLAGS:  outfile.write("<td>Flags</td><td>Title</td>")
     elif x==TIME: outfile.write("<td> BTime</td><td>ETime</td>")
     else:         outfile.write("<td>"+header[x]+"</td>")
  outfile.write("</tr>\n")
  for row in course_table:
    if row[TERM]==term:
      outfile.write("<tr>")
      for x in fields: 
          if x==FLAGS:  outfile.write("<td>"+row[FLAGS]+"</td><td>"+title[row[NUMBER]]+"</td>")
          elif x==TIME: outfile.write("<td>"+ time[row[x]][0]+"</td><td>"+time[row[x]][1]+"</td>")
          else:         outfile.write("<td>"+row[x]+"</td>")
      outfile.write("</tr>\n")  
  outfile.write("</table>\n</html>\n")
  outfile.close()
write_COURSE_FILE_html_sorted_by_course(FALL)
write_COURSE_FILE_html_sorted_by_course(SPRING)

def write_COURSE_FILE_abridged_html_sorted_by_course(term):
  nm = "TT_abridged_timetable_by_course_"+term+".html"
  print nm
  outfile=open(nm,"w")
  fields=[TERM,NUMBER,SECTION,DAYS,TIME,INSTRUCTOR,ROOM,CAP]
  outfile.write("<html><h2>Timetable "+cyyt2semester(term)+"</h2>\n")
  outfile.write( "\n<table CELLPADDING=3 BORDER=\"1\">\n")
  outfile.write("<tr>")
  for x in fields: 
     if x==TIME: outfile.write("<td> BTime</td><td>ETime</td>")
     else:         outfile.write("<td>"+header[x]+"</td>")
  outfile.write("</tr>\n")
  for row in course_table:
    if row[TERM]==term:
      outfile.write("<tr>")
      for x in fields: 
          if x==TIME: outfile.write("<td>"+ time[row[x]][0]+"</td><td>"+time[row[x]][1]+"</td>")
          else:         outfile.write("<td>"+row[x]+"</td>")
      outfile.write("</tr>\n")  
  outfile.write("</table>\n</html>\n")
  outfile.close()
#write_COURSE_FILE_abridged_html_sorted_by_course(FALL)
#write_COURSE_FILE_abridged_html_sorted_by_course(SPRING)

def write_GRAD_COURSE_FILE_html_sorted_by_course(term):
  nm = "TT_timetable_by_grad_course_"+term+".html"
  print nm
  outfile=open(nm,"w")
  fields=[TERM,NUMBER,SECTION,FLAGS,DAYS,TIME,INSTRUCTOR]
  outfile.write( "<html>\n<table CELLPADDING=3 BORDER=\"1\">\n")
  outfile.write("<tr>")
  for x in fields: 
     if x==FLAGS:  outfile.write("<td>Flags</td><td>Title</td>")
     elif x==TIME: outfile.write("<td> BTime</td><td>ETime</td>")
     else:         outfile.write("<td>"+header[x]+"</td>")
  outfile.write("</tr>\n")
  for row in course_table:
    if row[TERM]==term and int(row[NUMBER])>=700:
      outfile.write("<tr>")
      for x in fields:
         if x==TIME:
            outfile.write("<td>"+time[row[x]][0]+"</td>")
         else:
            outfile.write("<td>"+row[x]+"</td>")
         if x==FLAGS: outfile.write("<td>"+title[row[NUMBER]]+"</td>")
      outfile.write("</tr>\n")  
  outfile.write("</table>\n</html>\n")
  outfile.close()
write_GRAD_COURSE_FILE_html_sorted_by_course(FALL)
write_GRAD_COURSE_FILE_html_sorted_by_course(SPRING)


def write_COURSE_FILE_html_sorted_by_instructor():
  nm = "TT_timetable_by_instructor_"+academic_year+".html"
  print nm
  outfile=open(nm,"w")
  fields=[TERM,NUMBER,SECTION,FLAGS,DAYS,TIME]# and INSTRUCTOR, TERM
  outfile.write( "<html>\n<table CELLPADDING=3 BORDER=\"1\">\n") 
  outfile.write("<tr>")            
  outfile.write("<td>"+header[INSTRUCTOR]+"</td>")
  outfile.write("<td> Semester </td>")
  for x in fields: 
     if x==FLAGS:  outfile.write("<td>Flags</td><td>Title</td>")
     elif x==TIME: outfile.write("<td> BTime</td><td>ETime</td>")
     else:         outfile.write("<td>"+header[x]+"</td>")
  outfile.write("</tr>\n")
  for irow in instructor_table:
     count = int(irow[NCOURSES])
     for crow in course_table:          
       if crow[INSTRUCTOR]==irow[NAME]: 
            if crow[FLAGS].find('!')<0: count = count - 1
            outfile.write("<tr>")
            outfile.write("<td>"+crow[INSTRUCTOR]+"</td>")
            outfile.write("<td>"+cyyt2semester(crow[TERM])+"</td>")
            for x in fields: 
               if x==TIME:
                  outfile.write("<td>"+time[crow[x]][0]+"</td><td>"+time[crow[x]][1]+"</td>")
               else:
                  outfile.write("<td>"+crow[x]+"</td>")
               if x==FLAGS: outfile.write("<td>"+title[crow[NUMBER]]+"</td>")
            outfile.write("</tr>\n")
     while count > 0:   
          count = count -1
          outfile.write("<tr>")
          outfile.write("<td>"+repr(irow[NAME])+"</td>")
          outfile.write("<td>"+repr(irow[NCOURSES])+"-"+repr(irow[MAXFALL])+repr(irow[MAXSPRING])+"</td>")
          for x in range(2,len(fields)): 
               outfile.write("<td>*</td>")
          outfile.write("</tr>\n")
  outfile.write("</table>\n</html>\n")
  outfile.close()
write_COURSE_FILE_html_sorted_by_instructor()



def before(cx1,cx2): #which course is earlier?
  if (course_table[cx1][TERM] < course_table[cx2][TERM]):return 1
  if (course_table[cx2][TERM] < course_table[cx1][TERM]):return 0
  if ((course_table[cx1][DAYS].find("M")>-1 or 
       course_table[cx1][DAYS].find("W")>-1  or
       course_table[cx1][DAYS].find("F")>-1 ) and
      (course_table[cx2][DAYS].find("T")>-1  or
       course_table[cx2][DAYS].find("R")>-1 )) :return 1
  if ((course_table[cx2][DAYS].find("M")>-1  or 
       course_table[cx2][DAYS].find("W")>-1  or
       course_table[cx2][DAYS].find("F")>-1 ) and
      (course_table[cx1][DAYS].find("T")>-1  or
       course_table[cx1][DAYS].find("R")>-1 )) :return 0
  return  course_table[cx1][TIME] < course_table[cx2][TIME]


def sort_by_time():
  cxsort=range(len(course_table))
  for sorted in range(len(course_table)): 
      smallest = sorted
      for cx in range(sorted+1,len(course_table)):
         if (before(cxsort[cx],cxsort[smallest])):smallest = cx
      # now before(cxsort[smallest],cxsort[cx]) for cx=sorted+1 .. #course
      cxsort[sorted],cxsort[smallest] = cxsort[smallest],cxsort[sorted]  
  return cxsort
  
#sort_by_time()

def write_COURSE_FILE_html_sorted_by_time():
  nm="TT_sorted_by_time_"+academic_year+".html"
  nmG="TT_grad_courses_sorted_by_time_"+academic_year+".html"
  print nm
  print nmG
  fields = [TERM,DAYS,TIME,NUMBER,SECTION,FLAGS,INSTRUCTOR]
  cxsort=sort_by_time()
  outfile=open(nm,"w")
  outfileG=open(nmG,"w")
  outfile.write( "<html>\n<table>\n")  
  outfileG.write( "<html>\n<table>\n")  
  last="00:00"
  for sx in range(len(course_table)):  
         cx=cxsort[sx]  
         crow=course_table[cx]
         if last<>course_table[cx][TIME][0]:
            outfile.write( "<tr><td> -------</td></tr>\n")
            last=course_table[cx][TIME][0]
         outfile.write("<tr>")
         for x in fields:
           if x==TIME:
              outfile.write("<td>"+time[crow[x]][0]+"</td>")
           else:
              outfile.write("<td>"+crow[x]+"</td>")
           if x==FLAGS: outfile.write("<td>"+title[crow[NUMBER]]+"</td>")
         outfile.write("</tr>\n")
         if course_table[cx][NUMBER]>"700":         
            outfileG.write("<tr>")
            for x in fields:
              if x==TIME:
               outfileG.write("<td>"+time[crow[x]][0]+"</td>")
              else:
                outfileG.write("<td>"+crow[x]+"</td>")
              if x==FLAGS: outfileG.write("<td>"+title[crow[NUMBER]]+"</td>")
            outfileG.write("</tr>\n")
  outfile.write("</table>\n</html>\n")  
  outfileG.write("</table>\n</html>\n")
  outfile.close()
  outfileG.close()
write_COURSE_FILE_html_sorted_by_time()

def write_our_rooms_usage_as_html():
  nm="TT_our_rooms_usage_"+academic_year+".html"
  print nm
  fields = [ROOM,TERM,DAYS,TIME,NUMBER,SECTION,FLAGS,INSTRUCTOR]
  cxsort=sort_by_time()
  outfile=open(nm,"w")
  outfile.write( "<html>\n<table>\n")  
  for term in [FALL,SPRING]:
     for room in ['B102','B130','B239']:
       #outfile.write( "<tr><td> "+term +"</td><td>"+room+" </td></tr>\n")
       outfile.write( "<tr><td> -------</td></tr>\n")
       for sx in range(len(course_table)):
         cx=cxsort[sx]  
         if course_table[cx][TERM] == term and course_table[cx][ROOM]== room:
            outfile.write("<tr>")
            for x in fields:
              if x==TIME:
                outfile.write( "<td> "+time[course_table[cx][x]][0]+" </td>\n")
              else:
                outfile.write( "<td> "+course_table[cx][x]+" </td>\n")
              if x==FLAGS: outfile.write("<td>"+title[course_table[cx][NUMBER]]+"</td>")
            outfile.write("</tr>\n")
  outfile.write("</table>\n</html>\n")
  outfile.close()
write_our_rooms_usage_as_html()

def write_COURSE_FILE_warnings_as_html():
  nm="TT_warnings_"+academic_year+".html"
  print nm
  outfile=open(nm,"w")
  outfile.write( "<html>\n")  
  for w in warnings:
     outfile.write(w)
  outfile.write( "</html>\n")  
  outfile.close()
write_COURSE_FILE_warnings_as_html()

def write_doc_file():
   print "tt_doc.html"
   outfile=open("tt_doc.html","w")
   outfile.write(doc)
   outfile.close()
write_doc_file()

print "Done \n"

