#!/usr/bin/python
"""
This should eventually be merged into edx-platform master,
but for now, this is an quick fix to a bug in dump_grades
"""
import csv
import datetime
import os
import StringIO
import sys

# Setup environment here, before importing project specific stuff
from xsiftx.tools import enter_lms
enter_lms(sys.argv[1], sys.argv[2])

from collections import defaultdict, OrderedDict
from django.conf import settings
from django.core.cache import get_cache
from django.core.management.base import BaseCommand, make_option
from django.dispatch import Signal
from django.utils.translation import ugettext as _u
from request_cache.middleware import RequestCache

from courseware.courses import get_course_by_id
from instructor.offline_gradecalc import (student_grades,
                                          offline_grades_available)
from instructor.utils import DummyRequest
from student.models import CourseEnrollment, CourseEnrollmentAllowed
from xmodule.modulestore.django import modulestore


CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
    store = modulestore(store_name)
    store.metadata_inheritance_cache_subsystem = CACHE
    store.request_cache = RequestCache.get_request_cache()

    modulestore_update_signal = Signal(providing_args=[
            'modulestore', 'course_id', 'location',
            ])
    store.modulestore_update_signal = modulestore_update_signal



class GradeList(list):
    """
    Create row handler that automatically extends
    to create an index for whatever item is being
    added.
    """
    def __setitem__(self, index, value):
        if index >= len(self):
            self.extend([None] * (index + 1 - len(self)))
        list.__setitem__(self, index, value)


class GradeTable(object):
    """
    Keep track of grades, by student, for all graded assignment
    components.  Each student's grades are stored in a list.  The
    index of this list specifies the assignment component.  Not
    all lists have the same length, because at the start of going
    through the set of grades, it is unknown what assignment
    compoments exist.  This is because some students may not do
    all the assignment components.

    The student grades are then stored in a dict, with the student
    id as the key.
    """
    def __init__(self):
        self.components = OrderedDict()
        self.grades = {}
        self.current_row = GradeList()

    def add_grade_to_row(self, component, score):
        """
        Creates component if needed, and assigns score.
        """
        if not component in self.components:
            self.components[component] = len(self.components)
        self.current_row[self.components[component]] = score

    def start_row(self):
        """
        Create a new dynamically sizing GradeList to store components
        """
        self.current_row = GradeList()

    def end_row(self, student_id):
        """
        Wrap up current row by assigning it to student id in
        internal grades dict.
        """
        self.grades[student_id] = self.current_row

    def get_grade(self, student_id):
        """
        Return list of grades for student; make sure length of
        list is same as number of graded components.
        """
        row = self.grades.get(student_id, [])
        ncomp = len(self.components)
        if len(row) < ncomp:
            row.extend([None]*(ncomp-len(row)))
        return row

    def get_graded_components(self):
        """
        Return a list of components that have been
        discovered so far.
        """
        return self.components.keys()


def get_student_grade_summary_data(request, course, course_id,
                                   get_grades=True, get_raw_scores=False,
                                   use_offline=False):
    """
    This is an updated version of what is in instructor/legacy.py and
    should be added back into platform.

    Return data arrays with student identity and grades for specified course.

    course = CourseDescriptor
    course_id = course ID

    Note: both are passed in, only because instructor_dashboard
    already has them already.

    returns datatable = dict(header=header, data=data)
    where

    header = list of strings labeling the data fields
    data = list (one per student) of lists of data corresponding to the fields

    If get_raw_scores=True, then instead of grade summaries, the raw
    grades for all graded modules are returned.

    """
    enrollments = CourseEnrollment.objects.filter(course_id=course_id)
    enrollments = enrollments.prefetch_related("user","user__groups")
    enrollments = enrollments.order_by('user__username')
    enrolled_students = [ce.user for ce in enrollments]

    header = [_u('ID'), _u('Username'), _u('Full Name'),
              _u('edX email'), _u('External email'), ]
    if not get_grades:
        header += ['Enrolled Date']

    datatable = {'header': header, 'students': enrolled_students}
    data = []

    gtab = GradeTable()

    for ce in enrollments:
        student = ce.user
        datarow = [student.id, student.username, student.profile.name, student.email]
        try:
            datarow.append(student.externalauthmap.external_email)
        except:  # ExternalAuthMap.DoesNotExist
            datarow.append('')

        if get_grades:
            gradeset = student_grades(student, request, course,
                                      keep_raw_scores=get_raw_scores,
                                      use_offline=use_offline)
            gtab.start_row()
            if get_raw_scores:
                # TODO (ichuang) encode Score as dict instead of as
                # list, so score[0] -> score['earned']
                for score in gradeset['raw_scores']:
                    gtab.add_grade_to_row(
                        score.section,
                        (getattr(score, 'earned', '') or score[0])
                        )
            else:
                for gx in gradeset['section_breakdown']:
                    gtab.add_grade_to_row(gx['label'], gx['percent'])
            gtab.end_row(student.id)
            student.grades = gtab.get_grade(student.id)
        else:
            datarow.append(ce.created)
        data.append(datarow)

    # if getting grades, need to do a second pass, and add grades to
    # each datarow; on the first pass we don't know all the graded
    # components
    if get_grades:
        for datarow in data:
            # get grades for student
            sgrades = gtab.get_grade(datarow[0])
            datarow += sgrades
        # get graded components and add to table header
        assignments = gtab.get_graded_components()
        header += assignments
        datatable['assignments'] = assignments

    datatable['data'] = data
    return datatable


if __name__ == "__main__":
    help_txt = ("dump grades to CSV file.  Usage: future_dump_grades "
                 "filename dump_type\n"
                 "   filename: where the output CSV is to be stored\n"
                 "end of available data)\n"
                 "   dump_type: 'all' or 'raw' (see instructor dashboard)")

    if not len(sys.argv) == 6:
        sys.stderr.write('Usage:\n{0}\n'.format(help_txt))
        sys.stderr.write('A file name and grade type must be '
                         'specified for output.\n')
        sys.exit(-1)

    course_id = sys.argv[3]
    filename = sys.argv[4]
    if not filename.endswith('.csv'):
        sys.stderr.write('Usage:\n{0}\n'.format(help_txt))
        sys.stderr.write('File name must end in .csv\n')
        sys.exit(-1)

    get_raw_scores = False
    grade_type = sys.argv[5].lower()
    if not (grade_type == 'raw' or grade_type == 'all'):
        sys.stderr.write('Usage:\n{0}\n'.format(help_txt))
        sys.stderr.write('Grade type must be "all" or "raw"')
        sys.exit(-1)
    if grade_type == 'raw':
        get_raw_scores = True

    request = DummyRequest()
    try:
        course = get_course_by_id(course_id)
    except Exception:
        if course_id in modulestore().courses:
            course = modulestore().courses[course_id]
        else:
            sys.stderr.write('Sorry, cannot find course {0}\n'.format(course_id))
            sys.stderr.write('Please provide a course ID or course data '
                             'directory name, eg content-mit-801rq')
            sys.exit(-2)
    print(filename)
    datatable = get_student_grade_summary_data(request, course, course.id,
                                               get_raw_scores=get_raw_scores)
    fp = StringIO.StringIO()
    writer = csv.writer(fp, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
    try:
        writer.writerow([unicode(s).encode('utf-8') for s in datatable['header']])
    except Exception as err:
        sys.stderr.write("Oops, failed to write csv row, "
                         "error={0!r}\n".format(err))
    for datarow in datatable['data']:
        try:
            encoded_row = [unicode(s).encode('utf-8') for s in datarow]
            writer.writerow(encoded_row)
        except Exception as err:
            sys.stderr.write("Oops, failed to write csv row, "
                             "error={0!r}\n".format(err))

    print(fp.getvalue())
