 
   class Player
 
+    # Dependencies + Properties
+    # -------------------------
+
     constructor: (origin, game, gui) ->
       @sprite = game.add.sprite origin.x, origin.y, 'player', 17
       @sprite.anchor = new Phaser.Point 0.5, 0.5

      kVelocity = (1 - @jumpVelocityFactor) + @jumpVelocityFactor * ratio
      @acceleration.y = @jumpAcceleration * kVelocity

    _run: (direction) ->
      # Chain a start animation to the landing.
      landed = => @animation?.name is 'land' and @animation.isFinished
      isStill = @state is 'still' and @velocity.x is 0
      if @direction is direction and (isStill or landed())
        @nextAction = 'start'

      # Clamp velocity.
      @velocity.clampX -@maxVelocity.x, @maxVelocity.x

      if @isInMidAir()
        # No force, just air friction.
        @acceleration.x = @runAcceleration * @airFrictionRatio * -direction
        return

      else if isStill or landed()
        @nextState = 'running'

      # Otherwise, just step on the pedal.
      @acceleration.x = @runAcceleration * direction

    _turn: (direction) ->
      if @_isTurning and @animation?.isFinished
        @nextAction = 'start'
        @_isTurning = no
        @debug 'turn:end', @velocity.x
        # Visualize the turn.
        @sprite.scale.x = direction
        @physics.offset = new Phaser.Point @_xOffset(direction), @_yOffset

      else if !@_isTurning and direction isnt @direction
        @nextAction = 'stop'
        @_isTurning = yes
        @direction = direction
        @debug 'turn:start', @velocity.x

    _updateAnimations: ->
      unless @isInMidAir() or @nextAction is 'none'
        @_changeAnimation @nextAction, @animation?.loop
        return

      switch @nextState
        when 'running' then @_changeAnimation 'run', no
        when 'still' then @_changeAnimation 17, @animation?.loop
        when 'falling' then @_changeAnimation 31, no
        when 'landing' then @_changeAnimation 'land'

    _updateBaseMovement: ->
      @nextAction = 'none'

      if @state is 'falling' and @physics.touching.down
        @nextState = 'landing'
      else unless @isInMidAir()
        @nextState = 'running'

    _updateXMovement: ->
      ###
      Basically handle switching direction, and running or being still
      when not in the air. Note the player still runs in midair, but run
      will behave differently.
      ###
      # Revert to still. (Our acceleration updates funny.)
      @acceleration.x = 0 unless @isInMidAir()

      if @cursors.left.isDown
        @_turn Direction.Left
        @_run Direction.Left

      else if @cursors.right.isDown
        @_turn Direction.Right
        @_run Direction.Right

      else unless @isInMidAir()
        # Without user input, there's no force, so stop, then stay still.
        if @velocity.x isnt 0 then @nextAction = 'stop'
        else
          @nextState = 'still'
          @_isTurning = no # In case of being blocked.

    _updateYMovement: ->
      ###
      Basically handle starting and ending of jump, and starting of falling.
      The possibility of hitting the ceiling during jump is a complexity.
      ###

      if @cursors.up.isDown and not @isInMidAir() and not @_jumpTimer.running
        @_jump()

      else if @_jumpTimer.running and (@cursors.up.isUp or @_jumpTimer.ms >= @jumpMaxDuration) # Release to cancel early.
        @_jump yes

      else if @state is 'rising' and @velocity.y > 0
        @nextState = 'falling'
        @debug 'jump:peak', @physics.y

      # Speed up by persisting the jump acceleration but with quadratic decay.
      # The longer the hold, the higher the final jump, but power decays quickly.
      # Also note that negative is up, positive is down.
      if @cursors.up.isDown and @_jumpTimer.running
        kEasing = (1000 - @jumpMaxDuration) + (@jumpMaxDuration - @_jumpTimer.ms)
        kEasing = Math.pow kEasing / 1000, 2
        @acceleration.y *= kEasing
        @debug 'jump:build', kEasing

    _xOffset: (direction = @direction) -> direction * 10

  Player

      @_jumpTimer = @sprite.game.time.create() # This also acts like a flag.
      @_isTurning = no # Because velocity won't be 0 when turning while running.

    # Change
    # ------

    _changeAnimation: ->
      unless @_isInMidAir() or @nextAction is 'none'
        @_playAnimation @nextAction, @animation?.loop
        return

      switch @nextState
        when 'running' then @_playAnimation 'run', no
        when 'still' then @_playAnimation 17, @animation?.loop
        when 'falling' then @_playAnimation 31, no
        when 'landing' then @_playAnimation 'land'

    _changeState: ->
      return if @nextState is @state

      @debug 'jump:peak', @physics.y if @state is 'rising'

      @debug 'state', @nextState
      @debug 'jump:start', @physics.position if @nextState is 'rising'

      @state = @nextState

    # Computed
    # --------

    _isInMidAir: -> @state is 'rising' or @state is 'falling'
    _isLanded: -> @animation?.name is 'land' and @animation.isFinished
    _isTrulyStill: -> @state is 'still' and @velocity.x is 0

    _xDirectionInput: ->
      if @cursors.left.isDown then Direction.Left
      else if @cursors.right.isDown then Direction.Right

    _xOffset: (direction = @direction) -> direction * 10

    # Jump
    # ----

    _canBeginJump: ->
      @cursors.up.isDown and not (@_jumpTimer.running or @_isInMidAir())
    _canBuildJump: ->
      @cursors.up.isDown and @_jumpTimer.running
    _canEndJump: ->
      @nextAction isnt 'jump' and @_jumpTimer.running and
      (@cursors.up.isUp or @_jumpTimer.ms >= @jumpMaxDuration) # Release to cancel early.

    _canFall: ->
      not @_jumpTimer.running and @state is 'rising' and @velocity.y > 0
    _canLand: ->
      @state is 'falling' and @physics.touching.down

    _beginJump: ->
      @_jumpTimer.start()

      @nextAction = 'jump'
      @nextState = 'rising'
