from datetime import datetime, timedelta
import json
from random import sample, shuffle
from itertools import islice, cycle
from collections import Counter
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import User
from django.forms.models import model_to_dict
from django.contrib.contenttypes.models import ContentType
from taggit.managers import TaggableManager
from django.utils import timezone
import pytz
import os
import shutil

languages = (
        ("python", "Python"),
        ("bash", "Bash"),
        ("c", "C Language"),
        ("cpp", "C++ Language"),
        ("java", "Java Language"),
        ("scilab", "Scilab"),
    )


question_types = (
        ("mcq", "Multiple Choice"),
        ("mcc", "Multiple Correct Choices"),
        ("code", "Code"),
        ("upload", "Assignment Upload"),
    )

enrollment_methods = (
    ("default", "Enroll Request"),
    ("open", "Open Course"),
    )

test_case_types = (
        ("standardtestcase", "Standard Testcase"),
        ("stdoutbasedtestcase", "Stdout Based Testcase"),
        ("mcqtestcase", "MCQ Testcase"),
    )

attempts = [(i, i) for i in range(1, 6)]
attempts.append((-1, 'Infinite'))
days_between_attempts = [(j, j) for j in range(401)]

test_status = (
                ('inprogress', 'Inprogress'),
                ('completed', 'Completed'),
              )

def get_assignment_dir(instance, filename):
    return '%s/%s' % (instance.user.roll_number, instance.assignmentQuestion.id)

def get_model_class(model):
    ctype = ContentType.objects.get(app_label="yaksh", model=model)
    model_class = ctype.model_class()

    return model_class

def has_profile(user):
    """ check if user has profile """
    return True if hasattr(user, 'profile') else False

def get_upload_dir(instance, filename):
    return "question_%s/%s" % (instance.question.id, filename)


###############################################################################
class CourseManager(models.Manager):

    def create_trial_course(self, user):
        """Creates a trial course for testing questions"""
        trial_course = self.create(name="trial_course", enrollment="open",
                                   creator=user, is_trial=True)
        trial_course.enroll(False, user)
        return trial_course


###############################################################################
class Course(models.Model):
    """ Course for students"""
    name = models.CharField(max_length=128)
    enrollment = models.CharField(max_length=32, choices=enrollment_methods)
    active = models.BooleanField(default=True)
    creator = models.ForeignKey(User, related_name='creator')
    students = models.ManyToManyField(User, related_name='students')
    requests = models.ManyToManyField(User, related_name='requests')
    rejected = models.ManyToManyField(User, related_name='rejected')
    created_on = models.DateTimeField(auto_now_add=True)
    teachers = models.ManyToManyField(User, related_name='teachers')
    is_trial = models.BooleanField(default=False)
    objects = CourseManager()

    def request(self, *users):
        self.requests.add(*users)

    def get_requests(self):
        return self.requests.all()

    def enroll(self, was_rejected, *users):
        self.students.add(*users)
        if not was_rejected:
            self.requests.remove(*users)
        else:
            self.rejected.remove(*users)

    def get_enrolled(self):
        return self.students.all()

    def reject(self, was_enrolled, *users):
        self.rejected.add(*users)
        if not was_enrolled:
            self.requests.remove(*users)
        else:
            self.students.remove(*users)

    def get_rejected(self):
        return self.rejected.all()

    def is_enrolled(self, user):
        return user in self.students.all()

    def is_creator(self, user):
        return self.creator == user

    def is_teacher(self, user):
        return True if user in self.teachers.all() else False

    def is_self_enroll(self):
        return True if self.enrollment == enrollment_methods[1][0] else False

    def get_quizzes(self):
        return self.quiz_set.filter(is_trial=False)

    def activate(self):
        self.active = True

    def deactivate(self):
        self.active = False

    def add_teachers(self, *teachers):
        self.teachers.add(*teachers)

    def get_teachers(self):
        return self.teachers.all()

    def remove_teachers(self, *teachers):
        self.teachers.remove(*teachers)

    def __unicode__(self):
        return self.name

###############################################################################
class ConcurrentUser(models.Model):
    concurrent_user = models.OneToOneField(User, null=False)
    session_key = models.CharField(null=False, max_length=40)


###############################################################################
class Profile(models.Model):
    """Profile for a user to store roll number and other details."""
    user = models.OneToOneField(User)
    roll_number = models.CharField(max_length=20)
    institute = models.CharField(max_length=128)
    department = models.CharField(max_length=64)
    position = models.CharField(max_length=64)
    timezone = models.CharField(max_length=64,
                                default=pytz.utc.zone,
                                choices=[(tz, tz) for tz in pytz.common_timezones]
                                )


###############################################################################
class Question(models.Model):
    """Question for a quiz."""

    # A one-line summary of the question.
    summary = models.CharField(max_length=256)

    # The question text, should be valid HTML.
    description = models.TextField()

    # Number of points for the question.
    points = models.FloatField(default=1.0)

    # The language for question.
    language = models.CharField(max_length=24,
                                choices=languages)

    # The type of question.
    type = models.CharField(max_length=24, choices=question_types)

    # The type of evaluator
    test_case_type = models.CharField(max_length=24, choices=test_case_types)

    # Is this question active or not. If it is inactive it will not be used
    # when creating a QuestionPaper.
    active = models.BooleanField(default=True)

    # Tags for the Question.
    tags = TaggableManager(blank=True)

    # Snippet of code provided to the user.
    snippet = models.CharField(max_length=256, blank=True)

    # user for particular question
    user = models.ForeignKey(User, related_name="user")

    def consolidate_answer_data(self, user_answer):
        question_data = {}
        test_case_data = []

        test_cases = self.get_test_cases()

        for test in test_cases:
            test_case_as_dict = test.get_field_value()
            test_case_data.append(test_case_as_dict)

        question_data['test_case_data'] = test_case_data
        question_data['user_answer'] = user_answer
        files = FileUpload.objects.filter(question=self)
        if files:
            question_data['file_paths'] = [(file.file.path, file.extract)
                                           for file in files]

        return json.dumps(question_data)

    def dump_into_json(self, question_ids, user):
        questions = Question.objects.filter(id__in=question_ids, user_id=user.id)
        questions_dict = []
        for question in questions:
            test_case = question.get_test_cases()
            q_dict = {'summary': question.summary,
                      'description': question.description,
                      'points': question.points,
                      'language': question.language,
                      'type': question.type,
                      'active': question.active,
                      'test_case_type': question.test_case_type,
                      'snippet': question.snippet,
                      'testcase': [case.get_field_value() for case in test_case]}
            questions_dict.append(q_dict)

        return json.dumps(questions_dict, indent=2)

    def load_from_json(self, questions_list, user):
        questions = json.loads(questions_list)
        for question in questions:
            question['user'] = user
            test_cases = question.pop('testcase')
            que, result = Question.objects.get_or_create(**question)
            model_class = get_model_class(que.test_case_type)
            for test_case in test_cases:
                model_class.objects.get_or_create(question=que, **test_case)

    def get_test_cases(self, **kwargs):
        test_case_ctype = ContentType.objects.get(app_label="yaksh",
             model=self.test_case_type
        )
        test_cases = test_case_ctype.get_all_objects_for_this_type(
            question=self,
            **kwargs
        )

        return test_cases

    def get_test_case(self, **kwargs):
        test_case_ctype = ContentType.objects.get(app_label="yaksh",
            model=self.test_case_type
        )
        test_case = test_case_ctype.get_object_for_this_type(
            question=self,
            **kwargs
        )

        return test_case

    def __unicode__(self):
        return self.summary


###############################################################################
class FileUpload(models.Model):
    file = models.FileField(upload_to=get_upload_dir, blank=True)
    question = models.ForeignKey(Question, related_name="question")
    extract = models.BooleanField(default=False)

    def remove(self):
        if os.path.exists(self.file.path):
            os.remove(self.file.path)
            if os.listdir(os.path.dirname(self.file.path)) == []:
                os.rmdir(os.path.dirname(self.file.path))
        self.delete()

    def set_extract_status(self):
        if self.extract:
            self.extract = False
        else:
            self.extract = True
        self.save()


###############################################################################
class Answer(models.Model):
    """Answers submitted by the users."""

    # The question for which user answers.
    question = models.ForeignKey(Question)

    # The answer submitted by the user.
    answer = models.TextField(null=True, blank=True)

    # Error message when auto-checking the answer.
    error = models.TextField()

    # Marks obtained for the answer. This can be changed by the teacher if the
    # grading is manual.
    marks = models.FloatField(default=0.0)

    # Is the answer correct.
    correct = models.BooleanField(default=False)

    # Whether skipped or not.
    skipped = models.BooleanField(default=False)

    def set_marks(self, marks):
        if marks > self.question.points:
            self.marks = self.question.points
        else:
            self.marks = marks

    def __unicode__(self):
        return self.answer


###############################################################################
class QuizManager(models.Manager):
    def get_active_quizzes(self):
        return self.filter(active=True, is_trial=False)

    def create_trial_quiz(self, trial_course, user):
        """Creates a trial quiz for testing questions"""
        trial_quiz = self.create(course=trial_course,
                                 duration=1000,
                                 description="trial_questions",
                                 is_trial=True,
                                 time_between_attempts=0
                                 )
        return trial_quiz

    def create_trial_from_quiz(self, original_quiz_id, user, godmode):
        """Creates a trial quiz from existing quiz"""
        trial_quiz_name = "Trial_orig_id_{0}_{1}".format(original_quiz_id,
                                                        "godmode" if godmode else "usermode"
                                                        )
        
        if self.filter(description=trial_quiz_name).exists():
            trial_quiz = self.get(description=trial_quiz_name)
            
        else:
            trial_quiz = self.get(id=original_quiz_id)
            trial_quiz.course.enroll(False, user)
            trial_quiz.pk = None
            trial_quiz.description = trial_quiz_name
            trial_quiz.is_trial = True
            trial_quiz.prerequisite = None
            if godmode:
                trial_quiz.time_between_attempts = 0
                trial_quiz.duration = 1000
                trial_quiz.attempts_allowed = -1
                trial_quiz.active = True
                trial_quiz.start_date_time = timezone.now()
                trial_quiz.end_date_time = datetime(2199, 1, 1, 0, 0, 0, 0,
						    tzinfo=pytz.utc
						    )
            trial_quiz.save()
        return trial_quiz

###############################################################################
class Quiz(models.Model):
    """A quiz that students will participate in. One can think of this
    as the "examination" event.
    """

    course = models.ForeignKey(Course)

    # The start date of the quiz.
    start_date_time = models.DateTimeField("Start Date and Time of the quiz",
                                        default=timezone.now(),
                                        null=True)

    # The end date and time of the quiz
    end_date_time = models.DateTimeField("End Date and Time of the quiz",
                                         default=datetime(2199, 1, 1,
                                                          tzinfo=pytz.timezone
                                                         (timezone.
                                                          get_current_timezone_name())
                                                          ),
                                         null=True)

    # This is always in minutes.
    duration = models.IntegerField("Duration of quiz in minutes", default=20)

    # Is the quiz active. The admin should deactivate the quiz once it is
    # complete.
    active = models.BooleanField(default=True)

    # Description of quiz.
    description = models.CharField(max_length=256)

    # Mininum passing percentage condition.
    pass_criteria = models.FloatField("Passing percentage", default=40)

    # List of prerequisite quizzes to be passed to take this quiz
    prerequisite = models.ForeignKey("Quiz", null=True, blank=True)

    # Programming language for a quiz
    language = models.CharField(max_length=20, choices=languages)

    # Number of attempts for the quiz
    attempts_allowed = models.IntegerField(default=1, choices=attempts)

    time_between_attempts = models.IntegerField("Number of Days",\
            choices=days_between_attempts)

    is_trial = models.BooleanField(default=False)

    view_answerpaper = models.BooleanField('Allow student to view their answer\
                                            paper', default=False)

    objects = QuizManager()

    class Meta:
        verbose_name_plural = "Quizzes"


    def is_expired(self):
        return not self.start_date_time <= timezone.now() < self.end_date_time

    def has_prerequisite(self):
        return True if self.prerequisite else False
    
    def __unicode__(self):
        desc = self.description or 'Quiz'
        return '%s: on %s for %d minutes' % (desc, self.start_date_time,
                                             self.duration)


###############################################################################
class QuestionPaperManager(models.Manager):

    def _create_trial_from_questionpaper(self, original_quiz_id):
        """Creates a copy of the original questionpaper"""
        trial_questionpaper = self.get(quiz_id=original_quiz_id)
        trial_questions = {"fixed_questions": trial_questionpaper
                           .fixed_questions.all(),
                           "random_questions": trial_questionpaper
                           .random_questions.all()
                           }
        trial_questionpaper.pk = None
        trial_questionpaper.save()
        return trial_questionpaper, trial_questions

    def create_trial_paper_to_test_questions(self, trial_quiz,
                                             questions_list):
        """Creates a trial question paper to test selected questions"""
        if questions_list is not None:
            trial_questionpaper = self.create(quiz=trial_quiz,
                                              total_marks=10,
                                              )
            trial_questionpaper.fixed_questions.add(*questions_list)
            return trial_questionpaper

    def create_trial_paper_to_test_quiz(self, trial_quiz, original_quiz_id):
        """Creates a trial question paper to test quiz."""
        if self.filter(quiz=trial_quiz).exists():
            trial_questionpaper = self.get(quiz=trial_quiz)
        else:
            trial_questionpaper, trial_questions = self._create_trial_from_questionpaper\
                (original_quiz_id)
            trial_questionpaper.quiz = trial_quiz
            trial_questionpaper.fixed_questions\
                .add(*trial_questions["fixed_questions"])
            trial_questionpaper.random_questions\
                .add(*trial_questions["random_questions"])
            trial_questionpaper.save()
        return trial_questionpaper


###############################################################################
class QuestionPaper(models.Model):
    """Question paper stores the detail of the questions."""

    # Question paper belongs to a particular quiz.
    quiz = models.ForeignKey(Quiz)

    # Questions that will be mandatory in the quiz.
    fixed_questions = models.ManyToManyField(Question)

    # Questions that will be fetched randomly from the Question Set.
    random_questions = models.ManyToManyField("QuestionSet")

    # Option to shuffle questions, each time a new question paper is created.
    shuffle_questions = models.BooleanField(default=False)

    # Total marks for the question paper.
    total_marks = models.FloatField()

    objects = QuestionPaperManager()

    def update_total_marks(self):
        """ Updates the total marks for the Question Paper"""
        marks = 0.0
        questions = self.fixed_questions.all()
        for question in questions:
            marks += question.points
        for question_set in self.random_questions.all():
            marks += question_set.marks * question_set.num_questions
        self.total_marks = marks

    def _get_questions_for_answerpaper(self):
        """ Returns fixed and random questions for the answer paper"""
        questions = []
        questions = list(self.fixed_questions.all())
        for question_set in self.random_questions.all():
            questions += question_set.get_random_questions()
        return questions

    def make_answerpaper(self, user, ip, attempt_num):
        """Creates an  answer paper for the user to attempt the quiz"""
        ans_paper = AnswerPaper(user=user,
            user_ip=ip,
            attempt_number=attempt_num
        )
        ans_paper.start_time = timezone.now()
        ans_paper.end_time = ans_paper.start_time + \
            timedelta(minutes=self.quiz.duration)
        ans_paper.question_paper = self
        ans_paper.save()
        questions = self._get_questions_for_answerpaper()
        ans_paper.questions.add(*questions)
        ans_paper.questions_unanswered.add(*questions)
        return ans_paper

    def _is_questionpaper_passed(self, user):
        return  AnswerPaper.objects.filter(question_paper=self, user=user,
                                           passed = True).exists()

    def _is_attempt_allowed(self, user):
        attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self,
                                                         user=user)
        return attempts != self.quiz.attempts_allowed

    def can_attempt_now(self, user):
        if self._is_attempt_allowed(user):
            last_attempt = AnswerPaper.objects.get_user_last_attempt(user=user,
                           questionpaper=self)
            if last_attempt:
                time_lag = (timezone.now() - last_attempt.start_time).days
                return time_lag >= self.quiz.time_between_attempts
            else:
                return True
        else:
            return False

    def _get_prequisite_paper(self):
        return self.quiz.prerequisite.questionpaper_set.get()

    def is_prerequisite_passed(self, user):
        if self.quiz.has_prerequisite():
            prerequisite = self._get_prequisite_paper()
            return prerequisite._is_questionpaper_passed(user)


###############################################################################
class QuestionSet(models.Model):
    """Question set contains a set of questions from which random questions
       will be selected for the quiz.
    """

    # Marks of each question of a particular Question Set
    marks = models.FloatField()

    # Number of questions to be fetched for the quiz.
    num_questions = models.IntegerField()

    # Set of questions for sampling randomly.
    questions = models.ManyToManyField(Question)

    def get_random_questions(self):
        """ Returns random questions from set of questions"""
        return sample(self.questions.all(), self.num_questions)


###############################################################################
class AnswerPaperManager(models.Manager):
    def get_all_questions(self, questionpaper_id, attempt_number,
                          status='completed'):
        ''' Return a dict of question id as key and count as value'''
        papers = self.filter(question_paper_id=questionpaper_id,
                             attempt_number=attempt_number, status=status)
        all_questions = list()
        questions = list()
        for paper in papers:
            all_questions += paper.get_questions()
        for question in all_questions:
            questions.append(question.id)
        return Counter(questions)

    def get_all_questions_answered(self, questionpaper_id, attempt_number,
                                   status='completed'):
        ''' Return a dict of answered question id as key and count as value'''
        papers = self.filter(question_paper_id=questionpaper_id,
                             attempt_number=attempt_number, status=status)
        questions_answered = list()
        for paper in papers:
            for question in filter(None, paper.get_questions_answered()):
                if paper.is_answer_correct(question):
                    questions_answered.append(question.id)
        return Counter(questions_answered)

    def get_attempt_numbers(self, questionpaper_id, status='completed'):
        ''' Return list of attempt numbers'''
        attempt_numbers = self.filter(
            question_paper_id=questionpaper_id, status=status
        ).values_list('attempt_number', flat=True).distinct()
        return attempt_numbers

    def has_attempt(self, questionpaper_id, attempt_number, status='completed'):
        ''' Whether question paper is attempted'''
        return self.filter(question_paper_id=questionpaper_id,
                           attempt_number=attempt_number, status=status).exists()

    def get_count(self, questionpaper_id, attempt_number, status='completed'):
        ''' Return count of answerpapers for a specfic question paper
            and attempt number'''
        return self.filter(question_paper_id=questionpaper_id,
                           attempt_number=attempt_number, status=status).count()

    def get_question_statistics(self, questionpaper_id, attempt_number,
                                status='completed'):
        ''' Return dict with question object as key and list as value
            The list contains two value, first the number of times a question
            was answered correctly, and second the number of times a question
            appeared in a quiz'''
        question_stats = {}
        questions_answered = self.get_all_questions_answered(questionpaper_id,
                                                             attempt_number)
        questions = self.get_all_questions(questionpaper_id, attempt_number)
        all_questions = Question.objects.filter(
            id__in=set(questions)
        ).order_by('type')
        for question in all_questions:
            if question.id in questions_answered:
                question_stats[question] = [questions_answered[question.id],
                                            questions[question.id]]
            else:
                question_stats[question] = [0, questions[question.id]]
        return question_stats

    def _get_answerpapers_for_quiz(self, questionpaper_id, status=False):
        if not status:
            return self.filter(question_paper_id=questionpaper_id)
        else:
            return self.filter(question_paper_id=questionpaper_id,
                                status="completed")


    def _get_answerpapers_users(self, answerpapers):
        return answerpapers.values_list('user', flat=True).distinct()

    def get_latest_attempts(self, questionpaper_id):
        papers = self._get_answerpapers_for_quiz(questionpaper_id)
        users = self._get_answerpapers_users(papers)
        latest_attempts = []
        for user in users:
            latest_attempts.append(self._get_latest_attempt(papers, user))
        return latest_attempts

    def _get_latest_attempt(self, answerpapers, user_id):
        return answerpapers.filter(user_id=user_id).order_by('-attempt_number')[0]

    def get_user_last_attempt(self, questionpaper, user):
        attempts = self.filter(question_paper=questionpaper,
                               user=user).order_by('-attempt_number')
        if attempts:
            return attempts[0]

    def get_user_answerpapers(self, user):
        return self.filter(user=user)

    def get_total_attempt(self, questionpaper, user):
        return self.filter(question_paper=questionpaper, user=user).count()

    def get_users_for_questionpaper(self, questionpaper_id):
        return self._get_answerpapers_for_quiz(questionpaper_id, status=True)\
            .values("user__id", "user__first_name", "user__last_name")\
            .distinct()

    def get_user_all_attempts(self, questionpaper, user):
        return self.filter(question_paper=questionpaper, user=user)\
                            .order_by('-attempt_number')

    def get_user_data(self, user, questionpaper_id, attempt_number=None):
        if attempt_number is not None:
            papers = self.filter(user=user, question_paper_id=questionpaper_id,
                                     attempt_number=attempt_number)
        else:
            papers = self.filter(user=user, question_paper_id=questionpaper_id)\
                                    .order_by("-attempt_number")
        data = {}
        profile = user.profile if hasattr(user, 'profile') else None
        data['user'] = user
        data['profile'] = profile
        data['papers'] = papers
        data['questionpaperid'] = questionpaper_id
        return data


###############################################################################
class AnswerPaper(models.Model):
    """A answer paper for a student -- one per student typically.
    """
    # The user taking this question paper.
    user = models.ForeignKey(User)

    questions = models.ManyToManyField(Question, related_name='questions')

    # The Quiz to which this question paper is attached to.
    question_paper = models.ForeignKey(QuestionPaper)

    # The attempt number for the question paper.
    attempt_number = models.IntegerField()

    # The time when this paper was started by the user.
    start_time = models.DateTimeField()

    # The time when this paper was ended by the user.
    end_time = models.DateTimeField()

    # User's IP which is logged.
    user_ip = models.CharField(max_length=15)

    # The questions unanswered
    questions_unanswered = models.ManyToManyField(Question,
                                            related_name='questions_unanswered')

    # The questions answered
    questions_answered = models.ManyToManyField(Question,
                                            related_name='questions_answered')

    # All the submitted answers.
    answers = models.ManyToManyField(Answer)

    # Teacher comments on the question paper.
    comments = models.TextField()

    # Total marks earned by the student in this paper.
    marks_obtained = models.FloatField(null=True, default=None)

    # Marks percent scored by the user
    percent = models.FloatField(null=True, default=None)

    # Result of the quiz, True if student passes the exam.
    passed = models.NullBooleanField()

    # Status of the quiz attempt
    status = models.CharField(max_length=20, choices=test_status,\
            default='inprogress')

    objects = AnswerPaperManager()

    def current_question(self):
        """Returns the current active question to display."""
        if self.questions_unanswered.all():
            return self.questions_unanswered.all()[0]

    def questions_left(self):
        """Returns the number of questions left."""
        return self.questions_unanswered.count()

    def completed_question(self, question_id):
        """
            Adds the completed question to the list of answered
            questions and returns the next question.
        """
        self.questions_answered.add(question_id)
        self.questions_unanswered.remove(question_id)

        return self.current_question()

    def skip(self, question_id):
        """
            Skips the current question and returns the next sequentially
             available question.
        """
        questions = self.questions_unanswered.all()
        question_cycle = cycle(questions)
        for question in question_cycle:
            if question.id==int(question_id):
                return question_cycle.next()

    def time_left(self):
        """Return the time remaining for the user in seconds."""
        dt = timezone.now() - self.start_time
        try:
            secs = dt.total_seconds()
        except AttributeError:
            # total_seconds is new in Python 2.7. :(
            secs = dt.seconds + dt.days*24*3600
        total = self.question_paper.quiz.duration*60.0
        remain = max(total - secs, 0)
        return int(remain)

    def _update_marks_obtained(self):
        """Updates the total marks earned by student for this paper."""
        marks = sum([x.marks for x in self.answers.filter(marks__gt=0.0)])
        if not marks:
            self.marks_obtained = 0
        else:
            self.marks_obtained = marks

    def _update_percent(self):
        """Updates the percent gained by the student for this paper."""
        total_marks = self.question_paper.total_marks
        if self.marks_obtained is not None:
            percent = self.marks_obtained/self.question_paper.total_marks*100
            self.percent = round(percent, 2)

    def _update_passed(self):
        """
            Checks whether student passed or failed, as per the quiz
            passing criteria.
        """
        if self.percent is not None:
            if self.percent >= self.question_paper.quiz.pass_criteria:
                self.passed = True
            else:
                self.passed = False

    def _update_status(self, state):
        """ Sets status as inprogress or completed """
        self.status = state

    def update_marks(self, state='completed'):
        self._update_marks_obtained()
        self._update_percent()
        self._update_passed()
        self._update_status(state)
        self.save()

    def set_end_time(self, datetime):
        """ Sets end time """
        self.end_time = datetime
        self.save()

    def get_question_answers(self):
        """
            Return a dictionary with keys as questions and a list of the
            corresponding answers.
        """
        q_a = {}
        for answer in self.answers.all():
            question = answer.question
            if question in q_a:
                q_a[question].append(answer)
            else:
                q_a[question] = [answer]
        return q_a

    def get_questions(self):
        return self.questions.all()

    def get_questions_answered(self):
        return self.questions_answered.all()

    def get_questions_unanswered(self):
        return self.questions_unanswered.all()

    def is_answer_correct(self, question_id):
        ''' Return marks of a question answered'''
        return self.answers.filter(question_id=question_id,
                                   correct=True).exists()

    def is_attempt_inprogress(self):
        if self.status == 'inprogress':
            return self.time_left()> 0

    def get_previous_answers(self, question):
        if question.type == 'code':
            return self.answers.filter(question=question).order_by('-id')

    def __unicode__(self):
        u = self.user
        return u'Question paper for {0} {1}'.format(u.first_name, u.last_name)


###############################################################################
class AssignmentUpload(models.Model):
    user = models.ForeignKey(Profile)
    assignmentQuestion = models.ForeignKey(Question)
    assignmentFile = models.FileField(upload_to=get_assignment_dir)


################################################################################
class TestCase(models.Model):
    question = models.ForeignKey(Question, blank=True, null = True)

class StandardTestCase(TestCase):
    test_case = models.TextField(blank=True)

    def get_field_value(self):
        return {"test_case": self.test_case}

    def __unicode__(self):
        return u'Question: {0} | Test Case: {1}'.format(self.question,
            self.test_case
        )


class StdoutBasedTestCase(TestCase):
    expected_output = models.TextField(blank=True)

    def get_field_value(self):
        return {"expected_output": self.expected_output}

    def __unicode__(self):
        return u'Question: {0} | Exp. Output: {1}'.format(self.question,
            self.expected_output
        )


class McqTestCase(TestCase):
    options = models.TextField()
    correct = models.BooleanField(default=False)

    def get_field_value(self):
        return {"options": self.options, "correct": self.correct}

    def __unicode__(self):
        return u'Question: {0} | Correct: {1}'.format(self.question,
            self.correct
        )