RootView = require 'views/core/RootView'
template = require 'templates/play/spectate'
{me} = require 'core/auth'
ThangType = require 'models/ThangType'
utils = require 'core/utils'

World = require 'lib/world/world'

# tools
Surface = require 'lib/surface/Surface'
God = require 'lib/God' # 'lib/Buddha'
GoalManager = require 'lib/world/GoalManager'
ScriptManager = require 'lib/scripts/ScriptManager'
LevelLoader = require 'lib/LevelLoader'
LevelSession = require 'models/LevelSession'
Level = require 'models/Level'
LevelComponent = require 'models/LevelComponent'
Article = require 'models/Article'
Camera = require 'lib/surface/Camera'
AudioPlayer = require 'lib/AudioPlayer'

# subviews
LoadingView = require './level/LevelLoadingView'
TomeView = require './level/tome/TomeView'
ChatView = require './level/LevelChatView'
HUDView = require './level/LevelHUDView'
ControlBarView = require './level/ControlBarView'
PlaybackView = require './level/LevelPlaybackView'
GoalsView = require './level/LevelGoalsView'
GoldView = require './level/LevelGoldView'
DuelStatsView = require './level/DuelStatsView'
VictoryModal = require './level/modal/VictoryModal'
InfiniteLoopModal = require './level/modal/InfiniteLoopModal'

PROFILE_ME = false

module.exports = class SpectateLevelView extends RootView
  id: 'spectate-level-view'
  template: template
  cache: false
  isEditorPreview: false

  subscriptions:
    'level:set-volume': (e) -> createjs.Sound.setVolume(if e.volume is 1 then 0.6 else e.volume)  # Quieter for now until individual sound FX controls work again.
    'god:new-world-created': 'onNewWorld'
    'god:streaming-world-updated': 'onNewWorld'
    'god:infinite-loop': 'onInfiniteLoop'
    'level:next-game-pressed': 'onNextGamePressed'
    'level:started': 'onLevelStarted'
    'level:loading-view-unveiled': 'onLoadingViewUnveiled'

  constructor: (options, @levelID) ->
    console.profile?() if PROFILE_ME
    super options

    @sessionOne = @getQueryVariable 'session-one'
    @sessionTwo = @getQueryVariable 'session-two'
    if options.spectateSessions
      @sessionOne = options.spectateSessions.sessionOne
      @sessionTwo = options.spectateSessions.sessionTwo

    if not @sessionOne or not @sessionTwo
      @fetchRandomSessionPair (err, data) =>
        if err? then return console.log "There was an error fetching the random session pair: #{data}"
        @sessionOne = data[0]._id
        @sessionTwo = data[1]._id
        @load()
    else
      @load()

  setLevel: (@level, @supermodel) ->
    serializedLevel = @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
    @god?.setLevel serializedLevel
    if @world
      @world.loadFromLevel serializedLevel, false
    else
      @load()

  load: ->
    @levelLoader = new LevelLoader
      supermodel: @supermodel
      levelID: @levelID
      sessionID: @sessionOne
      opponentSessionID: @sessionTwo
      spectateMode: true
      team: @getQueryVariable('team')
    @god = new God maxAngels: 1, spectate: true

  getRenderData: ->
    c = super()
    c.world = @world
    c

  afterRender: ->
    window.onPlayLevelViewLoaded? @  # still a hack
    @insertSubView @loadingView = new LoadingView autoUnveil: true, level: @levelLoader?.level ? @level
    @$el.find('#level-done-button').hide()
    super()
    $('body').addClass('is-playing')

  onLoaded: ->
    _.defer => @onLevelLoaderLoaded()

  onLevelLoaderLoaded: ->
    @grabLevelLoaderData()
    #at this point, all requisite data is loaded, and sessions are not denormalized
    team = @world.teamForPlayer(0)
    @loadOpponentTeam(team)
    @god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
    @god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
    @god.setWorldClassMap @world.classMap
    @setTeam team
    @initSurface()
    @initGoalManager()
    @initScriptManager()
    @insertSubviews()
    @initVolume()

    @originalSessionState = $.extend(true, {}, @session.get('state'))
    @register()
    @controlBar.setBus(@bus)
    @surface.showLevel()

  grabLevelLoaderData: ->
    @session = @levelLoader.session
    @world = @levelLoader.world
    @level = @levelLoader.level
    @otherSession = @levelLoader.opponentSession
    @levelLoader.destroy()
    @levelLoader = null

  loadOpponentTeam: (myTeam) ->
    opponentSpells = []
    for spellTeam, spells of @session.get('teamSpells') ? @otherSession?.get('teamSpells') ? {}
      continue if spellTeam is myTeam or not myTeam
      opponentSpells = opponentSpells.concat spells

    opponentCode = @otherSession?.get('code') or {}
    myCode = @session.get('code') or {}
    for spell in opponentSpells
      [thang, spell] = spell.split '/'
      c = opponentCode[thang]?[spell]
      myCode[thang] ?= {}
      if c then myCode[thang][spell] = c else delete myCode[thang][spell]

    @session.set('code', myCode)

  onLevelStarted: (e) ->
    go = =>
      @loadingView?.startUnveiling()
      @loadingView?.unveil true
    _.delay go, 1000

  onLoadingViewUnveiled: (e) ->
    # Don't remove it; we want its decoration around on large screens.
    #@removeSubView @loadingView
    #@loadingView = null
    Backbone.Mediator.publish 'level:set-playing', playing: false
    Backbone.Mediator.publish 'level:set-time', time: 1  # Helps to have perhaps built a few Thangs and gotten a good list of spritesheets we need to render for our initial paused frame

  onSupermodelLoadedOne: =>
    @modelsLoaded ?= 0
    @modelsLoaded += 1
    @updateInitString()

  updateInitString: ->
    return if @surface
    @modelsLoaded ?= 0
    canvas = @$el.find('#surface')[0]
    ctx = canvas.getContext('2d')
    ctx.font='20px Georgia'
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)

  insertSubviews: ->
    @insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage'), level: @level, god: @god
    @insertSubView new PlaybackView session: @session, level: @level

    @insertSubView new GoldView {}
    @insertSubView new HUDView {level: @level}
    @insertSubView new DuelStatsView level: @level, session: @session, otherSession: @otherSession, supermodel: @supermodel, thangs: @world.thangs if @level.isType('hero-ladder', 'course-ladder')
    @insertSubView @controlBar = new ControlBarView {worldName: utils.i18n(@level.attributes, 'name'), session: @session, level: @level, supermodel: @supermodel, spectateGame: true}

  # callbacks

  onInfiniteLoop: (e) ->
    return unless e.firstWorld and e.god is @god
    @openModalView new InfiniteLoopModal()
    window.tracker?.trackEvent 'Saw Initial Infinite Loop', level: @world.name, label: @world.name

  # initialization

  initSurface: ->
    webGLSurface = $('canvas#webgl-surface', @$el)
    normalSurface = $('canvas#normal-surface', @$el)
    @surface = new Surface @world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), spectateGame: true, playerNames: @findPlayerNames(), levelType: @level.get('type', true)
    worldBounds = @world.getBounds()
    bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
    @surface.camera.setBounds(bounds)
    zoom = =>
      @surface.camera.zoomTo({x: (worldBounds.right - worldBounds.left) / 2, y: (worldBounds.top - worldBounds.bottom) / 2}, 0.1, 0)
    _.delay zoom, 4000  # call it later for some reason (TODO: figure this out)

  findPlayerNames: ->
    playerNames = {}
    for session in [@session, @otherSession] when session?.get('team')
      playerNames[session.get('team')] = session.get('creatorName') or 'Anonymous'
    playerNames

  initGoalManager: ->
    @goalManager = new GoalManager(@world, @level.get('goals'))
    @god.setGoalManager @goalManager

  initScriptManager: ->
    if @world.scripts
      nonVictoryPlaybackScripts = _.reject @world.scripts, (script) ->
        script.id.indexOf('Set Camera Boundaries') is -1
    else
      console.log 'World scripts don\'t exist!'
      nonVictoryPlaybackScripts = []
    @scriptManager = new ScriptManager({scripts: nonVictoryPlaybackScripts, view:@, session: @session})
    @scriptManager.loadFromSession()

  initVolume: ->
    volume = me.get('volume')
    volume = 1.0 unless volume?
    Backbone.Mediator.publish 'level:set-volume', volume: volume

  register: -> return

  onSessionWillSave: (e) ->
    # Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
    console.log 'Session is saving but shouldn\'t save!!!!!!!'

  # Throttled
  saveScreenshot: (session) =>
    return unless screenshot = @surface?.screenshot()
    session.save {screenshot: screenshot}, {patch: true, type: 'PUT'}

  setTeam: (team) ->
    team = team?.team unless _.isString team
    team ?= 'humans'
    me.team = team
    Backbone.Mediator.publish 'level:team-set', team: team

  # Dynamic sound loading

  onNewWorld: (e) ->
    return if @headless
    scripts = @world.scripts  # Since these worlds don't have scripts, preserve them.
    @world = e.world
    @world.scripts = scripts
    thangTypes = @supermodel.getModels(ThangType)
    startFrame = @lastWorldFramesLoaded ? 0
    if @world.frames.length is @world.totalFrames  # Finished loading
      @lastWorldFramesLoaded = 0
      unless @getQueryVariable('autoplay') is false
        Backbone.Mediator.publish 'level:set-playing', playing: true  # Since we paused at first, now we autostart playback.
    else
      @lastWorldFramesLoaded = @world.frames.length
    for [spriteName, message] in @world.thangDialogueSounds startFrame
      continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
      continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
      AudioPlayer.preloadSoundReference sound

  onNextGamePressed: (e) ->
    @fetchRandomSessionPair (err, data) =>
      return if @destroyed
      if err? then return console.log "There was an error fetching the random session pair: #{data}"
      @sessionOne = data[0]._id
      @sessionTwo = data[1]._id
      url = "/play/spectate/#{@levelID}?session-one=#{@sessionOne}&session-two=#{@sessionTwo}"
      if leagueID = @getQueryVariable 'league'
        url += "&league=" + leagueID
      Backbone.Mediator.publish 'router:navigate', {
        route: url,
        viewClass: SpectateLevelView,
        viewArgs: [
          {
            spectateSessions: {sessionOne: @sessionOne, sessionTwo: @sessionTwo}
            supermodel: @supermodel
          }
          @levelID
        ]
      }
      history?.pushState? {}, '', url  # Backbone won't update the URL if just query parameters change

  fetchRandomSessionPair: (cb) ->
    console.log 'Fetching random session pair!'
    randomSessionPairURL = "/db/level/#{@levelID}/random_session_pair"
    $.ajax
      url: randomSessionPairURL
      type: 'GET'
      cache: false
      complete: (jqxhr, textStatus) ->
        if textStatus isnt 'success'
          cb('error', jqxhr.statusText)
        else
          cb(null, $.parseJSON(jqxhr.responseText))

  destroy: ()->
    @levelLoader?.destroy()
    @surface?.destroy()
    @god?.destroy()
    @goalManager?.destroy()
    @scriptManager?.destroy()
    delete window.world # not sure where this is set, but this is one way to clean it up
    console.profileEnd?() if PROFILE_ME
    super()
