RootView = require 'views/core/RootView'
State = require 'models/State'
template = require 'templates/courses/teacher-class-view'
helper = require 'lib/coursesHelper'
ClassroomSettingsModal = require 'views/courses/ClassroomSettingsModal'
InviteToClassroomModal = require 'views/courses/InviteToClassroomModal'
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
EditStudentModal = require 'views/teachers/EditStudentModal'
RemoveStudentModal = require 'views/courses/RemoveStudentModal'
CoursesNotAssignedModal = require './CoursesNotAssignedModal'

Campaigns = require 'collections/Campaigns'
Classroom = require 'models/Classroom'
Classrooms = require 'collections/Classrooms'
Levels = require 'collections/Levels'
LevelSessions = require 'collections/LevelSessions'
User = require 'models/User'
Users = require 'collections/Users'
Course = require 'models/Course'
Courses = require 'collections/Courses'
CourseInstance = require 'models/CourseInstance'
CourseInstances = require 'collections/CourseInstances'
Prepaids = require 'collections/Prepaids'

module.exports = class TeacherClassView extends RootView
  id: 'teacher-class-view'
  template: template
  helper: helper

  events:
    'click .nav-tabs a': 'onClickNavTabLink'
    'click .unarchive-btn': 'onClickUnarchive'
    'click .edit-classroom': 'onClickEditClassroom'
    'click .add-students-btn': 'onClickAddStudents'
    'click .edit-student-link': 'onClickEditStudentLink'
    'click .sort-button': 'onClickSortButton'
    'click #copy-url-btn': 'onClickCopyURLButton'
    'click #copy-code-btn': 'onClickCopyCodeButton'
    'click .remove-student-link': 'onClickRemoveStudentLink'
    'click .assign-student-button': 'onClickAssignStudentButton'
    'click .enroll-student-button': 'onClickEnrollStudentButton'
    'click .assign-to-selected-students': 'onClickBulkAssign'
    'click .export-student-progress-btn': 'onClickExportStudentProgress'
    'click .select-all': 'onClickSelectAll'
    'click .student-checkbox': 'onClickStudentCheckbox'
    'keyup #student-search': 'onKeyPressStudentSearch'
    'change .course-select, .bulk-course-select': 'onChangeCourseSelect'

  getInitialState: ->
    {
      sortAttribute: 'name'
      sortDirection: 1
      activeTab: '#' + (Backbone.history.getHash() or 'students-tab')
      students: new Users()
      classCode: ""
      joinURL: ""
      errors:
        assigningToNobody: false
      selectedCourse: undefined
      checkboxStates: {}
      classStats:
        averagePlaytime: ""
        totalPlaytime: ""
        averageLevelsComplete: ""
        totalLevelsComplete: ""
        enrolledUsers: ""
    }

  getTitle: -> return @classroom?.get('name')

  initialize: (options, classroomID) ->
    super(options)
    @singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
    @singleStudentLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-level'
    @allStudentsLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-all-students-single-level'
    @urls = require('core/urls')

    @debouncedRender = _.debounce @render

    @state = new State(@getInitialState())
    @updateHash @state.get('activeTab') # TODO: Don't push to URL history (maybe don't use url fragment for default tab)

    @classroom = new Classroom({ _id: classroomID })
    @supermodel.trackRequest @classroom.fetch()
    @onKeyPressStudentSearch = _.debounce(@onKeyPressStudentSearch, 200)
    @sortedCourses = []

    @prepaids = new Prepaids()
    @prepaids.comparator = 'endDate' # use prepaids in order of expiration
    @supermodel.trackRequest @prepaids.fetchByCreator(me.id)

    @students = new Users()
    @listenTo @classroom, 'sync', ->
      jqxhrs = @students.fetchForClassroom(@classroom, removeDeleted: true)
      @supermodel.trackRequests jqxhrs

      @classroom.sessions = new LevelSessions()
      requests = @classroom.sessions.fetchForAllClassroomMembers(@classroom)
      @supermodel.trackRequests(requests)

    @students.comparator = (student1, student2) =>
      dir = @state.get('sortDirection')
      value = @state.get('sortValue')
      if value is 'name'
        return (if student1.broadName().toLowerCase() < student2.broadName().toLowerCase() then -dir else dir)

      if value is 'progress'
        # TODO: I would like for this to be in the Level model,
        #   but it doesn't know about its own courseNumber.
        level1 = student1.latestCompleteLevel
        level2 = student2.latestCompleteLevel
        return -dir if not level1
        return dir if not level2
        return dir * (level1.courseNumber - level2.courseNumber or level1.levelNumber - level2.levelNumber)

      if value is 'status'
        statusMap = { expired: 0, 'not-enrolled': 1, enrolled: 2 }
        diff = statusMap[student1.prepaidStatus()] - statusMap[student2.prepaidStatus()]
        return dir * diff if diff
        return (if student1.broadName().toLowerCase() < student2.broadName().toLowerCase() then -dir else dir)

    @courses = new Courses()
    @supermodel.trackRequest @courses.fetch()

    @courseInstances = new CourseInstances()
    @supermodel.trackRequest @courseInstances.fetchForClassroom(classroomID)

    @levels = new Levels()
    @supermodel.trackRequest @levels.fetchForClassroom(classroomID, {data: {project: 'original,concepts,primerLanguage,practice,shareable,i18n'}})

    @attachMediatorEvents()
    window.tracker?.trackEvent 'Teachers Class Loaded', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']

  attachMediatorEvents: () ->
    # Model/Collection events
    @listenTo @classroom, 'sync change update', ->
      classCode = @classroom.get('codeCamel') or @classroom.get('code')
      @state.set {
        classCode: classCode
        joinURL: document.location.origin + "/students?_cc=" + classCode
      }
    @listenTo @courses, 'sync change update', ->
      @setCourseMembers() # Is this necessary?
      @state.set selectedCourse: @courses.first() unless @state.get('selectedCourse')
    @listenTo @courseInstances, 'sync change update', ->
      @setCourseMembers()
    @listenTo @students, 'sync change update add remove reset', ->
      # Set state/props of things that depend on students?
      # Set specific parts of state based on the models, rather than just dumping the collection there?
      @calculateProgressAndLevels()
      classStats = @calculateClassStats()
      @state.set classStats: classStats if classStats
      @state.set students: @students
      checkboxStates = {}
      for student in @students.models
        checkboxStates[student.id] = @state.get('checkboxStates')[student.id] or false
      @state.set { checkboxStates }
    @listenTo @students, 'sort', ->
      @state.set students: @students
    @listenTo @, 'course-select:change', ({ selectedCourse }) ->
      @state.set selectedCourse: selectedCourse

  setCourseMembers: =>
    for course in @courses.models
      course.instance = @courseInstances.findWhere({ courseID: course.id, classroomID: @classroom.id })
      course.members = course.instance?.get('members') or []
    null

  onLoaded: ->
    @sortedCourses = @classroom.getSortedCourses()
    @removeDeletedStudents() # TODO: Move this to mediator listeners? For both classroom and students?
    @calculateProgressAndLevels()

    # render callback setup
    @listenTo @courseInstances, 'sync change update', @debouncedRender
    @listenTo @state, 'sync change', ->
      if _.isEmpty(_.omit(@state.changed, 'searchTerm'))
        @renderSelectors('#license-status-table')
      else
        @debouncedRender()
    @listenTo @students, 'sort', @debouncedRender
    super()

  afterRender: ->
    super(arguments...)
    $('.progress-dot, .btn-view-project-level').each (i, el) ->
      dot = $(el)
      dot.tooltip({
        html: true
        container: dot
      }).delegate '.tooltip', 'mousemove', ->
        dot.tooltip('hide')

  calculateProgressAndLevels: ->
    return unless @supermodel.progress is 1
    # TODO: How to structure this in @state?
    for student in @students.models
      # TODO: this is a weird hack
      studentsStub = new Users([ student ])
      student.latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, studentsStub)

    earliestIncompleteLevel = helper.calculateEarliestIncomplete(@classroom, @courses, @courseInstances, @students)
    latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, @students)

    classroomsStub = new Classrooms([ @classroom ])
    progressData = helper.calculateAllProgress(classroomsStub, @courses, @courseInstances, @students)
    # conceptData: helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)

    @state.set {
      earliestIncompleteLevel
      latestCompleteLevel
      progressData
      classStats: @calculateClassStats()
    }

  onClickNavTabLink: (e) ->
    e.preventDefault()
    hash = $(e.target).closest('a').attr('href')
    @updateHash(hash)
    @state.set activeTab: hash

  updateHash: (hash) ->
    return if application.testing
    window.location.hash = hash

  onClickCopyCodeButton: ->
    window.tracker?.trackEvent 'Teachers Class Copy Class Code', category: 'Teachers', classroomID: @classroom.id, classCode: @state.get('classCode'), ['Mixpanel']
    @$('#join-code-input').val(@state.get('classCode')).select()
    @tryCopy()

  onClickCopyURLButton: ->
    window.tracker?.trackEvent 'Teachers Class Copy Class URL', category: 'Teachers', classroomID: @classroom.id, url: @state.get('joinURL'), ['Mixpanel']
    @$('#join-url-input').val(@state.get('joinURL')).select()
    @tryCopy()

  onClickUnarchive: ->
    window.tracker?.trackEvent 'Teachers Class Unarchive', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
    @classroom.save { archived: false }

  onClickEditClassroom: (e) ->
    window.tracker?.trackEvent 'Teachers Class Edit Class Started', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
    classroom = @classroom
    modal = new ClassroomSettingsModal({ classroom: classroom })
    @openModalView(modal)
    @listenToOnce modal, 'hide', @render

  onClickEditStudentLink: (e) ->
    window.tracker?.trackEvent 'Teachers Class Students Edit', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
    user = @students.get($(e.currentTarget).data('student-id'))
    modal = new EditStudentModal({ user, @classroom })
    @openModalView(modal)

  onClickRemoveStudentLink: (e) ->
    user = @students.get($(e.currentTarget).data('student-id'))
    modal = new RemoveStudentModal({
      classroom: @classroom
      user: user
      courseInstances: @courseInstances
    })
    @openModalView(modal)
    modal.once 'remove-student', @onStudentRemoved, @

  onStudentRemoved: (e) ->
    @students.remove(e.user)
    window.tracker?.trackEvent 'Teachers Class Students Removed', category: 'Teachers', classroomID: @classroom.id, userID: e.user.id, ['Mixpanel']

  onClickAddStudents: (e) =>
    window.tracker?.trackEvent 'Teachers Class Add Students', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
    modal = new InviteToClassroomModal({ classroom: @classroom })
    @openModalView(modal)
    @listenToOnce modal, 'hide', @render

  removeDeletedStudents: () ->
    return unless @classroom.loaded and @students.loaded
    _.remove(@classroom.get('members'), (memberID) =>
      not @students.get(memberID) or @students.get(memberID)?.get('deleted')
    )
    true

  onClickSortButton: (e) ->
    value = $(e.target).val()
    if value is @state.get('sortValue')
      @state.set('sortDirection', -@state.get('sortDirection'))
    else
      @state.set({
        sortValue: value
        sortDirection: 1
      })
    @students.sort()

  onKeyPressStudentSearch: (e) ->
    @state.set('searchTerm', $(e.target).val())

  onChangeCourseSelect: (e) ->
    @trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) }

  getSelectedStudentIDs: ->
    Object.keys(_.pick @state.get('checkboxStates'), (checked) -> checked)

  ensureInstance: (courseID) ->

  onClickEnrollStudentButton: (e) ->
    userID = $(e.currentTarget).data('user-id')
    user = @students.get(userID)
    selectedUsers = new Users([user])
    @enrollStudents(selectedUsers)
    window.tracker?.trackEvent $(e.currentTarget).data('event-action'), category: 'Teachers', classroomID: @classroom.id, userID: userID, ['Mixpanel']

  enrollStudents: (selectedUsers) ->
    modal = new ActivateLicensesModal { @classroom, selectedUsers, users: @students }
    @openModalView(modal)
    modal.once 'redeem-users', (enrolledUsers) =>
      enrolledUsers.each (newUser) =>
        user = @students.get(newUser.id)
        if user
          user.set(newUser.attributes)
      null

  onClickExportStudentProgress: ->
    # TODO: Does not yield .csv download on Safari, and instead opens a new tab with the .csv contents
    window.tracker?.trackEvent 'Teachers Class Export CSV', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
    courseLabels = ""
    courseOrder = []
    courses = (@courses.get(c._id) for c in @classroom.get('courses'))
    courseLabelsArray = helper.courseLabelsArray courses
    for course, index in courses
      courseLabels += "#{courseLabelsArray[index]} Playtime,"
      courseOrder.push(course.id)
    csvContent = "data:text/csv;charset=utf-8,Username,Email,Total Playtime,#{courseLabels}Concepts\n"
    levelCourseMap = {}
    language = @classroom.get('aceConfig')?.language
    for trimCourse in @classroom.get('courses')
      for trimLevel in trimCourse.levels
        continue if language and trimLevel.primerLanguage is language
        levelCourseMap[trimLevel.original] = @courses.get(trimCourse._id)
    for student in @students.models
      concepts = []
      for trimCourse in @classroom.get('courses')
        course = @courses.get(trimCourse._id)
        instance = @courseInstances.findWhere({ courseID: course.id, classroomID: @classroom.id })
        if instance and instance.hasMember(student)
          for trimLevel in trimCourse.levels
            level = @levels.findWhere({ original: trimLevel.original })
            progress = @state.get('progressData').get({ classroom: @classroom, course: course, level: level, user: student })
            concepts.push(level.get('concepts') ? []) if progress?.completed
      concepts = _.union(_.flatten(concepts))
      conceptsString = _.map(concepts, (c) -> $.i18n.t("concepts." + c)).join(', ')
      coursePlaytimeMap = {}
      playtime = 0
      for session in @classroom.sessions.models when session.get('creator') is student.id
        playtime += session.get('playtime') or 0
        if courseID = levelCourseMap[session.get('level')?.original]?.id
          coursePlaytimeMap[courseID] ?= 0
          coursePlaytimeMap[courseID] += session.get('playtime') or 0
      playtimeString = if playtime is 0 then "0" else moment.duration(playtime, 'seconds').humanize()
      for course in courses
        coursePlaytimeMap[course.id] ?= 0
      coursePlaytimes = []
      for courseID, playtime of coursePlaytimeMap
        coursePlaytimes.push
          courseID: courseID
          playtime: playtime
      coursePlaytimes.sort (a, b) ->
        return -1 if courseOrder.indexOf(a.courseID) < courseOrder.indexOf(b.courseID)
        return 0 if courseOrder.indexOf(a.courseID) is courseOrder.indexOf(b.courseID)
        return 1
      coursePlaytimesString = ""
      for coursePlaytime, index in coursePlaytimes
        if coursePlaytime.playtime is 0
          coursePlaytimesString += "0,"
        else
          coursePlaytimesString += "#{moment.duration(coursePlaytime.playtime, 'seconds').humanize()},"
      csvContent += "#{student.get('name')},#{student.get('email') or ''},#{playtimeString},#{coursePlaytimesString}\"#{conceptsString}\"\n"
    csvContent = csvContent.substring(0, csvContent.length - 1)
    encodedUri = encodeURI(csvContent)
    window.open(encodedUri)

  onClickAssignStudentButton: (e) ->
    userID = $(e.currentTarget).data('user-id')
    user = @students.get(userID)
    members = [userID]
    courseID = $(e.currentTarget).data('course-id')
    @assignCourse courseID, members
    window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, userID: userID, ['Mixpanel']

  onClickBulkAssign: ->
    courseID = @$('.bulk-course-select').val()
    selectedIDs = @getSelectedStudentIDs()
    assigningToNobody = selectedIDs.length is 0
    @state.set errors: { assigningToNobody }
    return if assigningToNobody
    @assignCourse courseID, selectedIDs
    window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, ['Mixpanel']

  assignCourse: (courseID, members) ->
    courseInstance = null
    numberEnrolled = 0
    remainingSpots = 0
    
    return Promise.resolve()
    .then =>
      courseInstance = @courseInstances.findWhere({ courseID, classroomID: @classroom.id })
      if not courseInstance
        courseInstance = new CourseInstance {
          courseID,
          classroomID: @classroom.id
          ownerID: @classroom.get('ownerID')
          aceConfig: {}
        }
        courseInstance.notyErrors = false # handling manually
        @courseInstances.add(courseInstance)
        return courseInstance.save()
        
    .then =>
      availablePrepaids = @prepaids.filter((prepaid) -> prepaid.status() is 'available')
      unenrolledStudents = _(members)
        .map((userID) => @students.get(userID))
        .filter((user) => user.prepaidStatus() isnt 'enrolled')
        .value()
      totalSpotsAvailable = _.reduce(prepaid.openSpots() for prepaid in availablePrepaids, (val, total) -> val + total) or 0
      if totalSpotsAvailable < _.size(unenrolledStudents)
        modal = new CoursesNotAssignedModal({
          selected: members.length
          totalSpotsAvailable
          unenrolledStudents: _.size(unenrolledStudents)
        })
        @openModalView(modal)
        error = new Error('Not enough licenses available')
        error.handled = true
        throw error
        
      numberEnrolled = _.size(unenrolledStudents)
      remainingSpots = totalSpotsAvailable - numberEnrolled
      
      requests = []
      for prepaid in availablePrepaids
        for i in _.range(prepaid.openSpots())
          break unless _.size(unenrolledStudents) > 0
          user = unenrolledStudents.shift()
          requests.push(prepaid.redeem(user))
      
      @trigger 'begin-redeem-for-assign-course' 
      return $.when(requests...)

    .then =>
      # refresh prepaids, since the racing multiple parallel redeem requests in the previous `then` probably did not
      # end up returning the final result of all those requests together.
      @prepaids.fetchByCreator(me.id) 
      
      @trigger 'begin-assign-course'
      if members.length
        return courseInstance.addMembers(members)
      
    .then =>
      course = @courses.get(courseID)
      lines = [
        $.i18n.t('teacher.assigned_msg_1')
          .replace('{{numberAssigned}}', members.length)
          .replace('{{courseName}}', course.get('name'))
      ]
      if numberEnrolled > 0
        lines.push(
          $.i18n.t('teacher.assigned_msg_2')
            .replace('{{numberEnrolled}}', numberEnrolled)
        )
        lines.push(
          $.i18n.t('teacher.assigned_msg_3')
          .replace('{{remainingSpots}}', remainingSpots)
        )
      noty text: lines.join('<br />'), layout: 'center', type: 'information', killer: true, timeout: 5000
    
    .catch (e) =>
      # TODO: Use this handling for errors site-wide?
      return if e.handled
      throw e if e instanceof Error and application.testing
      text = if e instanceof Error then 'Runtime error' else e.responseJSON?.message or e.message or $.i18n.t('loading_error.unknown') 
      noty { text, layout: 'center', type: 'error', killer: true, timeout: 5000 }

  onClickSelectAll: (e) ->
    e.preventDefault()
    checkboxStates = _.clone @state.get('checkboxStates')
    if _.all(checkboxStates)
      for studentID of checkboxStates
        checkboxStates[studentID] = false
    else
      for studentID of checkboxStates
        checkboxStates[studentID] = true
    @state.set { checkboxStates }

  onClickStudentCheckbox: (e) ->
    e.preventDefault()
    checkbox = $(e.currentTarget).find('input')
    studentID = checkbox.data('student-id')
    checkboxStates = _.clone @state.get('checkboxStates')
    checkboxStates[studentID] = not checkboxStates[studentID]
    @state.set { checkboxStates }

  calculateClassStats: ->
    return {} unless @classroom.sessions?.loaded and @students.loaded
    stats = {}

    playtime = 0
    total = 0
    for session in @classroom.sessions.models
      pt = session.get('playtime') or 0
      playtime += pt
      total += 1
    stats.averagePlaytime = if playtime and total then moment.duration(playtime / total, "seconds").humanize() else 0
    stats.totalPlaytime = if playtime then moment.duration(playtime, "seconds").humanize() else 0
    # TODO: Humanize differently ('1 hour' instead of 'an hour')

    levelIncludeMap = {}
    language = @classroom.get('aceConfig')?.language
    for level in @levels.models
      levelIncludeMap[level.get('original')] = not level.get('practice') and (not language? or level.get('primerLanguage') isnt language)
    completeSessions = @classroom.sessions.filter (s) -> s.get('state')?.complete and levelIncludeMap[s.get('level')?.original]
    stats.averageLevelsComplete = if @students.size() then (_.size(completeSessions) / @students.size()).toFixed(1) else 'N/A'  # '
    stats.totalLevelsComplete = _.size(completeSessions)

    enrolledUsers = @students.filter (user) -> user.isEnrolled()
    stats.enrolledUsers = _.size(enrolledUsers)

    return stats

  studentStatusString: (student) ->
    status = student.prepaidStatus()
    expires = student.get('coursePrepaid')?.endDate
    string = switch status
      when 'not-enrolled' then $.i18n.t('teacher.status_not_enrolled')
      when 'enrolled' then (if expires then $.i18n.t('teacher.status_enrolled') else '-')
      when 'expired' then $.i18n.t('teacher.status_expired')
    return string.replace('{{date}}', moment(expires).utc().format('l'))
