import string

def outputZimplStr(orig):
  cnt = 1
  outL = []
  for s in orig:
    outL.append("<%02i> \"%s\"" % (cnt, s))
    cnt += 1
  return string.join(outL, ", ")

def outputZimplInt(orig):
  cnt = 1
  outL = []
  for i in orig:
    outL.append("<%02i> %i" % (cnt, i))
    cnt += 1
  return string.join(outL, ", ")

def outputZimplSet(orig):
  cnt = 1
  outL = []
  for l in orig:
    for i in range(0, len(l)):
      outL.append("<%02i,%i> %s" % (cnt, i + 1, str(l[i])))
    cnt += 1
  return string.join(outL, ", ")

def outputZimplSetCard(orig):
  cnt = 1
  outL = []
  for l in orig:
    outL.append("<%02i> %i" % (cnt, len(l)))
    cnt += 1
  return string.join(outL, ", ")
  
def check_and_gen_udine(ps):
  toSave = None
  penalty = 0
  if ps[0] == 1 and ps[1] == -1: penalty += 1
  if ps[-1] == 1 and ps[-2] == -1: penalty += 1
  for i in range(0, len(ps) - 2):
    if ps[i + 0] == -1 and ps[i + 1] == 1 and ps[i + 2] == -1: penalty += 1
  if penalty > 0: 
    positive = filter(lambda x: x > 0, ps)
    toSave = [penalty] + ps + [sum(positive) - 1]
  return toSave

def addUdinePatternPenalisation(outfilename, variant = 4):
  out = open(outfilename, "a")
  vals = [-1, 1]
  patterns = []
  for p1 in vals:
    for p2 in vals:
      for p3 in vals:
        for p4 in vals:
          toSave = None
          if variant == 5:
            for p5 in vals:
              toSave = check_and_gen_udine([p1, p2, p3, p4, p5])
              if toSave != None: patterns.append(toSave)
              toSave = None
          else:
            toSave = check_and_gen_udine([p1, p2, p3, p4])
            if toSave != None: patterns.append(toSave)
  patternsText = ["<" + string.join(map(str, pattern), ", ") + ">" for pattern in patterns]
  out.write("set BannedCurriculumPatterns := { \n" + string.join(patternsText, ", \n") + "};\n\n")
  out.close()   

def outputZimpl(infilename, modelname = "sparse14"):

  outfilename = infilename + "." + modelname + ".zpl"
  
  # inpus reads Name: <n> Courses: <n> Rooms: <n> Days: <n> Periods_per_day: <n> Curricula: <n> Constraints: <n>
  lines = open(infilename, "r").readlines()
  info = {'HeadLen' : 6}
  currentLine = 1
  for infoline in lines[currentLine : currentLine + info['HeadLen']]:
    f = infoline.split(": ")
    if (len(f) != 2): continue 
    info[f[0]] = int(f[1])
  
  # output reads nbPeriods = 2; nbTeachers = 2; nbRooms = 2; nbCurricula = 2;
  output = ""
  output += "param nbPeriodsPerDay := %i;\n" % info['Periods_per_day']
  output += "param nbDaySingletonChecks := %i;\n" % info['Periods_per_day']
  output += "param nbRooms := %i;\n" % (info['Rooms'])
  output += "param nbDays := %i;\n" % (info['Days'])
  output += "param nbCurricula := %i;\n" % (info['Curricula'])
  output += "param nbCourses := %i;\n" % (info['Courses'])
  output += "param nbPeriods := nbDays * nbPeriodsPerDay;\n"
  output += "param nbMaxListCourses := 10;\n"
  output += "param nbMaxListPeriods := 50;\n"
  output += "param nbMaxEventsPerCourse := 6;\n"
  
  output += """
set Periods   := { 1 .. nbPeriods };
set Rooms     := { 1 .. nbRooms   };  
set Courses   := { 1 .. nbCourses };
set Curricula := { 1 .. nbCurricula };
set Days      := { 1 .. nbDays };
set ListCourses := { 1 .. nbMaxListCourses };
set ListPeriods := { 1 .. nbMaxListPeriods };
set PeriodsPerDay := { 1 .. nbPeriodsPerDay };
set DaySingletonChecks := { 1 .. nbDaySingletonChecks };\n\n"""  
  
  # input reads <Group ID>, <Group size> [<Group members>]+
  # eg. Elettronica2 2 AnMat2E EcoOrAz
  curriculaTemp = []
  currentLine += info['HeadLen'] + 2 + info['Courses'] + 2 + info['Rooms'] + 2
  for curriculaLine in lines[currentLine : currentLine + info['Curricula']]:
    f = curriculaLine.split()
    if (len(f) < 3): continue
    if (len(f) != int(f[1]) + 2): continue
    curriculaTemp.append(f[2:])

  # input reads <Course ID> <Day> <Day_Period>
  # eg. IdrAppl 0 3 
  deprecatedTemp = {}
  currentLine = 3 + info['HeadLen'] + 2 + info['Courses'] + 2 + info['Rooms'] + info['Curricula'] + 2
  for deprecatedLine in lines[currentLine : currentLine + info['Constraints']]:
    f = deprecatedLine.split()
    if (len(f) < 3): continue
    # Day * info['Periods_per_day'] + Day_Period
    period = int(f[1]) * info['Periods_per_day'] + int(f[2]) + 1
    if not deprecatedTemp.has_key(f[0]): deprecatedTemp[f[0]] = []
    deprecatedTemp[f[0]].append(str(period))
   
  # input reads <Room ID> <Room Capacity>
  # eg. A 	 312
  output += "param RoomHasCapacity[Rooms] := ";
  outputTemp = []
  currentLine = 1 + info['HeadLen'] + 2 + info['Courses'] + 2
  roomCnt = 1
  for roomLine in lines[currentLine : currentLine + info['Rooms']]:
    f = roomLine.split()
    if (len(f) != 2): continue
    outputTemp.append(("<%02i> " % roomCnt) + f[1])
    roomCnt += 1 
  output += string.join(outputTemp, ", ")
  output += ";\n\n";

  # input reads <Course ID> <Teacher> <Number of lectures> <Min number of days> <Number of students enrolled>
  # eg. Mat1C      Rossi          5 4        100 
  outputTemp = []
  coursesTemp = {}
  TeacherToEvents = {}
  TeacherToCourses = {}
  currentLine = 1 + info['HeadLen'] + 2

  courseId = 1
  cTeachersList = []
  cStudentsList = []
  cNamesList = [] 
  cCurriculaList = []
  CurriculaToEvents = [0 for i in range(1, info['Curricula'] + 2)]
  TeacherToEventsCount = {}
  CurriculaToCourses = [[] for i in range(1, info['Curricula'] + 2)]
  CoursesToEvents = [[] for i in range(1, info['Courses'] + 2)]
  
  courseNames = []
  courseTeachers = []
  courseLectures = []
  courseMindays = []
  courseStudents = []
  courseInavail = []

  for courseLine in lines[currentLine : currentLine + info['Courses']]:
    f = courseLine.split()
    if (len(f) != 5): continue
    courseNames.append(f[0])
    courseTeachers.append(f[1])
    courseLectures.append(int(f[4]))
    courseMindays.append(int(f[2]))
    courseStudents.append(int(f[3]))
    inCurricula = []
    for i in range(0, len(curriculaTemp)):
     if f[0] in curriculaTemp[i]: inCurricula.append(str(i + 1))
    (cName, cTeacher, cStudents, cSessions, cMindays, cCurricula) = (f[0], f[1], int(f[4]), int(f[2]), int(f[3]), inCurricula)
    if not TeacherToEvents.has_key(f[1]): TeacherToEvents[f[1]] = []
    if not TeacherToCourses.has_key(f[1]): TeacherToCourses[f[1]] = []
    if deprecatedTemp.has_key(f[0]): courseInavail.append(deprecatedTemp[f[0]])
    else: courseInavail.append([])
    # cCurriculaString = "{" + string.join(cCurricula, ", ") + "}"
    TeacherToCourses[cTeacher].append(courseId)
    # print cTeacher, "teaches", courseId, cName
    for cu in cCurricula:
      CurriculaToCourses[int(cu)].append(courseId)
      CurriculaToEvents[int(cu)] += cSessions
    if not TeacherToEventsCount.has_key(cTeacher): TeacherToEventsCount[cTeacher] = 0
    TeacherToEventsCount[cTeacher] += cSessions
    courseId += 1

  currentLine = 1 + info['HeadLen'] + 2 + info['Courses'] + 2 + info['Rooms'] + 2
  for curriculaLine in lines[currentLine : currentLine + info['Curricula']]:
    f = curriculaLine.split()
    if (len(f) < 3): continue
    if (len(f) != int(f[1]) + 2): continue
    curriculaTemp.append(f[2:])
    
  output += "param CourseHasName[Courses] := " + outputZimplStr(courseNames) + ";\n\n"
  output += "param CourseHasTeacher[Courses] := " + outputZimplStr(courseTeachers) + ";\n\n"
  output += "param CourseHasStudents[Courses] := " + outputZimplInt(courseLectures) + ";\n\n"
  output += "param CourseHasEvents[Courses] := " + outputZimplInt(courseMindays) + ";\n\n"
  output += "param CourseHasMindays[Courses] := " + outputZimplInt(courseStudents) + ";\n\n"
  
  output += "param CourseHasDeprecatedPeriodsCount[Courses] := " + outputZimplSetCard(courseInavail) + ";\n\n"
  output += "param CourseHasDeprecatedPeriods[Courses * ListPeriods] := " + outputZimplSet(courseInavail) + ";\n\n"

  output += "param CurriculumHasCoursesCount[Curricula] := " + outputZimplSetCard(CurriculaToCourses[1:]) + ";\n\n"
  output += "param CurriculumHasCourses[Curricula * ListCourses] := " + outputZimplSet(CurriculaToCourses[1:]) + ";\n\n"
  CurriculumHasCoursesCount = [sum(map(lambda x: 1, x)) for x in CurriculaToCourses[1:]]
  output += "param CurriculumHasEventsCount[Curricula] := " + outputZimplInt(CurriculaToEvents[1:]) + ";\n\n"

  output += "param nbTeachers := %i;\n" % (len(TeacherToEvents.keys()))
  output += "set Teachers  := { 1 .. nbTeachers };\n\n"  
  teachersKeys = TeacherToEvents.keys()
  TeacherHasCourses = [TeacherToCourses[x] for x in teachersKeys]
  TeacherHasEventsCount = [TeacherToEventsCount[key] for key in teachersKeys]
  output += "param TeacherHasCoursesCount[Teachers] := " + outputZimplSetCard(TeacherHasCourses) + ";\n\n"
  output += "param TeacherHasCourses[Teachers * ListCourses] := " + outputZimplSet(TeacherHasCourses) + ";\n\n"
  output += "param TeacherHasEventsCount[Teachers] := " + outputZimplInt(TeacherHasEventsCount) + ";\n\n" 

  cnt = 1
  outputNamed = []
  outputPlain = []
  for day in range(0, info['Days']):
    strNum = map(str, range(day * info['Periods_per_day'] + 1, (day + 1) * info['Periods_per_day'] + 1))
    outputNamed.append(string.join([str(day + 1)]+ strNum, ", ")) 
    outputPlain.append(strNum)
  # <1,1> 1, <1,2> 2, <1,3> 3, <1,4> 4, <2,1> 5, 
  output += "param HasPeriods[Days * PeriodsPerDay] := ";
  output += outputZimplSet(outputPlain)
  output += ";\n\n";
  # <1, 1, 2, 3, 4>, <2, 5, 6, 7, 8>, 
  output += "set HasNamedPeriods := { ";
  output += " <" + string.join(outputNamed, ">, <") + "> "
  output += "};\n";
  
  modelfilename = modelname + ".%i.zpl" % info['Periods_per_day']
  model = open(modelfilename).read()
      
  outfile = open(outfilename, "w")
  outfile.write(output)
  outfile.close()

  addUdinePatternPenalisation(outfilename, info['Periods_per_day'])

  outfile = open(outfilename, "a")
  outfile.write(model)
  outfile.close()