#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import with_statement


import os
import ldap
import time
import paramiko
import traceback
import codecs
import subprocess
from cgi import escape

from vmchecker import paths, update_db, penalty, submissions, submit, coursedb
from vmchecker.coursedb import opening_course_db
from vmchecker.courselist import CourseList
from vmchecker.config import LdapConfig, StorerCourseConfig

try:
    import simplejson as json
except ImportError:
    import json

# The buffer size used for copying temporary files
FILE_BUF_SIZE=10000

# If requested, remember user for up to two weeks
EXTENDED_SESSION_TIMEOUT = 60 * 60 * 24 * 14;

# .vmr files may be very large because of errors in the student's submission.
MAX_VMR_FILE_SIZE = 5 * 1024 * 1024 # 500 KB

# define ERROR_MESSAGES
ERR_AUTH = 1
ERR_EXCEPTION = 2
ERR_OTHER = 3

# define MD5 processing errors
MD5_ERR_BAD_MD5 = 'md5'
MD5_ERR_BAD_ZIP = 'zip'


# I18N support
import gettext
# NOTE: This is where the locale files are installed by default.
# Ideally this shouldn't be hardcoded, but it will do for now.
DEFAULT_LOCALE_PATH="/usr/local/share/locale"
DEFAULT_LOCALE="en"
lang_en = gettext.translation("vmchecker", DEFAULT_LOCALE_PATH, languages=["en"])
lang_fr = gettext.translation("vmchecker", DEFAULT_LOCALE_PATH, languages=["fr"])
lang_ro = gettext.translation("vmchecker", DEFAULT_LOCALE_PATH, languages=["ro"])

def install_i18n(lang):
    if lang == "en":
        lang_en.install()
    elif lang == "ro":
        lang_ro.install()
    elif lang == "fr":
        lang_fr.install()
    else:
        lang_en.install()
# End I18N support


class OutputString():
    def __init__(self):
        self.st = ""

    def write(self, st):
        self.st += st

    def get(self):
        return self.st

def xssescape(text):
    """Gets rid of < and > and & and, for good measure, :"""
    return escape(text, quote=True).replace(':','&#58;')

def get_user(username, password):
    """Find the username for a user based on username/password.

    This searches LDAP or some file-based json files in the user's
    home directories.

    Returns the username on success.
    """

    # allthough a misconfigured user can block access to any course,
    # we preffer early LOUD errors to silently ignored ones.
    # Fail fast, fail lowdly!
    r = get_user_from_auth_files(username, password)
    if not r is None:
        return r
    try:
        r = get_ldap_user(username, password)
    except:
        r = None
    return r


def get_user_from_auth_files(username, password):
    """Search all courseses for auth_files and if we can login in any
    course, return the login from there"""
    for coursecfg_fname in CourseList().course_configs():
        vmpaths = paths.VmcheckerPaths(StorerCourseConfig(coursecfg_fname).root_path())
        r = get_user_from_auth_file(vmpaths, username, password)
        if not r is None:
            return r
    return None



def get_user_from_auth_file(vmpaths, username, password):
    """Try to authenticate using one course's auth_file.

    Return the username on success.
    """
    if not os.path.exists(vmpaths.auth_file()):
        return None
    with open(vmpaths.auth_file()) as handle:
        auth_file_contents =  handle.read()
        auth_dic = json.loads(auth_file_contents)['auth']
    if auth_dic.has_key(username) and auth_dic[username] == password:
        return username
    return None


def get_ldap_user(username, password):
    """Try to authenticate using the global LDAP configuration file.

    Return the username on success.
    """
    ldap_cfg = LdapConfig()
    con = ldap.initialize(ldap_cfg.server())
    if not ldap_cfg.bind_anonymous():
        con.simple_bind_s(ldap_cfg.bind_user(),
                            ldap_cfg.bind_pass())

    baseDN = ldap_cfg.root_search()
    searchScope = ldap.SCOPE_SUBTREE
    retrieveAttributes = None

    # XXX : Needs sanitation
    searchFilter = '(uid=' + username + ')'

    timeout = 0
    count = 0

    # find the user's dn
    result_id = con.search(baseDN,
                        searchScope,
                        searchFilter,
                        retrieveAttributes)
    result_set = []
    while 1:
        result_type, result_data = con.result(result_id, timeout)
        if (result_data == []):
            break
        else:
            if result_type == ldap.RES_SEARCH_ENTRY:
                result_set.append(result_data)

    if len(result_set) == 0:
        #no results
        return None

    if len(result_set) > 1:
        # too many results for the same uid
        raise

    user_dn, entry = result_set[0][0]
    if not ldap_cfg.bind_anonymous():
        con.unbind_s()

    # check the password
    try:
        con = ldap.initialize(ldap_cfg.server())
        con.simple_bind_s(user_dn, password)
    except ldap.INVALID_CREDENTIALS:
        return None
    except:
        raise

    return entry['cn'][0]


def _find_file(searched_file_name, rfiles):
    """Search for a filename in an array for {fname:fcontent} dicts"""
    for rfile in rfiles:
        if rfile.has_key(searched_file_name):
            return rfile
    return None



def submission_upload_info(vmcfg, courseId, assignment, account, isTeamAccount, isGraded):
    """Return a string explaining the submission upload time, deadline
    and the late submission penalty
    """

    vmpaths = paths.VmcheckerPaths(vmcfg.root_path())
    sbroot = vmpaths.dir_cur_submission_root(assignment, account)
    grade_file = paths.submission_results_grade(sbroot)
    sbcfg = paths.submission_config_file(sbroot)
    if not os.path.exists(sbcfg):
        return _("No submission exists for this assignment")

    late_penalty = update_db.compute_late_penalty(assignment, account, vmcfg)
    ta_penalty   = update_db.compute_TA_penalty(grade_file)
    deadline_str = vmcfg.assignments().get(assignment, 'Deadline')
    total_points = int(vmcfg.assignments().get(assignment, 'TotalPoints'))
    deadline_struct = time.strptime(vmcfg.assignments().get(assignment, 'Deadline'),
                                    penalty.DATE_FORMAT)
    sss = submissions.Submissions(vmpaths)
    upload_time_str = sss.get_upload_time_str(assignment, account)
    upload_time_struct = sss.get_upload_time_struct(assignment, account)

    deadline_explanation = penalty.verbose_time_difference(upload_time_struct, deadline_struct)

    submitter_explanation = None
    if isTeamAccount:
        submitting_user = sss.get_submitting_user(assignment, account)
        if submitting_user is not None:
            submitter_explanation = _("Submitted by") + ": " + submitting_user

    max_line_width = 0
    rows_to_print = []

    if submitter_explanation is not None:
        rows_to_print += [
            [ submitter_explanation ],
            [ '' ]
        ]

    rows_to_print += [
        [ _("Submission date"), upload_time_str ],
        [ _("Assignment deadline"), deadline_str ],
        [ deadline_explanation ]
    ]

    if isGraded or not vmcfg.assignments().is_deadline_hard(assignment):
        rows_to_print += [
            [ '' ]
        ]

    if not vmcfg.assignments().is_deadline_hard(assignment):
        rows_to_print += [
            [ _("Penalty (late submission)"), str(late_penalty) ],
        ]

    if isGraded:
        rows_to_print += [
            [ _("Penalty (grading)"), str(ta_penalty) ],
            [ _("Penalty (total)"), str(ta_penalty + late_penalty) ],
            [ '' ],
            [ _("Grade"), str(total_points + ta_penalty + late_penalty) ]
        ]

    for row in rows_to_print:
        row[0] = row[0].decode("utf-8")
        if len(row) == 2 and len(row[0]) > max_line_width:
            max_line_width = len(row[0])

    if isGraded:
        # Put a dashed line just above the 'Grade' line
        rows_to_print[len(rows_to_print) - 2][0] = '-' * max_line_width

    ret = u""
    for row in rows_to_print:
        if len(row) == 1:
            ret += row[0] + "\n"
        elif len(row) == 2:
            ret += unicode("{0[0]:<" + str(max_line_width) + "} : {0[1]}\n").format(row)