ModalView = require 'views/core/ModalView'
template = require 'templates/play/modal/play-heroes-modal'
buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt'
CocoCollection = require 'collections/CocoCollection'
ThangType = require 'models/ThangType'
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
AudioPlayer = require 'lib/AudioPlayer'
utils = require 'core/utils'
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
CreateAccountModal = require 'views/core/CreateAccountModal'
Purchase = require 'models/Purchase'
LayerAdapter = require 'lib/surface/LayerAdapter'
Lank = require 'lib/surface/Lank'

module.exports = class PlayHeroesModal extends ModalView
  className: 'modal fade play-modal'
  template: template
  id: 'play-heroes-modal'

  events:
    'slide.bs.carousel #hero-carousel': 'onHeroChanged'
    'change #option-code-language': 'onCodeLanguageChanged'
    'click #close-modal': 'hide'
    'click #confirm-button': 'saveAndHide'
    'click .unlock-button': 'onUnlockButtonClicked'
    'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked'
    'click': 'onClickedSomewhere'

  shortcuts:
    'left': -> @$el.find('#hero-carousel').carousel('prev') if @heroes.models.length and not @$el.hasClass 'secret'
    'right': -> @$el.find('#hero-carousel').carousel('next') if @heroes.models.length and not @$el.hasClass 'secret'
    'enter': -> @saveAndHide() if @visibleHero and not @visibleHero.locked

  constructor: (options) ->
    super options
    options ?= {}
    @confirmButtonI18N = options.confirmButtonI18N ? "common.save"
    @heroes = new CocoCollection([], {model: ThangType})
    @heroes.url = '/db/thang.type?view=heroes'
    @heroes.setProjection ['original','name','slug','soundTriggers','featureImages','gems','heroClass','description','components','extendedName','unlockLevelName','i18n']
    @heroes.comparator = 'gems'
    @listenToOnce @heroes, 'sync', @onHeroesLoaded
    @supermodel.loadCollection(@heroes, 'heroes')
    @stages = {}
    @layers = []
    @session = options.session
    @initCodeLanguageList options.hadEverChosenHero
    @heroAnimationInterval = setInterval @animateHeroes, 1000

  onHeroesLoaded: ->
    @formatHero hero for hero in @heroes.models

  formatHero: (hero) ->
    hero.name = utils.i18n hero.attributes, 'extendedName'
    hero.name ?= utils.i18n hero.attributes, 'name'
    hero.description = utils.i18n hero.attributes, 'description'
    hero.unlockLevelName = utils.i18n hero.attributes, 'unlockLevelName'
    original = hero.get('original')
    hero.locked = not me.ownsHero(original)
    hero.purchasable = hero.locked and (original in (me.get('earned')?.heroes ? []))
    if @options.level and allowedHeroes = @options.level.get 'allowedHeroes'
      hero.restricted = not (hero.get('original') in allowedHeroes)
    hero.class = (hero.get('heroClass') or 'warrior').toLowerCase()
    hero.stats = hero.getHeroStats()

  getRenderData: (context={}) ->
    context = super(context)
    context.heroes = @heroes.models
    context.level = @options.level
    context.codeLanguages = @codeLanguageList
    context.codeLanguage = @codeLanguage = @options?.session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
    context.confirmButtonI18N = @confirmButtonI18N
    context.visibleHero = @visibleHero
    context.gems = me.gems()
    context.isIE = @isIE()
    context

  afterRender: ->
    super()
    return unless @supermodel.finished()
    @playSound 'game-menu-open'
    @$el.find('.hero-avatar').addClass 'ie' if @isIE()
    heroes = @heroes.models
    @$el.find('.hero-indicator').each ->
      heroID = $(@).data('hero-id')
      hero = _.find heroes, (hero) -> hero.get('original') is heroID
      $(@).find('.hero-avatar').css('background-image', "url(#{hero.getPortraitURL()})").addClass('has-tooltip').tooltip()
    @canvasWidth = 313  # @$el.find('canvas').width() # unreliable, whatever
    @canvasHeight = @$el.find('canvas').height()
    heroConfig = @options?.session?.get('heroConfig') ? me.get('heroConfig') ? {}
    heroIndex = Math.max 0, _.findIndex(heroes, ((hero) -> hero.get('original') is heroConfig.thangType))
    @$el.find(".hero-item:nth-child(#{heroIndex + 1}), .hero-indicator:nth-child(#{heroIndex + 1})").addClass('active')
    @onHeroChanged direction: null, relatedTarget: @$el.find('.hero-item')[heroIndex]
    @$el.find('.hero-stat').addClass('has-tooltip').tooltip()
    @buildCodeLanguages()

  rerenderFooter: ->
    @formatHero @visibleHero
    @renderSelectors '#hero-footer'
    @buildCodeLanguages()
    @$el.find('#gems-count-container').toggle Boolean @visibleHero.purchasable

  initCodeLanguageList: (hadEverChosenHero) ->
    if application.isIPadApp
      @codeLanguageList = [
        {id: 'python', name: "Python (#{$.i18n.t('choose_hero.default')})"}
        {id: 'javascript', name: 'JavaScript'}
      ]
    else
      @codeLanguageList = switch me.getDefaultLanguageGroup()
        when 'javascript'
          [
            {id: 'javascript', name: "JavaScript (#{$.i18n.t('choose_hero.default')})"}
            {id: 'python', name: "Python"}
            {id: 'coffeescript', name: "CoffeeScript (#{$.i18n.t('choose_hero.experimental')})"}
            {id: 'lua', name: 'Lua'}
          ]
        else
          [
            {id: 'python', name: "Python (#{$.i18n.t('choose_hero.default')})"}
            {id: 'javascript', name: 'JavaScript'}
            {id: 'coffeescript', name: "CoffeeScript (#{$.i18n.t('choose_hero.experimental')})"}
            {id: 'lua', name: 'Lua'}
          ]

      if me.isAdmin() or not application.isProduction()
        @codeLanguageList.push {id: 'java', name: "Java (#{$.i18n.t('choose_hero.experimental')})"}

  onHeroChanged: (e) ->
    direction = e.direction  # 'left' or 'right'
    heroItem = $(e.relatedTarget)
    hero = _.find @heroes.models, (hero) -> hero.get('original') is heroItem.data('hero-id')
    return console.error "Couldn't find hero from heroItem:", heroItem unless hero
    heroIndex = heroItem.index()
    hero = @loadHero hero, heroIndex
    @preloadHero heroIndex + 1
    @preloadHero heroIndex - 1
    @selectedHero = hero unless hero.locked
    @visibleHero = hero
    @rerenderFooter()
    @trigger 'hero-loaded', {hero: hero}

  getFullHero: (original) ->
    url = "/db/thang.type/#{original}/version"
    if fullHero = @supermodel.getModel url
      return fullHero
    fullHero = new ThangType()
    fullHero.setURL url
    fullHero = (@supermodel.loadModel fullHero).model
    fullHero

  preloadHero: (heroIndex) ->
    return unless hero = @heroes.models[heroIndex]
    @loadHero hero, heroIndex, true

  loadHero: (hero, heroIndex, preloading=false) ->
    createjs.Ticker.removeEventListener 'tick', stage for stage in _.values @stages
    createjs.Ticker.setFPS 30  # In case we paused it from being inactive somewhere else
    if stage = @stages[heroIndex]
      unless preloading
        _.defer -> createjs.Ticker.addEventListener 'tick', stage  # Deferred, otherwise it won't start updating for some reason.
        @playSelectionSound hero
      return hero
    fullHero = @getFullHero hero.get 'original'
    onLoaded = =>
      canvas = $(".hero-item[data-hero-id='#{fullHero.get('original')}'] canvas")
      return unless canvas.length  # Don't render it if it's not on the screen.
      unless fullHero.get 'raw'
        console.error "Couldn't make animation for #{fullHero.get('name')} with attributes #{_.cloneDeep(fullHero.attributes)}. Was it loaded with an improper projection or something?", fullHero
        @rerenderFooter()
        return
      canvas.show().prop width: @canvasWidth, height: @canvasHeight

      layer = new LayerAdapter({webGL:true})
      @layers.push layer
      layer.resolutionFactor = 8 # hi res!
      layer.buildAsync = false
      multiplier = 7
      layer.scaleX = layer.scaleY = multiplier
      lank = new Lank(fullHero, {preloadSounds: false})

      layer.addLank(lank)
      layer.on 'new-spritesheet', ->
        #- maybe put some more normalization here?
        m = multiplier
        m *= 0.75 if fullHero.get('slug') in ['knight', 'samurai', 'librarian', 'sorcerer', 'necromancer']  # These heroes are larger for some reason. Shrink 'em.
        m *= 0.4 if fullHero.get('slug') is 'goliath'  # Just too big!
        m *= 0.9 if fullHero.get('slug') is 'champion'  # Gotta fit her hair in there
        layer.container.scaleX = layer.container.scaleY = m
        layer.container.children[0].x = 160/m
        layer.container.children[0].y = 250/m
        if fullHero.get('slug') in ['forest-archer', 'librarian', 'sorcerer', 'potion-master', 'necromancer']
          layer.container.children[0].y -= 3
        if fullHero.get('slug') in ['librarian', 'sorcerer', 'potion-master', 'necromancer', 'goliath']
          layer.container.children[0].x -= 3

      stage = new createjs.SpriteStage(canvas[0])
      @stages[heroIndex] = stage
      stage.addChild layer.container
      stage.update()
      unless preloading
        createjs.Ticker.addEventListener 'tick', stage
        @playSelectionSound hero
      @rerenderFooter()
    if fullHero.loaded
      _.defer onLoaded
    else
      @listenToOnce fullHero, 'sync', onLoaded
    fullHero

  animateHeroes: =>
    return unless @visibleHero
    heroIndex = Math.max 0, _.findIndex(@heroes.models, ((hero) => hero.get('original') is @visibleHero.get('original')))
    animation = _.sample(['attack', 'move_side', 'move_fore'])  # Must be in LayerAdapter default actions.
    @stages[heroIndex]?.children?[0]?.children?[0]?.gotoAndPlay? animation

  playSelectionSound: (hero) ->
    return if @$el.hasClass 'secret'
    @currentSoundInstance?.stop()
    return unless sounds = hero.get('soundTriggers')?.selected
    return unless sound = sounds[Math.floor Math.random() * sounds.length]
    name = AudioPlayer.nameForSoundReference sound
    AudioPlayer.preloadSoundReference sound
    @currentSoundInstance = AudioPlayer.playSound name, 1
    @currentSoundInstance

  buildCodeLanguages: ->
    $select = @$el.find('#option-code-language')
    $select.fancySelect().parent().find('.options li').each ->
      languageName = $(@).text()
      languageID = $(@).data('value')
      blurb = $.i18n.t("choose_hero.#{languageID}_blurb")
      $(@).text("#{languageName} - #{blurb}")

  onCodeLanguageChanged: (e) ->
    @codeLanguage = @$el.find('#option-code-language').val()
    @codeLanguageChanged = true
    window.tracker?.trackEvent 'Campaign changed code language', category: 'Campaign Hero Select', codeLanguage: @codeLanguage, levelSlug: @options.level?.get('slug')

  #- Purchasing the hero

  onUnlockButtonClicked: (e) ->
    e.stopPropagation()
    button = $(e.target).closest('button')
    affordable = @visibleHero.get('gems') <= me.gems()
    if not affordable
      @playSound 'menu-button-click'
      @askToBuyGems button unless me.isOnFreeOnlyServer()
    else if button.hasClass('confirm')
      @playSound 'menu-button-unlock-end'
      purchase = Purchase.makeFor(@visibleHero)
      purchase.save()

      #- set local changes to mimic what should happen on the server...
      purchased = me.get('purchased') ? {}
      purchased.heroes ?= []
      purchased.heroes.push(@visibleHero.get('original'))
      me.set('purchased', purchased)
      me.set('spent', (me.get('spent') ? 0) + @visibleHero.get('gems'))

      #- ...then rerender visible hero
      heroEntry = @$el.find(".hero-item[data-hero-id='#{@visibleHero.get('original')}']")
      heroEntry.find('.hero-status-value').attr('data-i18n', 'play.available').i18n()
      heroEntry.removeClass 'locked purchasable'
      @selectedHero = @visibleHero
      @rerenderFooter()

      Backbone.Mediator.publish 'store:hero-purchased', hero: @visibleHero, heroSlug: @visibleHero.get('slug')
    else
      @playSound 'menu-button-unlock-start'
      button.addClass('confirm').text($.i18n.t('play.confirm'))
      @$el.one 'click', (e) ->
        button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]

  askToSignUp: ->
    createAccountModal = new CreateAccountModal supermodel: @supermodel
    return @openModalView createAccountModal

  askToBuyGems: (unlockButton) ->
    @$el.find('.unlock-button').popover 'destroy'
    popoverTemplate = buyGemsPromptTemplate {}
    unlockButton.popover(
      animation: true
      trigger: 'manual'
      placement: 'left'
      content: ' '  # template has it
      container: @$el
      template: popoverTemplate
    ).popover 'show'
    popover = unlockButton.data('bs.popover')
    popover?.$tip?.i18n()

  onBuyGemsPromptButtonClicked: (e) ->
    return @askToSignUp() if me.get('anonymous')
    @openModalView new BuyGemsModal()

  onClickedSomewhere: (e) ->
    return if @destroyed
    @$el.find('.unlock-button').popover 'destroy'


  #- Exiting

  saveAndHide: ->
    hero = @selectedHero?.get('original')
    hero ?= @visibleHero?.get('original') if @visibleHero?.loaded and not @visibleHero.locked
    unless hero
      console.error 'Somehow we tried to hide without having a hero selected yet...'
      noty {
        text: "Error: hero not loaded. If this keeps happening, please report the bug."
        layout: 'topCenter'
        timeout: 10000
        type: 'error'
      }
      return

    if @session
      changed = @updateHeroConfig(@session, hero)
      if @session.get('codeLanguage') isnt @codeLanguage
        @session.set('codeLanguage', @codeLanguage)
        changed = true
        #Backbone.Mediator.publish 'tome:change-language', language: @codeLanguage, reload: true  # We'll reload the PlayLevelView instead.

      @session.patch() if changed

    changed = @updateHeroConfig(me, hero)
    aceConfig = _.clone(me.get('aceConfig')) or {}
    if @codeLanguage isnt aceConfig.language
      aceConfig.language = @codeLanguage
      me.set 'aceConfig', aceConfig
      changed = true

    me.patch() if changed

    @hide()
    @trigger?('confirm-click', hero: @selectedHero)

  updateHeroConfig: (model, hero) ->
    return false unless hero
    heroConfig = _.clone(model.get('heroConfig')) or {}
    if heroConfig.thangType isnt hero
      heroConfig.thangType = hero
      model.set('heroConfig', heroConfig)
      return true

  onHidden: ->
    super()
    @playSound 'game-menu-close'

  destroy: ->
    clearInterval @heroAnimationInterval
    for heroIndex, stage of @stages
      createjs.Ticker.removeEventListener "tick", stage
      stage.removeAllChildren()
    layer.destroy() for layer in @layers
    super()
