CocoCollection = require 'collections/CocoCollection'
Course = require 'models/Course'
CourseInstance = require 'models/CourseInstance'
require 'vendor/d3'
d3Utils = require 'core/d3_utils'
Payment = require 'models/Payment'
RootView = require 'views/core/RootView'
template = require 'templates/admin/analytics'
utils = require 'core/utils'

module.exports = class AnalyticsView extends RootView
  id: 'admin-analytics-view'
  template: template
  furthestCourseDayRangeRecent: 60
  furthestCourseDayRange: 365
  lineColors: ['red', 'blue', 'green', 'purple', 'goldenrod', 'brown', 'darkcyan']
  minSchoolCount: 20

  initialize: ->
    @activeClasses = []
    @activeClassGroups = {}
    @activeUsers = []
    @revenue = []
    @revenueGroups = {}
    @dayEnrollmentsMap = {}
    @enrollmentDays = []
    @loadData()

  afterRender: ->
    super()
    @createLineCharts()

  loadData: ->
    @supermodel.addRequestResource({
      url: '/db/analytics_perday/-/active_classes'
      method: 'POST'
      success: (data) =>
        # Organize data by day, then group
        groupMap = {}
        dayGroupMap = {}
        for activeClass in data
          dayGroupMap[activeClass.day] ?= {}
          dayGroupMap[activeClass.day]['Total'] = 0
          for group, val of activeClass.classes
            groupMap[group] = true
            dayGroupMap[activeClass.day][group] = val
            dayGroupMap[activeClass.day]['Total'] += val
        @activeClassGroups = Object.keys(groupMap)
        @activeClassGroups.push 'Total'
        # Build list of active classes, where each entry is a day of individual group values
        @activeClasses = []
        for day of dayGroupMap
          dashedDay = "#{day.substring(0, 4)}-#{day.substring(4, 6)}-#{day.substring(6, 8)}"
          data = day: dashedDay, groups: []
          for group in @activeClassGroups
            data.groups.push(dayGroupMap[day][group] ? 0)
          @activeClasses.push data
        @activeClasses.sort (a, b) -> b.day.localeCompare(a.day)

        @updateAllKPIChartData()
        @updateActiveClassesChartData()
        @render?()
    }, 0).load()

    @supermodel.addRequestResource({
      url: '/db/analytics_perday/-/active_users'
      method: 'POST'
      success: (data) =>
        @activeUsers = data.map (a) ->
          a.day = "#{a.day.substring(0, 4)}-#{a.day.substring(4, 6)}-#{a.day.substring(6, 8)}"
          a

        # Add campaign/classroom DAU 30-day averages and daily totals
        campaignDauTotals = []
        classroomDauTotals = []
        eventMap = {}
        for entry in @activeUsers
          day = entry.day
          campaignDauTotal = 0
          classroomDauTotal = 0
          for event, count of entry.events
            if event.indexOf('DAU campaign') >= 0
              campaignDauTotal += count
            else if event.indexOf('DAU classroom') >= 0
              classroomDauTotal += count
            eventMap[event] = true
          entry.events['DAU campaign total'] = campaignDauTotal
          eventMap['DAU campaign total'] = true
          campaignDauTotals.unshift(campaignDauTotal)
          campaignDauTotals.pop() while campaignDauTotals.length > 30
          if campaignDauTotals.length is 30
            entry.events['DAU campaign 30-day average'] = Math.round(_.reduce(campaignDauTotals, (a, b) -> a + b) / 30)
            eventMap['DAU campaign 30-day average'] = true
          entry.events['DAU classroom total'] = classroomDauTotal
          eventMap['DAU classroom total'] = true
          classroomDauTotals.unshift(classroomDauTotal)
          classroomDauTotals.pop() while classroomDauTotals.length > 30
          if classroomDauTotals.length is 30
            entry.events['DAU classroom 30-day average'] = Math.round(_.reduce(classroomDauTotals, (a, b) -> a + b) / 30)
            eventMap['DAU classroom 30-day average'] = true

        @activeUsers.sort (a, b) -> b.day.localeCompare(a.day)
        @activeUserEventNames = Object.keys(eventMap)
        @activeUserEventNames.sort (a, b) ->
          if a.indexOf('campaign') is b.indexOf('campaign') or a.indexOf('classroom') is b.indexOf('classroom')
            a.localeCompare(b)
          else if a.indexOf('campaign') > b.indexOf('campaign')
            1
          else
            -1

        @updateAllKPIChartData()
        @updateActiveUsersChartData()
        @updateCampaignVsClassroomActiveUsersChartData()
        @render?()
    }, 0).load()

    @supermodel.addRequestResource({
      url: '/db/analytics_perday/-/recurring_revenue'
      method: 'POST'
      success: (data) =>

        # Organize data by day, then group
        groupMap = {}
        dayGroupCountMap = {}
        for dailyRevenue in data
          dayGroupCountMap[dailyRevenue.day] ?= {}
          dayGroupCountMap[dailyRevenue.day]['DRR Total'] = 0
          for group, val of dailyRevenue.groups
            groupMap[group] = true
            dayGroupCountMap[dailyRevenue.day][group] = val
            dayGroupCountMap[dailyRevenue.day]['DRR Total'] += val
        @revenueGroups = Object.keys(groupMap)
        @revenueGroups.push 'DRR Total'

        # Build list of recurring revenue entries, where each entry is a day of individual group values
        @revenue = []
        for day of dayGroupCountMap
          dashedDay = "#{day.substring(0, 4)}-#{day.substring(4, 6)}-#{day.substring(6, 8)}"
          data = day: dashedDay, groups: []
          for group in @revenueGroups
            data.groups.push(dayGroupCountMap[day][group] ? 0)
          @revenue.push data

        # Order present to past
        @revenue.sort (a, b) -> b.day.localeCompare(a.day)

        return unless @revenue.length > 0

        # Add monthly recurring revenue values

        # For each daily group, add up monthly values walking forward through time, and add to revenue groups
        monthlyDailyGroupMap = {}
        dailyGroupIndexMap = {}
        for group, i in @revenueGroups
          monthlyDailyGroupMap[group.replace('DRR', 'MRR')] = group
          dailyGroupIndexMap[group] = i
        for monthlyGroup, dailyGroup of monthlyDailyGroupMap
          monthlyValues = []
          for i in [@revenue.length-1..0]
            dailyTotal = @revenue[i].groups[dailyGroupIndexMap[dailyGroup]]
            monthlyValues.push(dailyTotal)
            monthlyValues.shift() while monthlyValues.length > 30
            if monthlyValues.length is 30
              @revenue[i].groups.push(_.reduce(monthlyValues, (s, num) -> s + num))
        for monthlyGroup, dailyGroup of monthlyDailyGroupMap
          @revenueGroups.push monthlyGroup

        @updateAllKPIChartData()
        @updateRevenueChartData()
        @render?()

    }, 0).load()

    @supermodel.addRequestResource({
      url: '/db/user/-/school_counts'
      method: 'POST'
      data: {minCount: @minSchoolCount}
      success: (@schoolCounts) =>
        @schoolCounts?.sort (a, b) ->
          return -1 if a.count > b.count
          return 0 if a.count is b.count
          1
        @renderSelectors?('#school-counts')
    }, 0).load()

    @supermodel.addRequestResource({
      url: '/db/payment/-/school_sales'
      success: (@schoolSales) =>
        @schoolSales?.sort (a, b) ->
          return -1 if a.created > b.created
          return 0 if a.created is b.created
          1
        @renderSelectors?('.school-sales')
    }, 0).load()

    @supermodel.addRequestResource({
      url: '/db/prepaid/-/courses'
      method: 'POST'
      data: {project: {endDate: 1, maxRedeemers: 1, properties: 1, redeemers: 1}}
      success: (prepaids) =>
        paidDayMaxMap = {}
        paidDayRedeemedMap = {}
        trialDayMaxMap = {}
        trialDayRedeemedMap = {}
        for prepaid in prepaids
          day = utils.objectIdToDate(prepaid._id).toISOString().substring(0, 10)
          if prepaid.properties?.trialRequestID? or prepaid.properties?.endDate?
            trialDayMaxMap[day] ?= 0
            if prepaid.properties?.endDate?
              trialDayMaxMap[day] += prepaid.redeemers?.length ? 0
            else
              trialDayMaxMap[day] += prepaid.maxRedeemers
            for redeemer in (prepaid.redeemers ? [])
              redeemDay = redeemer.date.substring(0, 10)
              trialDayRedeemedMap[redeemDay] ?= 0
              trialDayRedeemedMap[redeemDay]++
          else if not prepaid.endDate? or new Date(prepaid.endDate) > new Date()
            paidDayMaxMap[day] ?= 0
            paidDayMaxMap[day] += prepaid.maxRedeemers
            for redeemer in prepaid.redeemers
              redeemDay = redeemer.date.substring(0, 10)
              paidDayRedeemedMap[redeemDay] ?= 0
              paidDayRedeemedMap[redeemDay]++
        @dayEnrollmentsMap = {}
        @paidCourseTotalEnrollments = []
        for day, count of paidDayMaxMap
          @paidCourseTotalEnrollments.push({day: day, count: count})
          @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0}
          @dayEnrollmentsMap[day].paidIssued += count
        @paidCourseTotalEnrollments.sort (a, b) -> a.day.localeCompare(b.day)
        @paidCourseRedeemedEnrollments = []
        for day, count of paidDayRedeemedMap
          @paidCourseRedeemedEnrollments.push({day: day, count: count})
          @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0}
          @dayEnrollmentsMap[day].paidRedeemed += count
        @paidCourseRedeemedEnrollments.sort (a, b) -> a.day.localeCompare(b.day)
        @trialCourseTotalEnrollments = []
        for day, count of trialDayMaxMap
          @trialCourseTotalEnrollments.push({day: day, count: count})
          @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0}
          @dayEnrollmentsMap[day].trialIssued += count
        @trialCourseTotalEnrollments.sort (a, b) -> a.day.localeCompare(b.day)
        @trialCourseRedeemedEnrollments = []
        for day, count of trialDayRedeemedMap
          @trialCourseRedeemedEnrollments.push({day: day, count: count})
          @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0}
          @dayEnrollmentsMap[day].trialRedeemed += count
        @trialCourseRedeemedEnrollments.sort (a, b) -> a.day.localeCompare(b.day)
        @updateEnrollmentsChartData()
        @render?()
    }, 0).load()

    @courses = new CocoCollection([], { url: "/db/course", model: Course})
    @listenToOnce @courses, 'sync', @onCoursesSync
    @supermodel.loadCollection(@courses)

  onCoursesSync: ->
    @courses.remove(@courses.findWhere({releasePhase: 'beta'}))
    sortedCourses = utils.sortCourses(@courses.models ? [])
    @courseOrderMap = {}
    @courseOrderMap[sortedCourses[i].get('_id')] = i for i in [0...sortedCourses.length]

    startDay = new Date()
    startDay.setUTCDate(startDay.getUTCDate() - @furthestCourseDayRange)
    startDay = startDay.toISOString().substring(0, 10)
    options =
      url: '/db/course_instance/-/recent'
      method: 'POST'
      data: {startDay: startDay}
    options.error = (models, response, options) =>
      return if @destroyed
      console.error 'Failed to get recent course instances', response
    options.success = (data) =>
      @onCourseInstancesSync(data)
      @renderSelectors?('#furthest-course')
    @supermodel.addRequestResource(options, 0).load()

  onCourseInstancesSync: (data) ->
    @courseDistributionsRecent = []
    @courseDistributions = []
    return unless data.courseInstances and data.students and data.prepaids

    createCourseDistributions = (numDays) =>
      # Find student furthest course
      startDate = new Date()
      startDate.setUTCDate(startDate.getUTCDate() - numDays)
      teacherStudentsMap = {}
      studentFurthestCourseMap = {}
      studentPaidStatusMap = {}
      for courseInstance in data.courseInstances
        continue if utils.objectIdToDate(courseInstance._id) < startDate
        courseID = courseInstance.courseID
        unless @courseOrderMap[courseID]?
          console.error "ERROR: no course order for courseID=#{courseID}"
          continue
        teacherID = courseInstance.ownerID
        for studentID in courseInstance.members
          studentPaidStatusMap[studentID] = 'free'
          if not studentFurthestCourseMap[studentID] or studentFurthestCourseMap[studentID] < @courseOrderMap[courseID]
            studentFurthestCourseMap[studentID] = @courseOrderMap[courseID]
          teacherStudentsMap[teacherID] ?= []
          teacherStudentsMap[teacherID].push(studentID)

      # Find paid students
      prepaidUserMap = {}
      for user in data.students
        continue unless studentPaidStatusMap[user._id]
        if prepaidID = user.coursePrepaid?._id
          studentPaidStatusMap[user._id] = 'paid'
          prepaidUserMap[prepaidID] ?= []
          prepaidUserMap[prepaidID].push(user._id)

      # Find trial students
      for prepaid in data.prepaids
        continue unless prepaidUserMap[prepaid._id]
        if prepaid.properties?.trialRequestID
          for userID in prepaidUserMap[prepaid._id]
            studentPaidStatusMap[userID] = 'trial'

      # Find teacher furthest course and paid status based on their students
      # Paid teacher: at least one paid student
      # Trial teacher: at least one trial student in course instance, and no paid students
      # Free teacher: no paid students, no trial students
      # Teacher furthest course is furthest course of highest paid status student
      teacherFurthestCourseMap = {}
      teacherPaidStatusMap = {}
      for teacher, students of teacherStudentsMap
        for student in students
          unless studentFurthestCourseMap[student]?
            console.error "ERROR: no student furthest map for teacher=#{teacher} student=#{student}"
            continue
          if not teacherPaidStatusMap[teacher]
            teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
            teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
          else if teacherPaidStatusMap[teacher] is 'paid'
            if studentPaidStatusMap[student] is 'paid' and teacherFurthestCourseMap[teacher] < studentFurthestCourseMap[student]
              teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
          else if teacherPaidStatusMap[teacher] is 'trial'
            if studentPaidStatusMap[student] is 'paid'
              teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
              teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
            else if studentPaidStatusMap[student] is 'trial' and teacherFurthestCourseMap[teacher] < studentFurthestCourseMap[student]
              teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
          else # free teacher
            if studentPaidStatusMap[student] in ['paid', 'trial']
              teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
              teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
            else if studentPaidStatusMap[student] is 'free' and teacherFurthestCourseMap[teacher] < studentFurthestCourseMap[student]
              teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]

      # Build table of student/teacher paid/trial/free totals
      updateCourseTotalsMap = (courseTotalsMap, furthestCourseMap, paidStatusMap, columnSuffix) =>
        for user, courseIndex of furthestCourseMap
          courseName = @courses.models[courseIndex].get('name')
          courseTotalsMap[courseName] ?= {}
          columnName = switch paidStatusMap[user]
            when 'paid' then 'Paid ' + columnSuffix
            when 'trial' then 'Trial ' + columnSuffix
            when 'free' then 'Free ' + columnSuffix
          courseTotalsMap[courseName][columnName] ?= 0
          courseTotalsMap[courseName][columnName]++
          courseTotalsMap[courseName]['Total ' + columnSuffix] ?= 0
          courseTotalsMap[courseName]['Total ' + columnSuffix]++
          courseTotalsMap['All Courses']['Total ' + columnSuffix] ?= 0
          courseTotalsMap['All Courses']['Total ' + columnSuffix]++
          courseTotalsMap['All Courses'][columnName] ?= 0
          courseTotalsMap['All Courses'][columnName]++
      courseTotalsMap = {'All Courses': {}}
      updateCourseTotalsMap(courseTotalsMap, teacherFurthestCourseMap, teacherPaidStatusMap, 'Teachers')
      updateCourseTotalsMap(courseTotalsMap, studentFurthestCourseMap, studentPaidStatusMap, 'Students')

      courseDistributions = []
      for courseName, totals of courseTotalsMap
        courseDistributions.push({courseName: courseName, totals: totals})
      courseDistributions.sort (a, b) =>
        if a.courseName.indexOf('All Courses') >= 0 and b.courseName.indexOf('All Courses') < 0 then return 1
        else if b.courseName.indexOf('All Courses') >= 0 and a.courseName.indexOf('All Courses') < 0 then return -1
        aID = @courses.findWhere({name: a.courseName}).id
        bID = @courses.findWhere({name: b.courseName}).id
        @courseOrderMap[aID] - @courseOrderMap[bID]

      courseDistributions

    @courseDistributionsRecent = createCourseDistributions(@furthestCourseDayRangeRecent)
    @courseDistributions = createCourseDistributions(@furthestCourseDayRange)

  createLineChartPoints: (days, data) ->
    points = []
    for entry, i in data
      points.push
        day: entry.day
        y: entry.value

    # Trim points preceding days
    if points.length and days.length and points[0].day.localeCompare(days[0]) < 0
      if points[points.length - 1].day.localeCompare(days[0]) < 0
        points = []
      else
        for point, i in points
          if point.day.localeCompare(days[0]) >= 0
            points.splice(0, i)
            break

    # Ensure points for each day
    for day, i in days
      if points.length <= i or points[i]?.day isnt day
        prevY = if i > 0 then points[i - 1].y else 0.0
        points.splice i, 0,
          day: day
          y: prevY
      points[i].x = i

    points.splice(0, points.length - days.length) if points.length > days.length
    points

  createLineCharts: ->
    visibleWidth = $('.kpi-recent-chart').width()
    d3Utils.createLineChart('.kpi-recent-chart', @kpiRecentChartLines, visibleWidth)
    d3Utils.createLineChart('.kpi-chart', @kpiChartLines, visibleWidth)
    d3Utils.createLineChart('.active-classes-chart-90', @activeClassesChartLines90, visibleWidth)
    d3Utils.createLineChart('.active-classes-chart-365', @activeClassesChartLines365, visibleWidth)
    d3Utils.createLineChart('.classroom-daily-active-users-chart-90', @classroomDailyActiveUsersChartLines90, visibleWidth)
    d3Utils.createLineChart('.classroom-monthly-active-users-chart-90', @classroomMonthlyActiveUsersChartLines90, visibleWidth)
    d3Utils.createLineChart('.classroom-daily-active-users-chart-365', @classroomDailyActiveUsersChartLines365, visibleWidth)
    d3Utils.createLineChart('.classroom-monthly-active-users-chart-365', @classroomMonthlyActiveUsersChartLines365, visibleWidth)
    d3Utils.createLineChart('.campaign-daily-active-users-chart-90', @campaignDailyActiveUsersChartLines90, visibleWidth)
    d3Utils.createLineChart('.campaign-monthly-active-users-chart-90', @campaignMonthlyActiveUsersChartLines90, visibleWidth)
    d3Utils.createLineChart('.campaign-daily-active-users-chart-365', @campaignDailyActiveUsersChartLines365, visibleWidth)
    d3Utils.createLineChart('.campaign-monthly-active-users-chart-365', @campaignMonthlyActiveUsersChartLines365, visibleWidth)
    d3Utils.createLineChart('.campaign-vs-classroom-monthly-active-users-recent-chart.line-chart-container', @campaignVsClassroomMonthlyActiveUsersRecentChartLines, visibleWidth)
    d3Utils.createLineChart('.campaign-vs-classroom-monthly-active-users-chart.line-chart-container', @campaignVsClassroomMonthlyActiveUsersChartLines, visibleWidth)
    d3Utils.createLineChart('.paid-courses-chart', @enrollmentsChartLines, visibleWidth)
    d3Utils.createLineChart('.recurring-daily-revenue-chart-90', @revenueDailyChartLines90Days, visibleWidth)
    d3Utils.createLineChart('.recurring-monthly-revenue-chart-90', @revenueMonthlyChartLines90Days, visibleWidth)
    d3Utils.createLineChart('.recurring-daily-revenue-chart-365', @revenueDailyChartLines365Days, visibleWidth)
    d3Utils.createLineChart('.recurring-monthly-revenue-chart-365', @revenueMonthlyChartLines365Days, visibleWidth)

  updateAllKPIChartData: ->
    @kpiRecentChartLines = []
    @kpiChartLines = []
    @updateKPIChartData(60, @kpiRecentChartLines)
    @updateKPIChartData(365, @kpiChartLines)

  updateKPIChartData: (timeframeDays, chartLines) ->
    days = d3Utils.createContiguousDays(timeframeDays)

    # Build active classes KPI line
    if @activeClasses?.length > 0
      data = []
      for entry in @activeClasses
        data.push
          day: entry.day
          value: entry.groups[entry.groups.length - 1]
      data.reverse()
      points = @createLineChartPoints(days, data)
      chartLines.push
        points: points
        description: 'Monthly Active Classes'
        lineColor: 'blue'
        strokeWidth: 1
        min: 0
        max: _.max(points, 'y').y
        showYScale: true

    # Build recurring revenue KPI line
    if @revenue?.length > 0
      data = []
      for entry in @revenue
        data.push
          day: entry.day
          value: entry.groups[entry.groups.length - 1] / 100000
      data.reverse()
      points = @createLineChartPoints(days, data)
      chartLines.push
        points: points
        description: 'Monthly Recurring Revenue (in thousands)'
        lineColor: 'green'
        strokeWidth: 1
        min: 0
        max: _.max(points, 'y').y
        showYScale: true

    # Build campaign MAU KPI line
    if @activeUsers?.length > 0
      eventDayDataMap = {}
      for entry in @activeUsers
        day = entry.day
        for event, count of entry.events
          if event.indexOf('MAU campaign') >= 0
            eventDayDataMap['MAU campaign'] ?= {}
            eventDayDataMap['MAU campaign'][day] ?= 0
            eventDayDataMap['MAU campaign'][day] += count

      campaignData = []
      for event, entry of eventDayDataMap
        for day, count of entry
          campaignData.push day: day, value: count / 1000
      campaignData.reverse()

      points = @createLineChartPoints(days, campaignData)
      chartLines.push
        points: points
        description: 'Campaign Monthly Active Users (in thousands)'
        lineColor: 'purple'
        strokeWidth: 1
        min: 0
        max: _.max(points, 'y').y
        showYScale: true

  updateActiveClassesChartData: ->
    @activeClassesChartLines90 = []
    @activeClassesChartLines365 = []
    return unless @activeClasses?.length

    groupDayMap = {}
    for entry in @activeClasses
      for count, i in entry.groups
        groupDayMap[@activeClassGroups[i]] ?= {}
        groupDayMap[@activeClassGroups[i]][entry.day] ?= 0
        groupDayMap[@activeClassGroups[i]][entry.day] += count

    createActiveClassesChartLines = (lines, numDays) =>
      days = d3Utils.createContiguousDays(numDays)
      colorIndex = 0
      totalMax = 0
      for group, entries of groupDayMap
        data = []
        for day, count of entries
          data.push
            day: day
            value: count
        data.reverse()
        points = @createLineChartPoints(days, data)
        lines.push
          points: points
          description: group.replace('Active classes ', '')
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: group is 'Total'
        totalMax = _.max(points, 'y').y if group is 'Total'
      line.max = totalMax for line in lines

    createActiveClassesChartLines(@activeClassesChartLines90, 90)
    createActiveClassesChartLines(@activeClassesChartLines365, 365)

  updateActiveUsersChartData: ->
    # Create chart lines for the active user events returned by active_users in analytics_perday_handler
    @campaignDailyActiveUsersChartLines90 = []
    @campaignMonthlyActiveUsersChartLines90 = []
    @campaignDailyActiveUsersChartLines365 = []
    @campaignMonthlyActiveUsersChartLines365 = []
    @classroomDailyActiveUsersChartLines90 = []
    @classroomMonthlyActiveUsersChartLines90 = []
    @classroomDailyActiveUsersChartLines365 = []
    @classroomMonthlyActiveUsersChartLines365 = []
    return unless @activeUsers?.length

    # Separate day/value arrays by event
    eventDataMap = {}
    for entry in @activeUsers
      day = entry.day
      for event, count of entry.events
        eventDataMap[event] ?= []
        eventDataMap[event].push
          day: entry.day
          value: count

    createActiveUsersChartLines = (lines, numDays, eventPrefix) =>
      days = d3Utils.createContiguousDays(numDays)
      colorIndex = 0
      lineMax = 0
      showYScale = true
      for event, data of eventDataMap
        continue unless event.indexOf(eventPrefix) >= 0
        points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
        lineMax = Math.max(_.max(points, 'y').y, lineMax)
        lines.push
          points: points
          description: event
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: showYScale
        showYScale = false
      line.max = lineMax for line in lines

    createActiveUsersChartLines(@campaignDailyActiveUsersChartLines90, 90, 'DAU campaign')
    createActiveUsersChartLines(@campaignMonthlyActiveUsersChartLines90, 90, 'MAU campaign')
    createActiveUsersChartLines(@classroomDailyActiveUsersChartLines90, 90, 'DAU classroom')
    createActiveUsersChartLines(@classroomMonthlyActiveUsersChartLines90, 90, 'MAU classroom')
    createActiveUsersChartLines(@campaignDailyActiveUsersChartLines365, 365, 'DAU campaign')
    createActiveUsersChartLines(@campaignMonthlyActiveUsersChartLines365, 365, 'MAU campaign')
    createActiveUsersChartLines(@classroomDailyActiveUsersChartLines365, 365, 'DAU classroom')
    createActiveUsersChartLines(@classroomMonthlyActiveUsersChartLines365, 365, 'MAU classroom')

  updateCampaignVsClassroomActiveUsersChartData: ->
    @campaignVsClassroomMonthlyActiveUsersRecentChartLines = []
    @campaignVsClassroomMonthlyActiveUsersChartLines = []
    return unless @activeUsers?.length

    # Separate day/value arrays by event
    eventDataMap = {}
    for entry in @activeUsers
      day = entry.day
      for event, count of entry.events
        eventDataMap[event] ?= []
        eventDataMap[event].push
          day: entry.day
          value: count

    days = d3Utils.createContiguousDays(90)
    colorIndex = 0
    max = 0
    for event, data of eventDataMap
      if event is 'MAU campaign paid'
        points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
        max = Math.max(max, _.max(points, 'y').y)
        @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push
          points: points
          description: event
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: true
      else if event is 'MAU classroom paid'
        points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
        max = Math.max(max, _.max(points, 'y').y)
        @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push
          points: points
          description: event
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: false

    for line in @campaignVsClassroomMonthlyActiveUsersRecentChartLines
      line.max = max

    days = d3Utils.createContiguousDays(365)
    colorIndex = 0
    max = 0
    for event, data of eventDataMap
      if event is 'MAU campaign paid'
        points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
        max = Math.max(max, _.max(points, 'y').y)
        @campaignVsClassroomMonthlyActiveUsersChartLines.push
          points: points
          description: event
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: true
      else if event is 'MAU classroom paid'
        points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
        max = Math.max(max, _.max(points, 'y').y)
        @campaignVsClassroomMonthlyActiveUsersChartLines.push
          points: points
          description: event
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          showYScale: false

    for line in @campaignVsClassroomMonthlyActiveUsersChartLines
      line.max = max

  updateEnrollmentsChartData: ->
    @enrollmentsChartLines = []
    return unless @paidCourseTotalEnrollments?.length and @trialCourseTotalEnrollments?.length
    days = d3Utils.createContiguousDays(90, false)
    @enrollmentDays = _.cloneDeep(days)
    @enrollmentDays.reverse()

    colorIndex = 0
    dailyMax = 0

    data = []
    for entry in @paidCourseTotalEnrollments
      data.push
        day: entry.day
        value: entry.count
    points = @createLineChartPoints(days, data)
    @enrollmentsChartLines.push
      points: points
      description: 'Paid enrollments issued'
      lineColor: @lineColors[colorIndex++ % @lineColors.length]
      strokeWidth: 1
      min: 0
      max: _.max(points, 'y').y
      showYScale: true
    dailyMax = _.max([dailyMax, _.max(points, 'y').y])

    data = []
    for entry in @paidCourseRedeemedEnrollments
      data.push
        day: entry.day
        value: entry.count
    points = @createLineChartPoints(days, data)
    @enrollmentsChartLines.push
      points: points
      description: 'Paid enrollments redeemed'
      lineColor: @lineColors[colorIndex++ % @lineColors.length]
      strokeWidth: 1
      min: 0
      max: _.max(points, 'y').y
      showYScale: false
    dailyMax = _.max([dailyMax, _.max(points, 'y').y])

    data = []
    for entry in @trialCourseTotalEnrollments
      data.push
        day: entry.day
        value: entry.count
    points = @createLineChartPoints(days, data, true)
    @enrollmentsChartLines.push
      points: points
      description: 'Trial enrollments issued'
      lineColor: @lineColors[colorIndex++ % @lineColors.length]
      strokeWidth: 1
      min: 0
      max: _.max(points, 'y').y
      showYScale: false
    dailyMax = _.max([dailyMax, _.max(points, 'y').y])

    data = []
    for entry in @trialCourseRedeemedEnrollments
      data.push
        day: entry.day
        value: entry.count
    points = @createLineChartPoints(days, data)
    @enrollmentsChartLines.push
      points: points
      description: 'Trial enrollments redeemed'
      lineColor: @lineColors[colorIndex++ % @lineColors.length]
      strokeWidth: 1
      min: 0
      max: _.max(points, 'y').y
      showYScale: false
    dailyMax = _.max([dailyMax, _.max(points, 'y').y])

    line.max = dailyMax for line in @enrollmentsChartLines

  updateRevenueChartData: ->
    @revenueDailyChartLines90Days = []
    @revenueMonthlyChartLines90Days = []
    @revenueDailyChartLines365Days = []
    @revenueMonthlyChartLines365Days = []
    return unless @revenue?.length

    groupDayMap = {}
    for entry in @revenue
      for count, i in entry.groups
        groupDayMap[@revenueGroups[i]] ?= {}
        groupDayMap[@revenueGroups[i]][entry.day] ?= 0
        groupDayMap[@revenueGroups[i]][entry.day] += count

    addRevenueChartLine = (days, eventPrefix, lines) =>
      colorIndex = 0
      dailyMax = 0
      for group, entries of groupDayMap
        continue unless group.indexOf(eventPrefix) >= 0
        data = []
        for day, count of entries
          data.push
            day: day
            value: count / 100
        data.reverse()
        points = @createLineChartPoints(days, data)
        lines.push
          points: points
          description: group.replace(eventPrefix + ' ', 'Daily ')
          lineColor: @lineColors[colorIndex++ % @lineColors.length]
          strokeWidth: 1
          min: 0
          max: _.max(points, 'y').y
          showYScale: group is eventPrefix + ' Total'
        dailyMax = _.max(points, 'y').y if group is eventPrefix + ' Total'
        for line in lines
          line.max = dailyMax

    addRevenueChartLine(d3Utils.createContiguousDays(90), 'DRR', @revenueDailyChartLines90Days)
    addRevenueChartLine(d3Utils.createContiguousDays(90), 'MRR', @revenueMonthlyChartLines90Days)
    addRevenueChartLine(d3Utils.createContiguousDays(365), 'DRR', @revenueDailyChartLines365Days)
    addRevenueChartLine(d3Utils.createContiguousDays(365), 'MRR', @revenueMonthlyChartLines365Days)
