module SpriteFactory
  module Layout
    module Packed

      #------------------------------------------------------------------------

      def self.layout(images, options = {})

        raise NotImplementedError, ":packed layout does not support fixed :width/:height option" if options[:width] || options[:height]

        return { :width => 0, :height => 0 } if images.empty?

        hpadding = options[:hpadding] || 0
        vpadding = options[:vpadding] || 0
        hmargin  = options[:hmargin]  || 0
        vmargin  = options[:vmargin]  || 0

        images.each do |i|
          i[:w] = i[:width]  + (2*hpadding) + (2*hmargin)
          i[:h] = i[:height] + (2*vpadding) + (2*vmargin)
        end

        images.sort! do |a,b|
          diff = [b[:w], b[:h]].max <=> [a[:w], a[:h]].max
          diff = [b[:w], b[:h]].min <=> [a[:w], a[:h]].min if diff.zero?
          diff = b[:h] <=> a[:h] if diff.zero?
          diff = b[:w]  <=> a[:w]  if diff.zero?
          diff
        end

        root = { :x => 0, :y => 0, :w => images[0][:w], :h => images[0][:h] }

        images.each do |i|
          if (node = findNode(root, i[:w], i[:h]))
            placeImage(i, node, hpadding, vpadding, hmargin, vmargin)
            splitNode(node, i[:w], i[:h])
          else
            root = grow(root, i[:w], i[:h])
            redo
          end
        end

        { :width => root[:w], :height => root[:h] }

      end

      def self.placeImage(image, node, hpadding, vpadding, hmargin, vmargin)
        image[:cssx] = node[:x] + hmargin
        image[:cssy] = node[:y] + vmargin
        image[:cssw] = image[:width]  + (2*hpadding)
        image[:cssh] = image[:height] + (2*vpadding)
        image[:x]    = image[:cssx] + hpadding 
        image[:y]    = image[:cssy] + vpadding
      end

      def self.findNode(root, w, h)
        if root[:used]
          findNode(root[:right], w, h) || findNode(root[:down], w, h)
        elsif (w <= root[:w]) && (h <= root[:h])
          root
        end
      end

      def self.splitNode(node, w, h)
        node[:used]  = true
        node[:down]  = { :x => node[:x],     :y => node[:y] + h, :w => node[:w],     :h => node[:h] - h }
        node[:right] = { :x => node[:x] + w, :y => node[:y],     :w => node[:w] - w, :h => h            }
      end

      def self.grow(root, w, h)

        canGrowDown  = (w <= root[:w])
        canGrowRight = (h <= root[:h])

        shouldGrowRight = canGrowRight && (root[:h] >= (root[:w] + w))
        shouldGrowDown  = canGrowDown  && (root[:w] >= (root[:h] + h))

        if shouldGrowRight
          growRight(root, w, h)
        elsif shouldGrowDown
          growDown(root, w, h)
        elsif canGrowRight
          growRight(root, w, h)
        elsif canGrowDown
          growDown(root, w, h)
        else
          raise RuntimeError, "can't fit #{w}x#{h} block into root #{root[:w]}x#{root[:h]} - this should not happen if images are pre-sorted correctly"
        end

      end

      def self.growRight(root, w, h)
        return {
          :used  => true,
          :x     => 0,
          :y     => 0,
          :w     => root[:w] + w,
          :h     => root[:h],
          :down  => root,
          :right => { :x => root[:w], :y => 0, :w => w, :h => root[:h] }
        }
      end

      def self.growDown(root, w, h)
        return {
          :used  => true,
          :x     => 0,
          :y     => 0,
          :w     => root[:w],
          :h     => root[:h] + h,
          :down  => { :x => 0, :y => root[:h], :w => root[:w], :h => h },
          :right => root
        }
      end

      end # module Packed
  end # module Layout
end # module SpriteFactory
