module Rubyvis
  class Layout
  def self.Stack
    Rubyvis::Layout::Stack
  end
  class Stack < Rubyvis::Layout
    @properties=Panel.properties.dup    
    
    attr_accessor :_x, :_y, :_values, :prop
    attr_accessor_dsl :orient,:offset, :order, :layers
    def self.defaults
      Stack.new.extend(Layout.defaults).orient("bottom-left").offset("zero").layers([[]])
    end
    def initialize
      super
      @none=lambda {nil}
      @prop = {"t"=> @none, "l"=> @none, "r"=> @none, "b"=> @none, "w"=> @none, "h"=> @none}
      @values=nil
      @_x=lambda {return 0}
      @_y=lambda {return 0}
      @_values=Rubyvis.identity
    end
    def x(f)
      @_x=Rubyvis.functor(f)
      return self
    end
    def y(f)
      @_y=Rubyvis.functor(f)
      return self
    end
    def values(f=nil)
      if f.nil?
        @values
      else
        @_values=Rubyvis.functor(f)
        return self
      end
    end
    
    
    def proxy(name)
      that=self
      return lambda {
        a=that.prop[name].js_call(self, self.parent.index, self.index);
        puts "proxy(#{name}): #{a}" if $DEBUG
        a
      }
    end
    
    def build_implied(s)
      # puts "Build stack" if $DEBUG
      panel_build_implied(s)
      data = s.layers
      n = data.size
      m = nil
      orient = s.orient
      if orient =~/^(top|bottom)\b/
        horizontal=true
      else
        horizontal=false
      end
      h = self.parent.send(horizontal ? "height" : "width")
      x = []
      y = []
      dy = []
      
      #
      # Iterate over the data, evaluating the values, x and y functions. The
      # context in which the x and y psuedo-properties are evaluated is a
      # pseudo-mark that is a grandchild of this layout.
      #
      stack = Rubyvis::Mark.stack
      
      o = OpenStruct.new({:parent=> OpenStruct.new({:parent=> self})})
      stack.unshift(nil)
      values = []
      n.times {|i|
      dy[i] = []
      y[i] = []
      o.parent.index = i
      stack[0] = data[i]
      values[i] = self._values.js_apply(o.parent, stack);
      m = values[i].size if (i==0) 
      stack.unshift(nil)
      m.times {|j|
        stack[0] = values[i][j]
        o.index = j
        x[j] = self._x.js_apply(o, stack) if i==0
        dy[i][j] = self._y.js_apply(o, stack)
      }
      stack.shift()
      }
      stack.shift()
      
      # order
      _index=nil
      case (s.order) 
      when "inside-out" 
        max  = dy.map {|v| Rubyvis.max.index(v) }
        map  = pv.range(n).sort {|a,b| return max[a] - max[b]}
        sums = dy.map {|v| Rubyvis.sum(v)}
        top = 0
        bottom = 0
        tops = []
        bottoms = []
        n.times {|i|
          j = map[i]
          if (top < bottom) 
            top += sums[j];
            tops.push(j);
          else
            bottom += sums[j];
            bottoms.push(j);
          end
        }
        _index = bottoms.reverse+tops
        
      when "reverse"
        _index = Rubyvis.range(n - 1, -1, -1)
      else
        _index = Rubyvis.range(n)
      end
      
      #/* offset */
      case (s.offset) 
      when "silohouette"
        m.times {|j|
          o = 0;
          n.times {|i| 
            o += dy[i][j]
          }
          y[_index[0]][j] = (h - o) / 2.0;
        }
      
      when "wiggle"
        o = 0;
        n.times {|i|  o += dy[i][0] }
        
        y[_index[0]][0] = o = (h - o) / 2.0
        
        (1...m).each  {|j|
          s1 = 0
          s2 = 0
          dx = x[j] - x[j - 1]
          n.times {|i| s1 += dy[i][j]}
          n.times {|i|
            
            s3 = (dy[_index[i]][j] - dy[_index[i]][j - 1]) / (2.0 * dx)
            i.times {|k|
              s3 += (dy[_index[k]][j] - dy[_index[k]][j - 1]) / dx.to_f
            }
            s2 += s3 * dy[_index[i]][j]
          }
          o -= (s1!=0) ? s2 / s1.to_f * dx : 0
          y[_index[0]][j] = o
          
        }
      when "expand"
        m.times {|j|
          y[_index[0]][j] = 0
          
          k = 0
          n.times {|i|k += dy[i][j]}
          if (k!=0) 
            k = h / k.to_f
            n.times {|i| dy[i][j] *= k}
          else 
            k = h / n.to_f
            n.times { dy[i][j] = k}
          end
        }
      else
        m.times {|j| y[_index[0]][j] = 0}
      end
      
      # Propagate the offset to the other series. */
      m.times {|j|
      o = y[_index[0]][j]
      (1...n).each {|i|
        
        o += dy[_index[i - 1]][j]
        y[_index[i]][j] = o
      }
      }
      
      # /* Find the property definitions for dynamic substitution. */
      
      i = orient.index("-")
      pdy = horizontal ? "h" : "w"
      px = i < 0 ? (horizontal ? "l" : "b") : orient[i + 1,1]
      py = orient[0,1]
      
      @values=values
      
      @prop.each {|k,v|
        @prop[k]=@none
      }
      # puts "stack: x:#{px}, y:#{py}, dy:#{pdy}" if $DEBUG
      @prop[px] =lambda {|i1,j| x[j]}
      @prop[py] =lambda {|i1,j| y[i1][j]}
      @prop[pdy]=lambda {|i1,j| dy[i1][j]}  
    end
    
    def layer
      that=self
      value = Rubyvis::Mark.new().data(lambda { that.values[self.parent.index] }).top(proxy("t")).left(proxy("l")).right(proxy("r")).
        bottom(proxy("b")).
      width(proxy("w")).
      height(proxy("h"))
      
      class << value
        def that=(v)
          @that = v
        end
        def add(type)
          that  = @that
          that.add( Rubyvis.Panel ).data(lambda { that.layers() }).add(type).extend( self )
        end
      end
      value.that=self
      return value
    end
  end
  end
end