{hexToHSL, hslToHex} = require 'core/utils'

module.exports = class SpriteBuilder
  constructor: (@thangType, @options) ->
    @options ?= {}
    raw = @thangType.get('raw')
    @shapeStore = raw.shapes
    @containerStore = raw.containers
    @animationStore = raw.animations
    @buildColorMaps()

  setOptions: (@options) ->

  buildMovieClip: (animationName, mode, startPosition, loops, labels) ->
    animData = @animationStore[animationName]
    unless animData
      console.error 'couldn\'t find animData from', @animationStore, 'for', animationName
      return null
    locals = {}
    _.extend locals, @buildMovieClipShapes(animData.shapes)
    _.extend locals, @buildMovieClipContainers(animData.containers)
    _.extend locals, @buildMovieClipAnimations(animData.animations)
    _.extend locals, @buildMovieClipGraphics(animData.graphics)
    anim = new createjs.MovieClip()
    if not labels
      labels = {}
      labels[animationName] = 0
    anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true, labels)
    for tweenData in animData.tweens
      tween = createjs.Tween
      stopped = false
      for func in tweenData
        args = _.cloneDeep(func.a)
        @dereferenceArgs(args, locals)
        if tween[func.n]
          tween = tween[func.n](args...)
        else
          # If we, say, skipped a shadow get(), then the wait() may not be present
          stopped = true
          break
      anim.timeline.addTween(tween) unless stopped

    anim.nominalBounds = new createjs.Rectangle(animData.bounds...)
    if animData.frameBounds
      anim.frameBounds = (new createjs.Rectangle(bounds...) for bounds in animData.frameBounds)
    anim

  dereferenceArgs: (args, locals) ->
    for key, val of args
      if locals[val]
        args[key] = locals[val]
      else if val is null
        args[key] = {}
      else if _.isString(val) and val.indexOf('createjs.') is 0
        args[key] = eval(val) # TODO: Security risk
      else if _.isObject(val) or _.isArray(val)
        @dereferenceArgs(val, locals)
    args

  buildMovieClipShapes: (localShapes) ->
    map = {}
    for localShape in localShapes
      if localShape.im
        shape = new createjs.Shape()
        shape._off = true
      else
        shape = @buildShapeFromStore(localShape.gn)
        if localShape.m
          shape.mask = map[localShape.m]
      map[localShape.bn] = shape
    map

  buildMovieClipContainers: (localContainers) ->
    map = {}
    for localContainer in localContainers
      container = @buildContainerFromStore(localContainer.gn)
      container.setTransform(localContainer.t...)
      container._off = localContainer.o if localContainer.o?
      container.alpha = localContainer.al if localContainer.al?
      map[localContainer.bn] = container
    map

  buildMovieClipAnimations: (localAnimations) ->
    map = {}
    for localAnimation in localAnimations
      animation = @buildMovieClip(localAnimation.gn, localAnimation.a...)
      animation.setTransform(localAnimation.t...)
      map[localAnimation.bn] = animation
    map

  buildMovieClipGraphics: (localGraphics) ->
    map = {}
    for localGraphic in localGraphics
      graphic = new createjs.Graphics().p(localGraphic.p)
      map[localGraphic.bn] = graphic
    map

  buildShapeFromStore: (shapeKey, debug=false) ->
    shapeData = @shapeStore[shapeKey]
    shape = new createjs.Shape()
    if shapeData.lf?
      shape.graphics.lf shapeData.lf...
    else if shapeData.fc?
      shape.graphics.f @colorMap[shapeKey] or shapeData.fc
    else if shapeData.rf?
      shape.graphics.rf shapeData.rf...
    if shapeData.ls?
      shape.graphics.ls shapeData.ls...
    else if shapeData.sc?
      shape.graphics.s shapeData.sc
    shape.graphics.ss shapeData.ss... if shapeData.ss?
    shape.graphics.de shapeData.de... if shapeData.de?
    shape.graphics.p shapeData.p if shapeData.p?
    shape.setTransform shapeData.t...
    shape

  buildContainerFromStore: (containerKey) ->
    console.error 'Yo we don\'t have no containerKey' unless containerKey
    contData = @containerStore[containerKey]
    cont = new createjs.Container()
    cont.initialize()
    for childData in contData.c
      if _.isString(childData)
        child = @buildShapeFromStore(childData)
      else
        continue if not childData.gn
        child = @buildContainerFromStore(childData.gn)
        child.setTransform(childData.t...)
      cont.addChild(child)
    cont.bounds = new createjs.Rectangle(contData.b...)
    cont

  buildColorMaps: ->
    @colorMap = {}
    colorGroups = @thangType.get('colorGroups')
    return if _.isEmpty colorGroups
    return unless _.size @shapeStore  # We don't have the shapes loaded because we are doing a prerendered spritesheet approach
    colorConfig = @options.colorConfig
#    colorConfig ?= {team: {hue:0.4, saturation: -0.5, lightness: -0.5}} # test config
    return if not colorConfig

    for group, config of colorConfig
      continue unless colorGroups[group] # color group not found...
      @buildColorMapForGroup(colorGroups[group], config)

  buildColorMapForGroup: (shapes, config) ->
    return unless shapes.length
    colors = @initColorMap(shapes)
    @adjustHuesForColorMap(colors, config.hue)
    @adjustValueForColorMap(colors, 1, config.saturation)
    @adjustValueForColorMap(colors, 2, config.lightness)
    @applyColorMap(shapes, colors)

  initColorMap: (shapes) ->
    colors = {}
    for shapeKey in shapes
      shape = @shapeStore[shapeKey]
      continue if (not shape.fc?) or colors[shape.fc]
      hsl = hexToHSL(shape.fc)
      colors[shape.fc] = hsl
    colors

  adjustHuesForColorMap: (colors, targetHue) ->
    hues = (hsl[0] for hex, hsl of colors)

    # 'rotate' the hue spectrum so averaging works
    if Math.max(hues) - Math.min(hues) > 0.5
      hues = (if h < 0.5 then h + 1.0 else h for h in hues)
    averageHue = sum(hues) / hues.length
    averageHue %= 1
    # end result should be something like a hue array of [0.9, 0.3] gets an average of 0.1

    targetHue ?= 0
    diff = targetHue - averageHue
    hsl[0] = (hsl[0] + diff + 1) % 1 for hex, hsl of colors

  adjustValueForColorMap: (colors, index, targetValue) ->
    values = (hsl[index] for hex, hsl of colors)
    averageValue = sum(values) / values.length
    targetValue ?= 0.5
    diff = targetValue - averageValue
    for hex, hsl of colors
      hsl[index] = Math.max(0, Math.min(1, hsl[index] + diff))

  applyColorMap: (shapes, colors) ->
    for shapeKey in shapes
      shape = @shapeStore[shapeKey]
      continue if (not shape.fc?) or not(colors[shape.fc])
      @colorMap[shapeKey] = hslToHex(colors[shape.fc])

sum = (nums) -> _.reduce(nums, (s, num) -> s + num)
