001package edu.pdx.cs410J.grader.canvas;
002
003import com.google.common.annotations.VisibleForTesting;
004import com.opencsv.CSVReader;
005import com.opencsv.exceptions.CsvValidationException;
006
007import java.io.IOException;
008import java.io.Reader;
009import java.util.ArrayList;
010import java.util.List;
011import java.util.SortedMap;
012import java.util.TreeMap;
013import java.util.regex.Matcher;
014import java.util.regex.Pattern;
015
016public class CanvasGradesCSVParser implements CanvasGradesCSVColumnNames {
017  private static final Pattern assignmentNamePattern = Pattern.compile("(.+) \\((\\d+)\\)");
018  private static final String[] ignoredColumnNames = new String[] {
019    "ID",
020    "SIS User ID",
021    SECTION_COLUMN,
022    "Quizzes and Surveys Current Points",
023    "Quizzes and Surveys Final Points",
024    "Quizzes and Surveys Current Score",
025    "Quizzes and Surveys Unposted Current Score",
026    "Quizzes and Surveys Final Score",
027    "Quizzes and Surveys Unposted Final Score",
028    "Getting Ready Current Points",
029    "Getting Ready Final Points",
030    "Getting Ready Current Score",
031    "Getting Ready Unposted Current Score",
032    "Getting Ready Final Score",
033    "Getting Ready Unposted Final Score",
034    "Assignments Current Points",
035    "Assignments Final Points",
036    "Assignments Current Score",
037    "Assignments Unposted Current Score",
038    "Assignments Final Score",
039    "Assignments Unposted Final Score",
040    "Imported Assignments Current Points",
041    "Imported Assignments Final Points",
042    "Imported Assignments Current Score",
043    "Imported Assignments Unposted Current Score",
044    "Imported Assignments Final Score",
045    "Imported Assignments Unposted Final Score",
046    "Current Points",
047    "Final Points",
048    "Current Score",
049    "Unposted Current Score",
050    "Final Score",
051    "Unposted Final Score"
052  };
053
054  private int studentNameColumn;
055  private int studentIdColumn;
056  private int canvasIdColumn;
057  private int sectionIdColumn;
058  private final SortedMap<Integer, GradesFromCanvas.CanvasAssignment> columnToAssignment = new TreeMap<>();
059  private final GradesFromCanvas grades;
060
061  public CanvasGradesCSVParser(Reader reader) throws IOException {
062    CSVReader csv = new CSVReader(reader);
063    try {
064      extractColumnNamesFromFirstLineOfCsv(csv.readNext());
065      extractPossiblePointsFromSecondLineOfCsv(csv.readNext());
066
067      grades = new GradesFromCanvas();
068
069      String[] studentLine;
070      while ((studentLine = csv.readNext()) != null) {
071        addStudentAndGradesFromLineOfCsv(studentLine);
072      }
073
074    } catch (CsvValidationException ex) {
075      throw new IOException("While parsing CSV", ex);
076    }
077
078  }
079
080  private void addStudentAndGradesFromLineOfCsv(String[] studentLine) {
081    GradesFromCanvas.CanvasStudent student = createStudentFrom(studentLine);
082
083    if (!student.getFirstName().equals("Test")) {
084      this.grades.addStudent(student);
085    }
086
087    addGradesFromLineOfCsv(student, studentLine);
088  }
089
090  private void addGradesFromLineOfCsv(GradesFromCanvas.CanvasStudent student, String[] studentLine) {
091    this.columnToAssignment.forEach((column, assignment) -> {
092      String score = studentLine[column];
093      if (!isEmptyString(score)) {
094        student.setScore(assignment, parseScore(score));
095      }
096    });
097
098  }
099
100  private boolean isEmptyString(String score) {
101    return "".equals(score);
102  }
103
104  private double parseScore(String score) {
105    return Double.parseDouble(score);
106  }
107
108  private GradesFromCanvas.CanvasStudent createStudentFrom(String[] studentLine) {
109    String studentName = studentLine[studentNameColumn];
110    Pattern studentNamePattern = Pattern.compile("(.*), (.*)");
111    Matcher matcher = studentNamePattern.matcher(studentName);
112    if (matcher.matches()) {
113      GradesFromCanvas.CanvasStudentBuilder builder = GradesFromCanvas.newStudent();
114      builder.setFirstName(matcher.group(2));
115      builder.setLastName(matcher.group(1));
116      builder.setLoginId(studentLine[studentIdColumn]);
117      builder.setCanvasId(studentLine[canvasIdColumn]);
118      builder.setSection(studentLine[sectionIdColumn]);
119
120      return builder.create();
121
122    } else {
123      throw new IllegalStateException("Can't parse student name \"" + studentName + "\"");
124    }
125  }
126
127  private void extractPossiblePointsFromSecondLineOfCsv(String[] secondLine) {
128    this.columnToAssignment.forEach((column, assignment) -> {
129      String possiblePointsText = secondLine[column];
130      try {
131        assignment.setPossiblePoints(Double.parseDouble(possiblePointsText));
132
133      } catch (NumberFormatException ex) {
134        throw new IllegalStateException("Can't parse points \"" + possiblePointsText + "\" for " + assignment.getName());
135      }
136    });
137
138  }
139
140  private void extractColumnNamesFromFirstLineOfCsv(String[] firstLine) {
141    for (int i = 0; i < firstLine.length; i++) {
142      String cell = firstLine[i];
143      switch (cell) {
144        case STUDENT_COLUMN:
145          this.studentNameColumn = i;
146          break;
147        case "SIS Login ID":
148          this.studentIdColumn = i;
149          break;
150        case ID_COLUMN:
151          this.canvasIdColumn = i;
152          break;
153        case SECTION_COLUMN:
154          this.sectionIdColumn = i;
155          break;
156        default:
157          if (!isColumnIgnored(cell)) {
158            addAssignment(cell, i);
159          }
160      }
161    }
162
163  }
164
165  @VisibleForTesting
166  static GradesFromCanvas.CanvasAssignment createAssignment(String assignmentText) {
167    Matcher matcher = assignmentNamePattern.matcher(assignmentText);
168    if (matcher.matches()) {
169      String name = matcher.group(1);
170      String idText = matcher.group(2);
171      return new GradesFromCanvas.CanvasAssignment(name, Integer.parseInt(idText));
172
173    } else {
174      throw new IllegalStateException("Can't create Assignment from \"" + assignmentText + "\"");
175    }
176  }
177
178  private void addAssignment(String assignmentText, int column) {
179    this.columnToAssignment.put(column, createAssignment(assignmentText));
180
181  }
182
183  private boolean isColumnIgnored(String columnName) {
184    for (String ignored : ignoredColumnNames) {
185      if (ignored.equals(columnName)) {
186        return true;
187      }
188    }
189    return false;
190  }
191
192  public List<GradesFromCanvas.CanvasAssignment> getAssignments() {
193    return new ArrayList<>(this.columnToAssignment.values());
194  }
195
196  public GradesFromCanvas getGrades() {
197    return this.grades;
198  }
199
200}