text:
window.util = util = {}

# =============================================================================
# Underscore Addons
# =============================================================================

_.concatMap = (array, fn) ->
  _.flatten(_.map(array, fn), true)

# =============================================================================
# DOM Addons
# =============================================================================

Element::matches ?= Element::webkitMatchesSelector ? Element::mozMatchesSelector ? Element::oMatchesSelector

Element::closest = (selector) ->
  if _.isString(selector)
    fn = (el) -> el.matches(selector)
  else
    fn = selector

  if fn(this)
    return this
  else
    parent = @parentNode
    if parent? && parent.nodeType == Node.ELEMENT_NODE
      return parent.closest(fn)
    else
      return undefined

Element::getMarginRect = ->
  rect = @getBoundingClientRect()
  style = window.getComputedStyle(this)
  result = {
    top: rect.top - parseInt(style["margin-top"], 10)
    left: rect.left - parseInt(style["margin-left"], 10)
    bottom: rect.bottom + parseInt(style["margin-bottom"], 10)
    right: rect.right + parseInt(style["margin-right"], 10)
  }
  result.width = result.right - result.left
  result.height = result.bottom - result.top
  return result

Element::isOnScreen = ->
  rect = @getBoundingClientRect()
  screenWidth = window.innerWidth
  screenHeight = window.innerHeight
  vertical = (0 <= rect.top <= screenHeight or 0 <= rect.bottom <= screenHeight)
  horizontal = (0 <= rect.left <= screenWidth or 0 <= rect.right <= screenWidth)
  return vertical and horizontal

Element::getClippingRect = ->
  rect = @getBoundingClientRect()
  rect = {
    left: rect.left
    right: rect.right
    top: rect.top
    bottom: rect.bottom
  }
  el = this.parentNode
  while el?.nodeType == Node.ELEMENT_NODE
    if el.matches(".Scroller")
      scrollerRect = el.getBoundingClientRect()
      rect.left   = Math.max(rect.left,   scrollerRect.left)
      rect.right  = Math.min(rect.right,  scrollerRect.right)
      rect.top    = Math.max(rect.top,    scrollerRect.top)
      rect.bottom = Math.min(rect.bottom, scrollerRect.bottom)
    el = el.parentNode
  rect.width  = rect.right  - rect.left
  rect.height = rect.bottom - rect.top
  return rect

# =============================================================================
# Util functions
# =============================================================================

util.preventDefault = (e) ->
  e.preventDefault()
  util.selection.set(null)

util.lerp = (x, dMin, dMax, rMin, rMax) ->
  ratio = (x - dMin) / (dMax - dMin)
  return ratio * (rMax - rMin) + rMin

util.floatToString = (value, precision = 0.1, removeExtraZeros = false) ->
  if precision < 1
    digitPrecision = -Math.round(Math.log(precision)/Math.log(10))
    string = value.toFixed(digitPrecision)
  else
    string = value.toFixed(0)

  if removeExtraZeros
    string = string.replace(/\.?0*$/, "")

  if /^-0(\.0*)?$/.test(string)
    # Remove extraneous negative sign
    string = string.slice(1)

  return string

util.glslString = (value) ->
  # Hard-coded optimizations
  if value == 0
    return "0."
  if value == 1
    return "1."

  if _.isNumber(value)
    # Float.
    string = value.toString()
    unless /\./.test(string)
      string = string + "."
    return string

  if _.isArray(value) and _.isNumber(value[0])
    # Vector.
    length = value.length
    if length == 1
      return util.glslString(value[0])
    string = ""
    for component, index in value
      string += util.glslString(component)
      string += "," if index < length - 1
    string = util.glslVectorType(length) + "(" + string + ")"
    return string

  if _.isArray(value) and _.isArray(value[0])
    # Matrix. Note: Numeric stores matrices as an array of arrays in row major
    # order, but glsl needs values in column major order.
    length = value.length # assume square matrix
    if length == 1
      return util.glslString(value[0][0])
    strings = []
    string = ""
    for col in [0...length]
      for row in [0...length]
        string += util.glslString(value[row][col])
        string += "," if row < length - 1 or col < length - 1
    string = util.glslMatrixType(length) + "(" + string + ")"
    return string

util.glslMatrixArray = (matrix) ->
  # Note: Numeric stores matrices as array of arrays in row major order, but
  # glsl array needs to be in column major order.
  result = []
  length = matrix.length
  for col in [0...length]
    for row in [0...length]
      result.push matrix[row][col]
  return result

util.glslVectorType = (dimensions) ->
  return "float" if dimensions == 1
  return "vec"+dimensions

util.glslMatrixType = (dimensions) ->
  return "float" if dimensions == 1
  return "mat"+dimensions

util.glslGetComponent = (expr, dimensions, component) ->
  return expr if dimensions == 1
  return expr + "[" + component + "]"

util.glslSetComponent = (expr, dimensions, component, value) ->
  return expr + " = " + value if dimensions == 1
  return expr + "[" + component + "]" + " = " + value

util.constructVector = (dimensions, value) ->
  return [0...dimensions].map -> value

util.safeInv = (m) ->
  if m.length == 1 and m[0][0] == 0
    return numeric.identity(1)

  try
    return numeric.inv(m)
  catch
    return numeric.identity(m.length)

util.vectorMask = (a, b, mask) ->
  # Takes components from a when mask is 1, b when mask is 0
  return numeric.add(
    numeric.mul(a, mask)
    numeric.mul(b, numeric.sub(1, mask))
  )

util.onceDragConsummated = (downEvent, callback, notConsummatedCallback=null) ->
  consummated = false
  originalX = downEvent.clientX
  originalY = downEvent.clientY

  handleMove = (moveEvent) ->
    dx = moveEvent.clientX - originalX
    dy = moveEvent.clientY - originalY
    d  = Math.max(Math.abs(dx), Math.abs(dy))
    if d > 3
      consummated = true
      removeListeners()
      callback?(moveEvent)

  handleUp = (upEvent) ->
    if !consummated
      notConsummatedCallback?(upEvent)
    removeListeners()

  removeListeners = ->
    window.removeEventListener("mousemove", handleMove)
    window.removeEventListener("mouseup", handleUp)

  window.addEventListener("mousemove", handleMove)
  window.addEventListener("mouseup", handleUp)

# =============================================================================
# Color
# =============================================================================

`
function RGBToHSL(r, g, b) {
  var
  min = Math.min(r, g, b),
  max = Math.max(r, g, b),
  diff = max - min,
  h = 0, s = 0, l = (min + max) / 2;

  if (diff != 0) {
    s = l < 0.5 ? diff / (max + min) : diff / (2 - max - min);

    h = (r == max ? (g - b) / diff : g == max ? 2 + (b - r) / diff : 4 + (r - g) / diff) * 60;
  }

  return [h, s, l];
}

function HSLToRGB(h, s, l) {
  if (s == 0) {
    return [l, l, l];
  }

  var temp2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
  var temp1 = 2 * l - temp2;

  h /= 360;

  var
  rtemp = (h + 1 / 3) % 1,
  gtemp = h,
  btemp = (h + 2 / 3) % 1,
  rgb = [rtemp, gtemp, btemp],
  i = 0;

  for (; i < 3; ++i) {
    rgb[i] = rgb[i] < 1 / 6 ? temp1 + (temp2 - temp1) * 6 * rgb[i] : rgb[i] < 1 / 2 ? temp2 : rgb[i] < 2 / 3 ? temp1 + (temp2 - temp1) * 6 * (2 / 3 - rgb[i]) : temp1;
  }

  return rgb;
}
`

util.rgbToHsl = RGBToHSL
util.hslToRgb = HSLToRGB

# =============================================================================
# Additional
# =============================================================================

require("./selection")
require("./canvas")
require("./evaluate")
require("./vector")


text:
HighlightColor = #09c
SliceColor = #0c9
PlotBackgroundColor = #FAF8F2

LeftSidebarWidth = 180px
RightSidebarWidth = 270px
InspectorHeight = 266px

HeaderSize = 18px

PlusButtonSize = 18px

DisclosureTriangleSize = 12px
SpacingSize = 6px
OutlineThumbnailSize = 36px
PaletteThumbnailSize = 48px

PointControlSize = 12px
LineControlSize = 2px

ShaderOverlayZIndex = 10
PlotControlZIndex = 20
SymbolicZIndex = 30

inline(va)
  display inline-block
  vertical-align va

shadow(size, opacity)
  box-shadow 0px size 2*size rgba(0, 0, 0, opacity)

// ============================================================================
// Generic
// ============================================================================

html, body
  margin 0

[contenteditable]:active, [contenteditable]:focus, button:active, button:focus
  outline none

[contenteditable]
  cursor text

body
  font-family "Lucida Sans Unicode", "Lucida Grande", sans-serif
  font-size 12px
  cursor default
  overflow hidden
  color #333

canvas, .Shader
  position absolute
  top 0
  left 0
  width 100%
  height 100%
  z-index 15

// Kill scrollbars in webkit...
// ::-webkit-scrollbar {
//     width: 0px;
//     height: 0px;
// }

// ============================================================================
// Main Layout
// ============================================================================

.ShaderOverlay
  z-index ShaderOverlayZIndex
  pointer-events none

.PlotLayout
  position absolute
  top 0px
  left LeftSidebarWidth
  bottom 0px
  right RightSidebarWidth
  border-left 1px solid #e6e6e6
  border-right 1px solid #e6e6e6
  background-color #eee
  box-sizing border-box

.Palette
  position absolute
  left 0px
  width LeftSidebarWidth
  top 0px
  bottom 0px

.Outline
  position absolute
  right 0px
  width RightSidebarWidth
  top 0px
  bottom InspectorHeight

.Inspector
  position absolute
  right 0px
  width RightSidebarWidth
  height InspectorHeight
  bottom 0px

.Symbolic
  position absolute
  bottom 90px
  left LeftSidebarWidth
  right RightSidebarWidth
  z-index SymbolicZIndex

// ============================================================================
// Dragging
// ============================================================================

.DraggingObject
  position: fixed
  z-index: 100;
  pointer-events: none;
  opacity 0.5
  box-shadow: 0px 2px 6px rgba(0,0,0,0.5);

.DraggingOverlay
  position: fixed
  top: 0; left: 0; bottom: 0; right: 0;
  z-index: 200

// ============================================================================
// Common
// ============================================================================

.Header
  position absolute
  width 100%
  top 0px
  height HeaderSize
  box-sizing border-box
  line-height HeaderSize - 2
  text-align center
  color #666
  text-shadow 0px 1px 0px rgba(255,255,255,0.5)
  background-color #eee
  background-image linear-gradient(to bottom, #fff, #ddd)
  border-top 1px solid #e6e6e6
  border-bottom 1px solid #d3d3d3

.Scroller
  position absolute
  width 100%
  top HeaderSize
  bottom 0px
  overflow-y auto
  overflow-x hidden

.Label
  // Give text editing cursor some room
  min-width 30px
  padding-right 10px
  &:empty:not(:active):not(:focus):before
    content "Untitled Function"
    font-style italic
    color #ccc

// ============================================================================
// Palette
// ============================================================================

.Definition
  white-space nowrap

  // &.Hovered
  &:hover
    background-color tint(HighlightColor, 95%)

  .PlotContainer
    inline middle
    position relative
    width PaletteThumbnailSize
    height PaletteThumbnailSize
    border 1px solid #eee
    margin SpacingSize SpacingSize

  &.Selected
    .PlotContainer
      outline 1px solid HighlightColor
      shadow 1px 0.5

  .Label
    inline middle
    // margin-left SpacingSize

.DragHint
  position fixed
  z-index SymbolicZIndex
  pointer-events none
  margin-left SpacingSize
  margin-top SpacingSize * -2
  white-space nowrap
  padding SpacingSize
  border-radius SpacingSize
  background-color #333
  color #eee
  opacity 0
  transition opacity 0.2s

.PaletteHeader
  // padding-left SpacingSize*2 + PaletteThumbnailSize
  padding SpacingSize 0 SpacingSize SpacingSize*2
  background-color #f3f3f3
  // text-align center
  &:not(:first-child)
    margin-top SpacingSize*2

.AddDefinition
  margin SpacingSize 0px
  padding SpacingSize
  // width PaletteThumbnailSize
  text-align center

// ============================================================================
// Plot Layout
// ============================================================================

.PlotLocation
  position absolute

.PlotContainer
  position absolute
  top 0
  left 0
  width 100%
  height 100%
  background-color PlotBackgroundColor
  overflow hidden

.PointControlContainer
  position absolute
  top 50%
  left 50%
  width 100%
  height 100%

.PointControl
  position absolute
  width PointControlSize
  height PointControlSize
  margin-left -0.5 * PointControlSize
  margin-top -0.5 * PointControlSize
  border-radius 0.5 * PointControlSize
  background-color HighlightColor
  opacity 0.25
  z-index PlotControlZIndex
  &:hover
    opacity 1

.LineControl
  position absolute
  background-color SliceColor
  z-index PlotControlZIndex
  opacity 0.5
  width LineControlSize
  height LineControlSize
  margin LineControlSize * -0.5
  &:hover
    opacity 1
  &:before
    content " "
    position absolute
    width 100%
    height 100%
    padding 10px
    margin -10px

.SettingsButton
  position absolute
  top SpacingSize*2
  right SpacingSize*2
  z-index PlotControlZIndex
  font-size 20px
  color tint(HighlightColor, 50%)
  text-shadow 0px 1px 3px rgba(0,0,0,0.5)
  cursor pointer
  opacity 0.5
  &:hover
    opacity 1

// ============================================================================
// Outline
// ============================================================================

.OutlineCombinerToolbar
  padding SpacingSize 0 SpacingSize SpacingSize*2
  background-color #f3f3f3
  .OutlineCombinerButton
    padding-right SpacingSize
    border-right 1px solid #ccc
    margin-right SpacingSize
    cursor pointer
    &:hover
      color HighlightColor
    &:last-child
      border-right 0

.Outline .Scroller > .OutlineChildren
  margin-left 0px

.OutlineItem
  &.Invisible
    opacity 0.25

.OutlineRow
  position relative
  white-space nowrap
  &.Hovered
    background-color tint(HighlightColor, 95%)
  &.Selected
    background-color tint(HighlightColor, 85%)

.OutlineVisible
  position absolute
  right SpacingSize
  line-height OutlineThumbnailSize + SpacingSize
  font-size 18px
  color #bbb
  opacity 0
  &:hover
    color HighlightColor
.OutlineRow:hover
  .OutlineVisible
    opacity 1

.OutlineChildren
  margin-left DisclosureTriangleSize + SpacingSize*2

.OutlineDisclosure
  inline middle
  padding SpacingSize

.OutlineInternals
  inline middle
  margin 3px 6px
  .Label
    inline middle

.OutlineThumbnail
  position relative
  inline middle
  width OutlineThumbnailSize
  height OutlineThumbnailSize
  margin 2px
  border 1px solid #eee

.Placeholder
  background-color #bbb
  box-shadow inset 0px 1px 3px rgba(0,0,0,0.5)

.DisclosureTriangle
  width 0
  height 0
  margin 0px 0.067*DisclosureTriangleSize
  border-top: 0.5*DisclosureTriangleSize solid transparent
  border-bottom: 0.5*DisclosureTriangleSize solid transparent
  border-left: 0.866*DisclosureTriangleSize solid #ddd
  transition -webkit-transform 0.05s
  &.Expanded
    -webkit-transform: rotate(90deg)
  &.Hidden
    // visibility hidden
    display none

// ============================================================================
// Inspector
// ============================================================================

table
  border-collapse collapse
  width 100%

td, th
  width 54px

tr
  td:first-child, th:first-child
    width 24px
    color #333
    text-align center
  td:last-child, th:last-child
    width auto

th
  background-color #f3f3f3

.Translate
  td
    // background-color #f3f3f3
    border-bottom 1px solid #eee

.Variable, th
  padding 2px 6px
  text-align left

// ============================================================================
// Symbolic View
// ============================================================================

.Symbolic
  text-align center
  font-size 24px
  margin 0px 10px
  border-radius 8px
  padding 0px 4px
  line-height 36px
  background-color rgba(255,255,255,0.8)

.SymbolVector, .SymbolMatrix
  display inline-table
  position relative
  border-collapse collapse

.SymbolVector
  top -2px

.SymbolMatrix
  top: -10px
  height 36px

.SymbolRow
  display table-row
  > .SymbolCell
    font-size 12px

.SymbolCell
  display table-cell
  padding 3px
  font-size 16px
  line-height 1
  background-color rgba(0,0,0,0.2)

// ============================================================================
// Buttons
// ============================================================================

.TextButton
  cursor pointer
  font-size 10px
  &:hover
    color HighlightColor

.AddButton
  width PlusButtonSize
  height PlusButtonSize
  padding 0
  line-height PlusButtonSize
  font-size 9px
  color #777
  font-family verdana
  font-weight bold
  text-shadow 0 1px 0 rgba(255,255,255,0.5)
  background-color #ddd
  background-image linear-gradient(to bottom, #eee, #ccc)
  box-shadow 0 2px 1px rgba(0,0,0,0.16), 0 0 1px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.55)
  border-radius 20px
  border 0px
  &:before
    content "+"
  &:hover
    background-image linear-gradient(to bottom, #f3f3f3, #d3d3d3)
  &:active
    background-image linear-gradient(to bottom, #cacaca, #d5d5d5)
    box-shadow 0 1px 1px rgba(255,255,255,0.35), inset 0 0 1px rgba(0,0,0,0.5), inset 0 1px 4px rgba(0,0,0,0.4)

repo_name: cdglabs/Shadershop

repo_language: JavaScript

mime_type: text/plain


text:
R.create "PlotLayoutView",
  propTypes:
    fn: C.DefinedFn

  render: ->
    plotLocations = @fn.plotLayout.getPlotLocations()

    R.div {className: "PlotLayout"},

      for {plot, x, y, w, h}, index in plotLocations
        R.div {
          className: "PlotLocation"
          style: {
            left:   x * 100 + "%"
            top:    y * 100 + "%"
            width:  w * 100 + "%"
            height: h * 100 + "%"
          }
          key: index
        },
          R.PlotView {
            fn: @fn
            plot: plot
            showSliceControl: plotLocations.length > 1
          }

      # Settings Button
      R.div {className: "SettingsButton Interactive", onClick: @_onSettingsButtonClick},
        R.div {className: "icon-cog"}

  _onSettingsButtonClick: ->
    # TODO This method of controlling plot layouts is getting even more hardcoded...
    plotLayout = @fn.plotLayout
    if plotLayout.display2d
      plotLayout.display2d = false
      plotLayout.plots[0].type = "cartesian"
    else
      plotLayout.display2d = true
      plotLayout.plots[0].type = "colorMap"
      plotLayout.plots[1].type = "cartesian"
      plotLayout.plots[2].type = "cartesian2"

R.create "PlotView",
  propTypes:
    fn: C.DefinedFn
    plot: C.Plot
    showSliceControl: Boolean

  _getExpandedChildFns: ->
    result = []
    recurse = (childFns) ->
      for childFn in childFns
        continue unless childFn.visible
        result.push(childFn)
        if UI.isChildFnExpanded(childFn) and childFn.fn instanceof C.CompoundFn
          recurse(childFn.fn.childFns)
    recurse(@fn.childFns)
    return result

  _getWorldMouseCoords: ->
    rect = @getDOMNode().getBoundingClientRect()
    x = UI.mousePosition.x - (rect.left + rect.width/2)
    y = UI.mousePosition.y - (rect.top + rect.height/2)
    y *= -1
    return @plot.toWorld({x, y})

  _findHitTarget: ->
    pixelSize = @plot.getPixelSize()
    maxDistance = config.hitTolerance * pixelSize
    maxQuadrance = maxDistance * maxDistance

    point = @_getWorldMouseCoords()

    mask = @plot.getMask()
    outputMask = mask[config.dimensions ... ]

    inputVal  = point[ ... config.dimensions]
    outputVal = point[config.dimensions ... ]

    found = null
    foundError = maxQuadrance

    for childFn in @_getExpandedChildFns()
      evaluated = childFn.evaluate(inputVal)

      offset = numeric.sub(outputVal, evaluated)
      offset = numeric.mul(offset, outputMask) # We only care about the projected output.
      error = numeric.norm2Squared(offset)

      if error < foundError
        found = childFn
        foundError = error

    return found

  render: ->
    fns = []

    if @plot.type == "colorMap"

      # Main
      fns.push {
        fn: @fn
      }

    else

      expandedChildFns = @_getExpandedChildFns()

      # Child Fns
      for childFn in expandedChildFns
        fns.push {
          fn: childFn
          color: config.color.child
        }

      # Hovered
      if UI.hoveredChildFn and _.contains(expandedChildFns, UI.hoveredChildFn)
        fns.push {
          fn: UI.hoveredChildFn
          color: config.color.hovered
        }

      # Main
      fns.push {
        fn: @fn
        color: config.color.main
      }

      # Selected
      for childFn in UI.selectedChildFns
        fns.push {
          fn: childFn
          color: config.color.selected
        }

      # Remove redundant fns
      fns = _.reject fns, (fnHolder, fnIndex) ->
        for i in [fnIndex+1 ... fns.length]
          if fns[i].fn == fnHolder.fn
            return true
        return false

    R.div {
      className: "PlotContainer",
      onMouseDown: @_onMouseDown,
      onWheel: @_onWheel,
      onMouseMove: @_onMouseMove,
      onMouseLeave: @_onMouseLeave
    },
      # Grid
      R.GridView {plot: @plot, isThumbnail: false}

      R.ShaderCartesianView {
        plot: @plot
        fns: fns
        isThumbnail: false
      }

      if @showSliceControl
        R.SliceControlsView {
          plot: @plot
        }

      if UI.selectedChildFns.length == 1
        R.ChildFnControlsView {
          childFn: UI.selectedChildFns[0]
          plot: @plot
        }

      # # Settings Button
      # R.div {className: "SettingsButton Interactive", onClick: @_onSettingsButtonClick},
      #   R.div {className: "icon-cog"}

  _onMouseMove: ->
    Actions.hoverChildFn(@_findHitTarget())

  _onMouseLeave: ->
    Actions.hoverChildFn(null)

  _onMouseDown: (e) ->
    return if e.target.closest(".Interactive")
    util.preventDefault(e)

    @_startPan(e)

    util.onceDragConsummated e, null, =>
      @_changeSelection()

  _onWheel: (e) ->
    e.preventDefault()

    return if Math.abs(e.deltaY) <= 1
    scaleFactor = 1.1
    scaleFactor = 1 / scaleFactor if e.deltaY < 0

    zoomCenter = @_getWorldMouseCoords()

    plotLayout = @fn.plotLayout
    Actions.zoomPlotLayout(plotLayout, zoomCenter, scaleFactor)

  _changeSelection: ->
    childFn = @_findHitTarget()
    if key.command or key.shift
      if childFn?
        Actions.toggleSelectChildFn(childFn)
    else
      Actions.selectChildFn(childFn)

  _startPan: (e) ->
    from = @_getWorldMouseCoords()

    UI.dragging = {
      cursor: config.cursor.grabbing
      onMove: (e) =>
        to = @_getWorldMouseCoords()

        plotLayout = @fn.plotLayout
        Actions.panPlotLayout(plotLayout, from, to)
    }

  _onSettingsButtonClick: ->
    # TODO: will be more than just a toggle...
    if @plot.type == "cartesian"
      @plot.type = "colorMap"
    else
      @plot.type = "cartesian"

R.create "ChildFnControlsView",
  propTypes:
    childFn: C.ChildFn
    plot: C.Plot

  render: ->
    R.div {className: "Interactive PointControlContainer"},
      R.PointControlView {
        position: @_getTranslatePosition()
        onMove: @_setTranslatePosition
      }
      for transformInfo in @_getVisibleTransforms()
        R.PointControlView {
          position: @_getTransformPosition(transformInfo)
          onMove: @_setTransformPosition(transformInfo)
          key: transformInfo.space + transformInfo.basisVectorIndex
        }

  _snap: (value) ->
    pixelSize = @plot.getPixelSize()

    {largeSpacing, smallSpacing} = util.canvas.getSpacing(pixelSize)

    snapTolerance = pixelSize * config.snapTolerance

    nearestSnap = Math.round(value / largeSpacing) * largeSpacing
    if Math.abs(value - nearestSnap) < snapTolerance
      value = nearestSnap
      digitPrecision = Math.floor(Math.log(largeSpacing) / Math.log(10))
      precision = Math.pow(10, digitPrecision)
      return util.floatToString(value, precision)

    digitPrecision = Math.floor(Math.log(pixelSize) / Math.log(10))
    precision = Math.pow(10, digitPrecision)

    return util.floatToString(value, precision)

  _getTranslatePosition: ->
    translate = [].concat(
      @childFn.domainTranslate.map (v) -> v.getValue()
      @childFn.rangeTranslate.map (v) -> v.getValue()
    )
    return @plot.toPixel(translate)

  _setTranslatePosition: ({x, y}) ->
    translate = @plot.toWorld({x, y})

    for isRepresented, coord in @plot.getMask()
      if isRepresented
        value = translate[coord]
        valueString = @_snap(value)

        # This is hacky because childFn's data is stored the "old" way
        variable = if coord < translate.length/2
          @childFn.domainTranslate[coord]
        else
          @childFn.rangeTranslate[coord - translate.length/2]

        Actions.setVariableValueString(variable, valueString)

  _getVisibleTransforms: ->
    mask = @plot.getMask()
    visibleTransforms = []
    for space in ["domain", "range"]
      for basisVectorIndex in [0 ... config.dimensions]
        basisVector = @childFn.getBasisVector(space, basisVectorIndex)
        indexOffset = if space == "domain" then 0 else config.dimensions

        isVisible = _.all [0 ... config.dimensions], (coord) =>
          return true if mask[coord + indexOffset] == 1 # represented on the plot
          return true if basisVector[coord] == @plot.focus[coord + indexOffset] # "in" the projection
          return false

        if isVisible
          visibleTransforms.push {
            space
            basisVectorIndex
            basisVector
          }
    return visibleTransforms

  _getTransformPosition: (transformInfo) ->
    translate = [].concat(
      @childFn.domainTranslate.map (v) -> v.getValue()
      @childFn.rangeTranslate.map (v) -> v.getValue()
    )
    if transformInfo.space == "domain"
      transform = [].concat(
        transformInfo.basisVector
        util.constructVector(config.dimensions, 0)
      )
    else
      transform = [].concat(
        util.constructVector(config.dimensions, 0)
        transformInfo.basisVector
      )

    worldPosition = numeric.add(translate, transform)
    return @plot.toPixel(worldPosition)

  _setTransformPosition: (transformInfo) ->
    ({x, y}) =>
      translate = [].concat(
        @childFn.domainTranslate.map (v) -> v.getValue()
        @childFn.rangeTranslate.map (v) -> v.getValue()
      )

      point = @plot.toWorld({x, y})

      offset = numeric.sub(point, translate)

      if transformInfo.space == "domain"
        newBasisVector = offset[ ... config.dimensions]
        mask = @plot.getMask()[ ... config.dimensions]
      else
        newBasisVector = offset[config.dimensions ... ]
        mask = @plot.getMask()[config.dimensions ... ]

      newBasisVector = util.vectorMask(newBasisVector, transformInfo.basisVector, mask)

      valueStrings = newBasisVector.map (value) => @_snap(value)

      Actions.setBasisVector(@childFn, transformInfo.space, transformInfo.basisVectorIndex, valueStrings)

R.create "PointControlView",
  propTypes:
    position: Object # {x, y} in the Pixel frame
    onMove: Function

  render: ->
    R.div {
      className: "PointControl",
      onMouseDown: @_onMouseDown
      style: {
        left: @position.x
        top: -@position.y
      }
    }

  _onMouseDown: (e) ->
    util.preventDefault(e)

    container = @getDOMNode().closest(".PointControlContainer")
    rect = container.getBoundingClientRect()

    UI.dragging = {
      onMove: (e) =>
        x = e.clientX - rect.left
        y = e.clientY - rect.top
        y *= -1
        @onMove({x, y})
    }

R.create "SliceControlsView",
  propTypes:
    plot: C.Plot

  render: ->
    R.div {className: "Interactive PointControlContainer"},
      for slice, index in @_getSlices()
        R.LineControlView {position: slice, onMove: @_onMove, key: index}

  _getSlices: ->
    slices = []

    pixelFocus = @plot.toPixel(@plot.focus)

    for dimension, dimensionIndex in @plot.getDimensions()
      for coord in [0 ... config.dimensions]
        if dimension[coord] == 1
          if dimensionIndex == 0 # x
            slices.push({x: pixelFocus.x})
          else if dimensionIndex == 1 # y
            slices.push({y: pixelFocus.y})
    return slices

  _onMove: (position) ->
    pixelFocus = @plot.toPixel(@plot.focus)
    pixelFocus = _.extend(pixelFocus, position)
    focus = @plot.toWorld(pixelFocus)

    pixelSize = @plot.getPixelSize()
    {largeSpacing, smallSpacing} = util.canvas.getSpacing(pixelSize)
    snapTolerance = pixelSize * config.snapTolerance

    focus = focus.map (value) ->
      nearestSnap = Math.round(value / largeSpacing) * largeSpacing
      if Math.abs(value - nearestSnap) < snapTolerance
        return nearestSnap
      else
        return value

    plotLayout = @lookup("fn").plotLayout
    Actions.setPlotLayoutFocus(plotLayout, focus)

R.create "LineControlView",
  propTypes:
    position: Object # {x, y} in the Pixel frame
    onMove: Function

  render: ->
    R.div {
      className: "LineControl"
      onMouseDown: @_onMouseDown
      style: {
        left:   if @position.x? then  @position.x else "-50%"
        top:    if @position.y? then -@position.y else "-50%"
        width:  if @position.x? then "" else "100%"
        height: if @position.y? then "" else "100%"
      }
    }

  _onMouseDown: (e) ->
    util.preventDefault(e)

    container = @getDOMNode().closest(".PointControlContainer")
    rect = container.getBoundingClientRect()

    UI.dragging = {
      onMove: (e) =>
        x = e.clientX - rect.left
        y = e.clientY - rect.top
        y *= -1
        if @position.x?
          @onMove({x})
        else
          @onMove({y})
    }


text:
R.create "SymbolicView",
  render: ->
    return R.div() if !UI.showSymbolic

    d = dimensionsToDisplay()
    if d == 1
      freeVariable = "x"
    else
      freeVariable = 'x₁x₂'

    string = stringifyFn(UI.selectedFn, freeVariable, true)

    R.div {},
      if string
        R.div {className: "Symbolic", dangerouslySetInnerHTML: {__html: string}}

# HACK
dimensionsToDisplay = ->
  if UI.selectedFn.plotLayout.display2d then 2 else 1

isVectorIdentity = (v) ->
  return _.all v, (n) -> n == 0

isMatrixIdentity = (m) ->
  for row, rowIndex in m
    for value, colIndex in row
      return false if rowIndex == colIndex and value != 1
      return false if rowIndex != colIndex and value != 0
  return true

nonIdentityCell = (x, y, m) ->
  identityCell = if x == y then 1 else 0
  return m[x][y] != identityCell

nonIdentitySize = (m) ->
  size = 0
  for d in [0 ... config.dimensions]
    found = false

    y = d
    for x in [0 .. d]
      if nonIdentityCell(x, y, m)
        found = true

    x = d
    for y in [0 .. d]
      if nonIdentityCell(x, y, m)
        found = true

    if found
      size = d + 1

  return size

formatMatrix = (m) ->
  if isMatrixIdentity(m)
    return null

  size = dimensionsToDisplay()
  if size == 1
    return formatNumber m[0][0]

  rows = []
  for rowIndex in [0...size]
    row = []
    for colIndex in [0...size]
      row.push '' + formatNumber(m[rowIndex][colIndex]) + ''
    rows.push '' + row.join("") + ''
  return '' + rows.join("") + ''

formatVector = (v) ->
  if isVectorIdentity(v)
    return null

  size = dimensionsToDisplay()
  if size == 1
    return formatNumber v[0]

  cells = []
  for d in [0...size]
    cells.push '' + formatNumber(v[d]) + ''
  return '' + cells.join("") + ''

formatNumber = (n) ->
  s = n.toFixed(3)
  # remove excess 0s after decimal point
  if s.indexOf(".") != -1
    s = s.replace(/\.?0*$/, "")
  return s

stringifyFn = (fn, freeVariable, force=false) ->
  if fn instanceof C.BuiltInFn
    if fn.label == "Line"
      return freeVariable
    else
      return fn.label + "( #{freeVariable} )"

  if fn instanceof C.DefinedFn and !force
    return fn.label + "( #{freeVariable} )"

  if fn instanceof C.CompoundFn
    visibleChildFns = _.filter fn.childFns, (childFn) -> childFn.visible

    if fn.combiner == "last"
      return stringifyFn(_.last(visibleChildFns), freeVariable)

    if fn.combiner == "composition"
      s = freeVariable
      for childFn in visibleChildFns
        s = stringifyFn(childFn, s)
      return s

    if fn.combiner == "sum"
      if visibleChildFns.length == 0
        return "0"
      strings = for childFn in visibleChildFns
        stringifyFn(childFn, freeVariable)
      return "(" + strings.join(" + ") + ")"

    if fn.combiner == "product"
      if visibleChildFns.length == 0
        return "1"
      strings = for childFn in visibleChildFns
        "(#{stringifyFn(childFn, freeVariable)})"
      return "(" + strings.join(" * ") + ")"

    if fn.combiner == "min" or fn.combiner == "max"
      if visibleChildFns.length == 0
        return "0"
      strings = for childFn in visibleChildFns
        stringifyFn(childFn, freeVariable)
      label = if fn.combiner == "min" then "Min" else "Max"
      return "#{label}(" + strings.join(", ") + ")"

  if fn instanceof C.ChildFn
    domainTranslate = formatVector fn.getDomainTranslate()
    domainTransform = formatMatrix fn.getDomainTransform()
    rangeTranslate  = formatVector fn.getRangeTranslate()
    rangeTransform  = formatMatrix fn.getRangeTransform()

    s = freeVariable

    if domainTranslate?
      s = "(#{s} - #{domainTranslate})"
    if domainTransform?
      s = "#{s} / #{domainTransform}"

    s = stringifyFn(fn.fn, s)

    if rangeTransform?
      s = "#{s} * #{rangeTransform}"
    if rangeTranslate?
      s = "#{s} + #{rangeTranslate}"

    return s


text:
R.create "VariableView",
  propTypes:
    variable: C.Variable

  render: ->
    R.TextFieldView {
      className: "Variable"
      value: @variable.valueString
      onInput: @_onInput
    }

  _onInput: (newValue) ->
    Actions.setVariableValueString(@variable, newValue)


text:
R.create "PaletteView",
  propTypes:
    appRoot: C.AppRoot

  render: ->
    R.div {className: "Palette"},

      R.div {className: "DragHint"}, "Drag →"

      R.div {className: "Header"},
        "Library"

      R.div {className: "Scroller"},

        R.div {className: "PaletteHeader"}, "Built In Functions"

        builtIn.fns.map (fn) =>
          R.DefinitionView {fn, key: C.id(fn)}

        R.div {className: "PaletteHeader"}, "Custom Functions"

        @appRoot.fns.map (fn) =>
          R.DefinitionView {fn, key: C.id(fn)}

        R.div {className: "AddDefinition"},
          R.button {className: "AddButton", onClick: @_onAddButtonClick}

  _onAddButtonClick: ->
    Actions.addDefinedFn()

R.create "DefinitionView",
  propTypes:
    fn: C.Fn

  render: ->
    if @fn instanceof C.BuiltInFn
      plotLayout = builtIn.defaultPlotLayout
    else
      plotLayout = @fn.plotLayout

    className = R.cx {
      Definition: true
      Selected: UI.selectedFn == @fn
    }

    R.div {
      className: className
      onMouseDown: @_onMouseDown
    },
      R.ThumbnailPlotLayoutView {plotLayout, fn: @fn}
      R.LabelView {
        fn: @fn
      }

  _onMouseDown: (e) ->
    return if e.target.matches(".Interactive")
    util.preventDefault(e)

    x = e.clientX
    y = e.clientY

    addChildFn = =>
      Actions.addChildFn(@fn)

    selectFn = =>
      if @fn instanceof C.BuiltInFn
        @_showDragHint(x, y)
      else
        Actions.selectFn(@fn)

    util.onceDragConsummated(e, addChildFn, selectFn)

  _showDragHint: (x, y) ->
    el = document.querySelector(".DragHint")
    el.style.left = x + "px"
    el.style.top = y + "px"
    el.style.opacity = "1"
    setTimeout (-> el.style.opacity = "0"), 1600


text:
Glod = require("./plot/glod")

R.create "ShaderOverlayView",

  initializeGlod: ->
    @glod = new Glod()
    canvas = @getDOMNode()
    @glod.canvas(canvas, {antialias: true})

    gl = @glod.gl()
    gl.enable(gl.SCISSOR_TEST)
    gl.lineWidth(1.25)

    bufferQuad(@glod)
    bufferCartesianSamples(@glod, 20000)

    @programs = {}

  sizeCanvas: ->
    canvas = @getDOMNode()
    rect = canvas.getBoundingClientRect()
    devicePixelRatio = window.devicePixelRatio || 1 # Scale canvas for HiDPI displays
    if canvas.width != rect.width or canvas.height != rect.height
      canvas.width = rect.width * devicePixelRatio
      canvas.height = rect.height * devicePixelRatio

  draw: ->
    canvas = @getDOMNode()

    usedPrograms = {}

    shaderEls = document.querySelectorAll(".Shader")
    for shaderEl in shaderEls
      continue unless shaderEl.isOnScreen()

      rect = shaderEl.getBoundingClientRect()
      clippingRect = shaderEl.getClippingRect()

      continue if clippingRect.height <= 0 or clippingRect.width <= 0

      setViewport(@glod, rect, clippingRect)

      shaderView = shaderEl.dataFor
      fns = shaderView.fns
      plot = shaderView.plot

      if shaderView.isThumbnail
        scaleFactor = window.innerHeight / rect.height # kinda hacky?
      else
        scaleFactor = 1

      for fnHolder in fns
        glsl = Compiler.getGlsl(fnHolder.fn)
        name = plot.type + "," + glsl
        unless @programs[name]

          if plot.type == "cartesian" or plot.type == "cartesian2"
            createCartesianProgram(@glod, name, glsl)
          else if plot.type == "colorMap"
            createColorMapProgram(@glod, name, glsl)

          @programs[name] = true
        usedPrograms[name] = true

        if plot.type == "cartesian" or plot.type == "cartesian2"
          drawCartesianProgram(@glod, name, fnHolder.color, plot, rect.width, rect.height, scaleFactor)
        else if plot.type == "colorMap"
          bounds = plot.getScaledBounds(rect.width, rect.height, scaleFactor)
          drawColorMapProgram(@glod, name, bounds)

    # Delete unused programs
    for own name, junk of @programs
      unless usedPrograms[name]
        delete @glod._programs[name]
        delete @programs[name]

  getAdditionalCode: ->
    additionalCode = ""
    vecType = util.glslVectorType(config.dimensions)
    for own id, exprString of Compiler.getAllDefinedFnExprStrings()
      additionalCode += "#{vecType} #{id}(#{vecType} inputVal) {return #{exprString};}\n"
    return additionalCode

  # ===========================================================================
  # Lifecycle
  # ===========================================================================

  handleResize: ->
    @sizeCanvas()
    @draw()

  componentDidMount: ->
    @initializeGlod()
    @sizeCanvas()
    window.addEventListener("resize", @handleResize)

  componentWillUnmount: ->
    window.removeEventListener("resize", @handleResize)

  # ===========================================================================
  # Render
  # ===========================================================================

  render: ->
    R.canvas {className: "ShaderOverlay"}

# =============================================================================
# Glod
# =============================================================================

# =============================================================================
# Utility
# =============================================================================

createProgramFromSrc = (glod, name, vertex, fragment) ->
  Glod.preprocessed[name] = {name, fragment, vertex}

  delete glod._programs[name]
  glod.createProgram(name)

setViewport = (glod, rect, clippingRect) ->
  gl = glod.gl()
  canvas = glod.canvas()

  # Scale everything by devicePixelRatio for HiDPI displays
  x = rect.left * devicePixelRatio
  y = canvas.height - rect.bottom * devicePixelRatio
  w = rect.width * devicePixelRatio
  h = rect.height * devicePixelRatio

  sx = clippingRect.left * devicePixelRatio
  sy = canvas.height - clippingRect.bottom * devicePixelRatio
  sw = clippingRect.width * devicePixelRatio
  sh = clippingRect.height * devicePixelRatio

  gl.viewport(x, y, w, h)
  gl.scissor(sx, sy, sw, sh)

  glod.viewport_ = {x, y, w, h}

# =============================================================================
# VBOs
# =============================================================================

bufferQuad = (glod) ->
  glod
    .createVBO("quad")
    .uploadCCWQuad("quad")

bufferCartesianSamples = (glod, numSamples) ->
  samplesArray = []
  for i in [0..numSamples]
    samplesArray.push(i)

  if glod.hasVBO("samples")
    glod.deleteVBO("samples")

  glod
    .createVBO("samples")
    .bufferDataStatic("samples", new Float32Array(samplesArray))

# =============================================================================
# Shader Programs
# =============================================================================

createCartesianProgram = (glod, name, glsl) ->

  vecType = util.glslVectorType(config.dimensions)
  matType = util.glslMatrixType(config.dimensions)

  vertex = """
  precision highp float;
  precision highp int;

  attribute float sample;

  uniform #{vecType} domainStart, domainStep;

  uniform #{vecType} domainCenter, rangeCenter;
  uniform #{matType} domainTransform, rangeTransform;

  uniform #{vecType} selectedDomainTranslate, selectedRangeTranslate;
  uniform #{matType} selectedDomainTransformInv, selectedRangeTransform;

  uniform vec2 pixelScale;

  #{glsl}

  void main() {
    #{vecType} inputVal, outputVal;
    inputVal = domainStart + domainStep * sample;
    outputVal = mainFn(inputVal);

    #{vecType} position = domainTransform * (inputVal - domainCenter) +
                          rangeTransform * (outputVal - rangeCenter);

    gl_Position = vec4(vec2(position.x, position.y) * pixelScale, 0., 1.);
  }
  """

  fragment = """
  precision highp float;
  precision highp int;

  uniform vec4 color;

  void main() {
    gl_FragColor = color;
  }
  """

  createProgramFromSrc(glod, name, vertex, fragment)

drawCartesianProgram = (glod, name, color, plot, width, height, scaleFactor) ->
  dimensions = plot.getDimensions()
  pixelSize  = plot.getPixelSize()

  width *= scaleFactor
  height *= scaleFactor

  # Determine domainStart, domainStep, numSamples. Note: this probably needs
  # to be generalized with more information in plot, e.g. for parametric
  # cartesian plot.
  isHorizontal = (dimensions[0][0] == 1 or dimensions[1][0] == 1)
  if isHorizontal
    domainStart = plot.toWorld({x: -width/2, y: 0})[ ... config.dimensions]
    domainEnd   = plot.toWorld({x:  width/2, y: 0})[ ... config.dimensions]
    numSamples  = (width / scaleFactor) / config.resolution
  else
    domainStart = plot.toWorld({x: 0, y: -height/2})[ ... config.dimensions]
    domainEnd   = plot.toWorld({x: 0, y:  height/2})[ ... config.dimensions]
    numSamples  = (height / scaleFactor) / config.resolution

  domainStep = numeric.div(
    numeric.sub(domainEnd, domainStart)
    numSamples
  )

  # Determine domainCenter, rangeCenter
  domainCenter = plot.center[ ... config.dimensions]
  rangeCenter  = plot.center[config.dimensions ... ]

  # Determine domainTransform, rangeTransform
  domainTransformSmall = numeric.getBlock(dimensions, [0,0], [1,config.dimensions - 1])
  rangeTransformSmall  = numeric.getBlock(dimensions, [0,config.dimensions], [1,config.dimensions*2 - 1])

  domainTransform = numeric.identity(config.dimensions)
  rangeTransform  = numeric.identity(config.dimensions)
  numeric.setBlock(domainTransform, [0,0], [1,config.dimensions - 1], domainTransformSmall)
  numeric.setBlock(rangeTransform,  [0,0], [1,config.dimensions - 1], rangeTransformSmall)

  # Determine pixelScale
  pixelScale = [
    (1 / pixelSize) * (1 / (width / 2))
    (1 / pixelSize) * (1 / (height / 2))
  ]

  glod.begin(name)

  glod.pack("samples", "sample")

  glod.valuev("color", color)

  glod.valuev("domainStart", domainStart)
  glod.valuev("domainStep", domainStep)
  glod.valuev("domainCenter", domainCenter)
  glod.valuev("rangeCenter", rangeCenter)
  glod.valuev("domainTransform", util.glslMatrixArray(domainTransform))
  glod.valuev("rangeTransform", util.glslMatrixArray(rangeTransform))
  glod.valuev("pixelScale", pixelScale)
  addSelectedChildFnUniforms(glod)

  glod.ready().lineStrip().drawArrays(0, numSamples)

  glod.end()

createColorMapProgram = (glod, name, glsl) ->

  vecType = util.glslVectorType(config.dimensions)
  matType = util.glslMatrixType(config.dimensions)

  vertex = """
  precision highp float;
  precision highp int;

  attribute vec4 position;
  varying vec2 vPosition;

  void main() {
    vPosition = position.xy;
    gl_Position = position;
  }
  """

  # TODO: Needs proper support for config.dimensions
  fragment = """
  precision highp float;
  precision highp int;

  uniform float xMin, xMax, yMin, yMax;

  uniform #{vecType} selectedDomainTranslate, selectedRangeTranslate;
  uniform #{matType} selectedDomainTransformInv, selectedRangeTransform;

  varying vec2 vPosition;

  #{glsl}

  float lerp(float x, float dMin, float dMax, float rMin, float rMax) {
    float ratio = (x - dMin) / (dMax - dMin);
    return ratio * (rMax - rMin) + rMin;
  }

  void main() {
    vec4 inputVal = vec4(
      lerp(vPosition.x, -1., 1., xMin, xMax),
      lerp(vPosition.y, -1., 1., yMin, yMax),
      0.,
      0.
    );
    vec4 outputVal = mainFn(inputVal);

    float value = outputVal.x;
    vec3 color;

    float normvalue = clamp(abs(value), 0., 1.);
    if (value > 0.) {
      color = mix(vec3(#{config.colorMapZero}), vec3(#{config.colorMapPositive}), normvalue);
    } else {
      color = mix(vec3(#{config.colorMapZero}), vec3(#{config.colorMapNegative}), normvalue);
    }

    //color = vec3(value, value, value);

    gl_FragColor = vec4(color, 1.);
  }
  """

  createProgramFromSrc(glod, name, vertex, fragment)

drawColorMapProgram = (glod, name, bounds) ->
  canvas = glod.canvas()

  glod.begin(name)

  glod.pack("quad", "position")

  glod.value("xMin", bounds.xMin)
  glod.value("xMax", bounds.xMax)
  glod.value("yMin", bounds.yMin)
  glod.value("yMax", bounds.yMax)
  addSelectedChildFnUniforms(glod)

  glod.ready().triangles().drawArrays(0, 6)

  glod.end()

addSelectedChildFnUniforms = (glod) ->
  selectedChildFn = UI.getSingleSelectedChildFn()
  return unless selectedChildFn
  domainTranslate = selectedChildFn.getDomainTranslate()
  domainTransformInv = packMatrix util.safeInv(selectedChildFn.getDomainTransform())
  rangeTranslate  = selectedChildFn.getRangeTranslate()
  rangeTransform  = packMatrix selectedChildFn.getRangeTransform()

  glod.valuev("selectedDomainTranslate", domainTranslate)
  glod.valuev("selectedDomainTransformInv", domainTransformInv)
  glod.valuev("selectedRangeTranslate", rangeTranslate)
  glod.valuev("selectedRangeTransform", rangeTransform)

packMatrix = (m) ->
  m = numeric.transpose(m)
  _.flatten(m)


text:
R.create "OutlineView",
  propTypes:
    definedFn: C.DefinedFn

  render: ->
    R.div {className: "Outline"},

      R.LabelView {
        fn: @definedFn
        className: "Header"
      }

      R.div {className: "Scroller"},

        R.OutlineCombinerToolbar {}

        R.OutlineChildrenView {
          compoundFn: @definedFn
        }

# =============================================================================

R.create "OutlineCombinerToolbar",
  render: ->
    R.div {className: "OutlineCombinerToolbar"},
      R.span {className: "OutlineCombinerButton", onClick: @_compose}, "Compose"
      R.span {className: "OutlineCombinerButton", onClick: @_add}, "Add"
      R.span {className: "OutlineCombinerButton", onClick: @_multiply}, "Multiply"
      R.span {className: "OutlineCombinerButton", onClick: @_min}, "Min"
      R.span {className: "OutlineCombinerButton", onClick: @_max}, "Max"
      # R.span {className: "OutlineCombinerButton", onClick: @_define}, "Define"

  shouldComponentUpdate: -> false

  _compose: -> Actions.addCompoundFn("composition")
  _add: -> Actions.addCompoundFn("sum")
  _multiply: -> Actions.addCompoundFn("product")
  _min: -> Actions.addCompoundFn("min")
  _max: -> Actions.addCompoundFn("max")
  _define: -> # TODO

# =============================================================================

R.create "OutlineChildrenView",
  propTypes:
    compoundFn: C.CompoundFn

  render: ->
    R.div {className: "OutlineChildren"},
      for childFn in @compoundFn.childFns
        R.OutlineItemView {
          childFn: childFn
          key: C.id(childFn)
        }

# =============================================================================

R.create "OutlineItemView",
  propTypes:
    childFn: C.ChildFn

  render: ->
    if !@isDraggingCopy and @childFn == UI.dragging?.childFn
      return R.div {className: "Placeholder", style: {height: UI.dragging.placeholderHeight}}

    canHaveChildren = @childFn.fn instanceof C.CompoundFn
    expanded = UI.isChildFnExpanded(@childFn)
    selected = _.contains(UI.selectedChildFns, @childFn)
    hovered = (@childFn == UI.hoveredChildFn)

    itemClassName = R.cx {
      OutlineItem: true
      Invisible: !@childFn.visible
    }

    rowClassName = R.cx {
      OutlineRow: true
      Selected: selected
      Hovered: hovered
    }

    disclosureClassName = R.cx {
      DisclosureTriangle: true
      Expanded: expanded
    }

    R.div {className: itemClassName},
      R.div {className: rowClassName, onMouseDown: @_onRowMouseDown, onMouseEnter: @_onRowMouseEnter, onMouseLeave: @_onRowMouseLeave},
        R.div {className: "OutlineVisible Interactive", onClick: @_onVisibleClick},
          R.div {className: "icon-eye"}

        if canHaveChildren
          R.div {className: "OutlineDisclosure Interactive", onClick: @_onDisclosureClick},
            R.div {className: disclosureClassName}

        R.OutlineThumbnailView {childFn: @childFn}

        R.OutlineInternalsView {fn: @childFn.fn}

      if canHaveChildren and expanded
        R.OutlineChildrenView {
          compoundFn: @childFn.fn
        }

  _onDisclosureClick: ->
    Actions.toggleChildFnExpanded(@childFn)

  _onVisibleClick: ->
    Actions.toggleChildFnVisible(@childFn)

  _onRowMouseDown: (e) ->
    return if e.target.closest(".Interactive, select, [contenteditable]")

    if key.command or key.shift
      if _.contains(UI.selectedChildFns, @childFn)
        util.onceDragConsummated e, null, =>
          Actions.toggleSelectChildFn(@childFn)
      else
        Actions.toggleSelectChildFn(@childFn)
    else
      Actions.selectChildFn(@childFn)

    util.preventDefault(e)

    el = @getDOMNode()
    rect = el.getMarginRect()
    myWidth = rect.width
    myHeight = rect.height
    offset = {
      x: e.clientX - rect.left
      y: e.clientY - rect.top
    }

    UI.dragging = {
      cursor: "-webkit-grabbing"
    }

    # TODO: This only handles what you started dragging. Instead it should
    # move the selection. Could use code cleanup too. childFn is the childFn
    # that's being moved.
    if key.command or key.shift
      # Duplicate
      childFn = @childFn.duplicate()
      # HACK: Should maybe copy the expanded state of the original
      Actions.setChildFnExpanded(childFn, false)
      parentCompoundFn = null
    else
      childFn = @childFn
      parentCompoundFn = @lookup("compoundFn")

    util.onceDragConsummated e, =>
      UI.dragging = {
        cursor: "-webkit-grabbing"
        offset: offset
        placeholderHeight: myHeight
        childFn: childFn
        render: =>
          R.div {style: {width: myWidth, height: myHeight, overflow: "hidden", "background-color": "#fff"}},
            R.OutlineItemView {childFn, isDraggingCopy: true}
        onMove: =>
          draggingPosition = {
            x: UI.mousePosition.x - offset.x
            y: UI.mousePosition.y - offset.y
          }

          # Hide placeholder for purposes of this calculation
          placeholderEl = document.querySelector(".Placeholder")
          placeholderEl?.style.display = "none"

          # Keep track of the best dropping opportunity
          bestQuadrance = 40*40
          bestDrop = null
          checkFit = (droppedPosition, outlineChildrenEl, index) ->
            dx = draggingPosition.x - droppedPosition.x
            dy = draggingPosition.y - droppedPosition.y
            quadrance = dx*dx + dy*dy
            if quadrance < bestQuadrance
              bestQuadrance = quadrance
              bestDrop = {outlineChildrenEl, index}

          outlineChildrenEls = document.querySelectorAll(".Outline .OutlineChildren")
          for outlineChildrenEl in outlineChildrenEls
            # Check before each existing child
            outlineItemEls = _.filter(outlineChildrenEl.childNodes, (el) -> el.classList.contains("OutlineItem"))
            for outlineItemEl, index in outlineItemEls
              rect = outlineItemEl.getBoundingClientRect()
              droppedPosition = {
                x: rect.left
                y: rect.top
              }
              checkFit(droppedPosition, outlineChildrenEl, index)
            # Check after the last child
            rect = outlineChildrenEl.getBoundingClientRect()
            droppedPosition = {
              x: rect.left
              y: rect.bottom
            }
            checkFit(droppedPosition, outlineChildrenEl, index)

          # Reshow placeholder
          placeholderEl?.style.display = ""

          # Remove self
          if parentCompoundFn
            Actions.removeChildFn(parentCompoundFn, childFn)
            parentCompoundFn = null

          # Add self
          if bestDrop
            parentCompoundFn = bestDrop.outlineChildrenEl.dataFor.compoundFn
            Actions.insertChildFn(parentCompoundFn, childFn, bestDrop.index)

      }

  _onRowMouseEnter: ->
    Actions.hoverChildFn(@childFn)

  _onRowMouseLeave: ->
    Actions.hoverChildFn(null)

# =============================================================================

R.create "OutlineInternalsView",
  propTypes:
    fn: C.Fn

  render: ->
    R.div {className: "OutlineInternals"},
      if @fn instanceof C.BuiltInFn
        R.LabelView {fn: @fn}

      else if @fn instanceof C.DefinedFn
        R.LabelView {fn: @fn}

      else if @fn instanceof C.CompoundFn
        R.CombinerView {compoundFn: @fn}

# =============================================================================

R.create "OutlineThumbnailView",
  propTypes:
    childFn: C.ChildFn

  render: ->
    plotLayout = UI.selectedFn.plotLayout # HACK
    R.div {className: "OutlineThumbnail"},
      R.ThumbnailPlotLayoutView {plotLayout, fn: @childFn}

# =============================================================================

R.create "LabelView",
  propTypes:
    fn: C.Fn

  render: ->
    className = R.cx {
      Label: true
      Interactive: !(@fn instanceof C.BuiltInFn)
    }
    className += " " + @className if @className
    if @fn instanceof C.BuiltInFn
      R.div {className: className}, @fn.label
    else
      R.TextFieldView {
        className: className
        value: @fn.label
        onInput: @_onInput
      }

  _onInput: (newValue) ->
    Actions.setFnLabel(@fn, newValue)

# =============================================================================

R.create "CombinerView",
  propTypes:
    compoundFn: C.CompoundFn

  render: ->
    R.select {value: @compoundFn.combiner, onChange: @_onChange},
      R.option {value: "sum"}, "Add"
      R.option {value: "product"}, "Multiply"
      R.option {value: "composition"}, "Compose"
      R.option {value: "min"}, "Min"
      R.option {value: "max"}, "Max"

  _onChange: (e) ->
    value = e.target.selectedOptions[0].value
    Actions.setCompoundFnCombiner(@compoundFn, value)


text:
R.create "InspectorView",
  render: ->
    R.div {className: "Inspector"},
      R.div {className: "Header"}, "Inspector"
      R.div {className: "Scroller"},
        if UI.selectedChildFns.length == 1
          R.InspectorTableView {fn: UI.selectedChildFns[0]}

R.create "InspectorTableView",
  propTypes:
    fn: C.ChildFn

  render: ->
    # HACK
    dimensions = if UI.selectedFn.plotLayout.display2d then 2 else 1

    R.table {},
      R.tbody {},

        R.tr {style: {color: config.domainLabelColor}},
          R.th {}
          for coordIndex in [0...dimensions]
            R.th {key: coordIndex}, "x"+(coordIndex+1)

        R.tr {className: "Translate"},
          R.td {className: "icon-move"}
          for coordIndex in [0...dimensions]
            variable = @fn.domainTranslate[coordIndex]
            R.td {key: C.id(variable)},
              R.VariableView {variable}

        for coordIndex in [0...dimensions]
          className = R.cx {
            "icon-resize-full-alt": coordIndex == 0
          }
          R.tr {key: coordIndex},
            R.td {className}
            for rowIndex in [0...dimensions]
              variable = @fn.domainTransform[rowIndex][coordIndex]
              R.td {key: C.id(variable)},
                R.VariableView {variable}

        R.tr {style: {color: config.rangeLabelColor}},
          R.th {}
          for coordIndex in [0...dimensions]
            R.th {key: coordIndex}, "y"+(coordIndex+1)

        R.tr {className: "Translate"},
          R.td {className: "icon-move"}
          for coordIndex in [0...dimensions]
            variable = @fn.rangeTranslate[coordIndex]
            R.td {key: C.id(variable)},
              R.VariableView {variable}

        for coordIndex in [0...dimensions]
          className = R.cx {
            "icon-resize-full-alt": coordIndex == 0
          }
          R.tr {key: coordIndex},
            R.td {className}
            for rowIndex in [0...dimensions]
              variable = @fn.rangeTransform[rowIndex][coordIndex]
              R.td {key: C.id(variable)},
                R.VariableView {variable}


text:
window.R = R = {}

# =============================================================================
# Provide easy access to React.DOM and React.addons.classSet
# =============================================================================

for own key, value of React.DOM
  R[key] = value

R.cx = React.addons.classSet

# =============================================================================
# UniversalMixin gets mixed in to every component
# =============================================================================

R.UniversalMixin = {
  # Extra traversal powers
  ownerView: ->
    @_owner ? @props.__owner__ # undocumented React property

  lookup: (keyName) ->
    return this[keyName] ? @ownerView()?.lookup(keyName)

  lookupView: (viewName) ->
    return this if this == viewName or @viewName() == viewName
    return @ownerView()?.lookupView(viewName)

  lookupViewWithKey: (keyName) ->
    return this if this[keyName]?
    return @ownerView()?.lookupViewWithKey(keyName)

  # Move props to be actual properties on the view
  setPropsOnSelf: (nextProps) ->
    for own propName, propValue of nextProps
      continue if propName == "__owner__"
      this[propName] = propValue

  componentWillMount: ->
    @setPropsOnSelf(@props)

  componentWillUpdate: (nextProps) ->
    @setPropsOnSelf(nextProps)

  # Annotate the created DOM Node
  componentDidMount: ->
    el = @getDOMNode()
    el.dataFor ?= this

  componentWillUnmount: ->
    el = @getDOMNode()
    delete el.dataFor
}

# =============================================================================
# Extra stuff on React.create
# =============================================================================

desugarPropType = (propType, optional=false) ->
  if propType.optional
    propType = propType.optional
    required = false
  else if optional
    required = false
  else
    required = true

  if propType == Number
    propType = React.PropTypes.number
  else if propType == String
    propType = React.PropTypes.string
  else if propType == Boolean
    propType = React.PropTypes.bool
  else if propType == Function
    propType = React.PropTypes.func
  else if propType == Array
    propType = React.PropTypes.array
  else if propType == Object
    propType = React.PropTypes.object
  else if _.isArray(propType)
    # TODO: can't get this to work for some reason
    propType = React.PropTypes.any
    # childPropTypes = propType.map (childPropType) ->
    #   desugarPropType(childPropType, true)
    # propType = React.PropTypes.oneOfType(childPropTypes)
  else
    propType = React.PropTypes.instanceOf(propType)

  if required
    propType = propType.isRequired

  return propType

R.create = (name, opts) ->
  # add name stuff
  opts.displayName = name
  opts.viewName = -> name

  # desugar propTypes
  opts.propTypes ?= {}
  for own propName, propType of opts.propTypes
    opts.propTypes[propName] = desugarPropType(propType)

  # add the universal mixin
  opts.mixins ?= []
  opts.mixins.unshift(R.UniversalMixin)

  # create and register it
  R[name] = React.createClass(opts)

# =============================================================================
# Include all the view code
# =============================================================================

require("./ui/TextFieldView")

require("./AppRootView")

require("./ShaderOverlayView")

require("./PaletteView")
require("./PlotLayoutView")
require("./ThumbnailPlotLayoutView")
require("./OutlineView")
require("./InspectorView")
require("./VariableView")
require("./SymbolicView")

require("./plot/GridView")
require("./plot/ShaderCartesianView")


text:
R.create "ThumbnailPlotLayoutView",
  propTypes:
    plotLayout: C.PlotLayout
    fn: C.Fn

  render: ->
    plot = @plotLayout.getMainPlot()

    R.div {className: "PlotContainer"},
      R.GridView {plot: plot, isThumbnail: true}

      R.ShaderCartesianView {
        plot: plot
        isThumbnail: true
        fns: [
          {
            fn: @fn
            color: config.color.main
          }
        ]
      }


text:
R.create "AppRootView",
  propTypes:
    appRoot: C.AppRoot

  refreshShaderOverlay: ->
    @refs.shaderOverlay.draw()

  componentDidMount: ->
    @refreshShaderOverlay()

  componentDidUpdate: ->
    @refreshShaderOverlay()

  render: ->
    R.div {},
      R.PlotLayoutView {fn: UI.selectedFn}
      R.PaletteView {appRoot: @appRoot}
      R.OutlineView {definedFn: UI.selectedFn}
      R.InspectorView {}
      R.SymbolicView {}
      # R.DebugView {}
      R.DraggingView {}
      R.ShaderOverlayView {ref: "shaderOverlay"}

R.create "DraggingView",
  render: ->
    R.div {},
      if UI.dragging?.render
        R.div {
          className: "DraggingObject"
          style: {
            left: UI.mousePosition.x - UI.dragging.offset.x
            top:  UI.mousePosition.y - UI.dragging.offset.y
          }
        },
          UI.dragging.render()
      if UI.dragging
        R.div {className: "DraggingOverlay"}

R.create "DebugView",
  render: ->
    R.div {
      style: {
        position: "absolute"
        bottom: 10
        left: 10
        zIndex: 99999
      }
    },
      R.button {onClick: reset}, "Reset"


text:
'use strict';

module.exports = Glod;

function GlodError(message, data) {
  this.message = message;
  this.data = data;
}
GlodError.prototype = Object.create(Error.prototype);

function die(message, data) {
  var error = new GlodError(message || 'Glod: die', data);

  // try {
  //   throw new Error(message || 'die');
  // }
  // catch(err) {
  //   error = err;
  // }

  // var line = error.stack.split('\n')[3];
  // var at = line.indexOf('at ');
  // var origin = line.slice(at + 3, line.length);

  // error.name = 'at ' + origin;

  throw error;
}

function Glod() {
  // Prevent instantiation without new.
  if (!Glod.prototype.isPrototypeOf(this)) {
    die('Glod: instantiate with `new Glod()`')
  }

  this._canvas            = null;
  this._gl                = null;
  this._vbos              = {};
  this._fbos              = {};
  this._rbos              = {};
  this._programs          = {};
  this._textures          = {};
  this._extensions        = {};

  this._variables         = {};

  this._mode              = -1;
  this._activeProgram     = null;
  this._contextLost       = false;
  this._onContextLost     = this.onContextLost.bind(this);
  this._onContextRestored = this.onContextRestored.bind(this);
  this.loseContext        = null;
  this.restoreContext     = null;
  this._initIds           = {};
  this._allocIds          = {};
  this._versionedIds      = {};

  this._optional  = {};
  this._optionalv = {};

  this._state = 0;
}

Glod.preprocessed = {};

// this should probably be called "cache shader" or something like that
Glod.preprocess = function(source) {
  var line_re      = /\n|\r/;
  var directive_re = /^\/\/!\s*(.*)$/;

  var vertex   = [];
  var fragment = [];

  var lines = source.split(line_re);

  var name = null;

  var section = "common";

  for (var i = 0; i < lines.length; i++) {
    var line  = lines[i];
    var match = directive_re.exec(line);

    if (match) {
      var tokens = match[1].split(/\s+/);

      switch(tokens[0]) {
        case "name":     name    = tokens[1];  break;
        case "common":   section = "common";   break;
        case "vertex":   section = "vertex";   break;
        case "fragment": section = "fragment"; break;
        default: die('gl.preprocess: bad directive: ' + tokens[0]);
      }
    }

    switch(section) {
      case "common":   vertex.push(line); fragment.push(line); break;
      case "vertex":   vertex.push(line); fragment.push(''  ); break;
      case "fragment": vertex.push(''  ); fragment.push(line); break;
    }
  }

  var fragment_src = fragment.join('\n');
  var vertex_src   = vertex  .join('\n');

  name         || die('gl.preprocess: no name');
  vertex_src   || die('gl.preprocess: no vertex source: ' + name);
  fragment_src || die('gl.preprocess: no fragment source: ' + name);

  var o = {
    name:     name,
    vertex:   vertex_src,
    fragment: fragment_src
  };

  Glod.preprocessed[o.name] && die('Glod: duplicate shader name: '+ o.name);
  Glod.preprocessed[o.name] = o;
};

Glod.prototype.isInactive      = function() { return this._state === 0;     };
Glod.prototype.isPreparing     = function() { return this._state === 1;     };
Glod.prototype.isDrawing       = function() { return this._state === 2;     };
Glod.prototype.isProgramActive = function() { return !!this._activeProgram; };

Glod.prototype.startInactive  = function() { this._state = 0; return this; };
Glod.prototype.startPreparing = function() { this._state = 1; return this; };
Glod.prototype.startDrawing   = function() { this._state = 2; return this; };

Glod.prototype.assertInactive      = function() { this.isInactive()      || this.outOfPhase(0); return this; };
Glod.prototype.assertPreparing     = function() { this.isPreparing()     || this.outOfPhase(1); return this; };
Glod.prototype.assertDrawing       = function() { this.isDrawing()       || this.outOfPhase(2); return this; };
Glod.prototype.assertProgramActive = function() { this.isProgramActive() || this.outOfPhase(1); return this; };

Glod.prototype.outOfPhase = function(expected, actual) {
  function s(n) {
    return n === 0 ? 'inactive'  :
           n === 1 ? 'preparing' :
           n === 2 ? 'drawing'   :
                     'unknown (' + n + ')';
  }

  die('Glod: out of phase: expected to be ' + s(expected) + ' but was ' + s(this._state));
};

// todo: print string names and type instead of [object WebGLProgram]
// function throwOnGLError(err, funcName, args) {
//   throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName;
// }

// function validateNoneOfTheArgsAreUndefined(functionName, args) {
//   for (var ii = 0; ii < args.length; ++ii) {
//     if (args[ii] === undefined) {
//       console.error("undefined passed to gl." + functionName + "(" +
//                     WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
//     }
//   }
// }

// function logGLCall(functionName, args) {
//   console.log("gl." + functionName + "(" +
//       WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
// }

// function logAndValidate(functionName, args) {
//   logGLCall(functionName, args);
//   validateNoneOfTheArgsAreUndefined (functionName, args);
// }

Glod.prototype.initContext = function() {
  var gl = this._gl;

  var supported = gl.getSupportedExtensions();

  for (var i = 0; i < supported.length; i++) {
    var name = supported[i];
    this._extensions[name] = gl.getExtension(name);
  }

  var lc = this.extension('WEBGL_lose_context');

  this.loseContext    = lc.loseContext.bind(lc);
  this.restoreContext = lc.restoreContext.bind(lc);
};

Glod.prototype.gl = function() {
  this._gl || die('Glod.gl: no gl context');
  return this._gl;
};

Glod.prototype.extension = function() {
  var l = arguments.length;
  for (var i = 0; i < l; i++) {
    var e = this._extensions[arguments[i]];
    if (e) return e;
  }
  die('Glod.extension: extension not found: ' + arguments);
};

Glod.prototype.canvas = function(canvas, options) {
  if (arguments.length === 0) {
    this.hasCanvas() || die('Glod.canvas: no canvas');
    return this._canvas;
  }

  if (this.hasCanvas()) {
    this._canvas.removeEventListener('webglcontextlost', this._onContextLost);
    this._canvas.removeEventListener('webglcontextrestored', this._onContextRestored);
  }

  this._canvas = canvas || null;

  if (canvas && !this.hasCanvas()) {
    die('Glod.canvas: bad canvas: ' + canvas);
  }

  if (this.hasCanvas()) {
    this._canvas.addEventListener('webglcontextlost', this._onContextLost);
    this._canvas.addEventListener('webglcontextrestored', this._onContextRestored);
    var opts = options || { antialias: false };
    var gl = this._canvas.getContext('webgl', options);
    gl || (gl = this._canvas.getContext('experimental-webgl', opts));
    gl || (die('Glod.canvas: failed to create context'));
    // wrap && (gl = WebGLDebugUtils.makeDebugContext(gl, throwOnGLError, logAndValidate));
    this._gl = gl;
    this.initContext();
  }
  else {
    this._gl = null;
  }

  return this;
};

Glod.prototype.hasCanvas = function() {
  return !!(this._canvas); // && this._canvas.length == 1);
};

Glod.prototype.hasVBO     = function(name) { return this._vbos    .hasOwnProperty(name); };
Glod.prototype.hasFBO     = function(name) { return this._fbos    .hasOwnProperty(name); };
Glod.prototype.hasRBO     = function(name) { return this._rbos    .hasOwnProperty(name); };
Glod.prototype.hasTexture = function(name) { return this._textures.hasOwnProperty(name); };
Glod.prototype.hasProgram = function(name) { return this._programs.hasOwnProperty(name); };

Glod.prototype.createVBO = function(name) {
  this.hasVBO(name) && die('Glod.createVBO: duplicate name: ' + name);
  this._vbos[name] = this.gl().createBuffer();
  return this;
};

Glod.prototype.createFBO = function(name) {
  this.hasFBO(name) && die('Glod.createFBO: duplicate resource name: ' + name);
  this._fbos[name] = this.gl().createFramebuffer();
  return this;
};

Glod.prototype.createRBO = function(name) {
  this.hasRBO(name) && die('Glod.createRBO: duplicate resource name: ' + name);
  this._rbos[name] = this.gl().createRenderbuffer();
  return this;
};

Glod.prototype.createTexture = function(name) {
  this.hasTexture(name) && die('Glod.createTexture: duplicate resource name: ' + name);
  this._textures[name] = this.gl().createTexture();
  return this;
};

Glod.prototype.deleteVBO = function(name) {
  var vbo = this.vbo(name);
  this.gl().deleteBuffer(vbo);
  delete this._vbos[name];
  return this;
};

var NRF = function(type, name) {
  die('Glod.' + type + ': no resource found: ' + name);
};

Glod.prototype.vbo     = function(name) { this.hasVBO(name) || NRF('vbo', name); return this._vbos[name]; };
Glod.prototype.fbo     = function(name) { this.hasFBO(name) || NRF('fbo', name); return this._fbos[name]; };
Glod.prototype.rbo     = function(name) { this.hasRBO(name) || NRF('rbo', name); return this._rbos[name]; };

Glod.prototype.program = function(name) {
  this.hasProgram(name) || NRF('program', name); return this._programs[name];
};

Glod.prototype.texture = function(name) {
  this.hasTexture(name) || NRF('texture', name); return this._textures[name];
};

Glod.prototype.onContextLost = function(e) {
  e.preventDefault();
  this._contextLost = true;
};

Glod.prototype.onContextRestored = function(e) {
  this._contextLost = false;

  var name;
  for (name in this._vbos    ) { delete this._vbos    [name]; this.createVBO    (name); }
  for (name in this._fbos    ) { delete this._fbos    [name]; this.createFBO    (name); }
  for (name in this._rbos    ) { delete this._rbos    [name]; this.createRBO    (name); }
  for (name in this._textures) { delete this._textures[name]; this.createTexture(name); }
  for (name in this._programs) { delete this._programs[name]; this.createProgram(name); }

  this.initContext();
  this._allocIds     = {};
  this._versionedIds = {};
};

Glod.prototype.createProgram = function(name) {
  name || die('bad program name: ' + name);

  var o = Glod.preprocessed[name];

  o          || die('Glod.createProgram: program not preprocessed: ' + name);
  o.name     || die('Glod.createProgram: no name specified');
  o.vertex   || die('Glod.createProgram: no vertex source');
  o.fragment || die('Glod.createProgram: no fragment source');

  name             = o.name;
  var vertex_src   = o.vertex;
  var fragment_src = o.fragment;

  this.hasProgram(name) && die('Glod.createProgram: duplicate program name: ' + name);

  var gl = this.gl();
  var program = gl.createProgram();
  this._programs[name] = program;

  function shader(type, source) {
    var s = gl.createShader(type);

    gl.shaderSource(s, source);
    gl.compileShader(s);

    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
      var log = gl.getShaderInfoLog(s);
      console.log(log);
      console.log(source);
      die('Glod.createProgram: compilation failed', log);
    }

    gl.attachShader(program, s);
  }

  shader(gl.VERTEX_SHADER,   vertex_src);
  shader(gl.FRAGMENT_SHADER, fragment_src);

  for (var pass = 0; pass < 2; pass++) {
    gl.linkProgram(program);

    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      console.log(gl.getProgramInfoLog(program));
      die('Glod.createProgram: linking failed');
    }

    gl.validateProgram(program);
    if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
      console.log(gl.getProgramInfoLog(program));
      die('Glod.createProgram: validation failed');
    }

    if (pass === 0) {
      var active = [];

      var activeAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
      for (var i = 0; i < activeAttributes; i++) {
        var info = gl.getActiveAttrib(program, i);
        var re = new RegExp('^\\s*attribute\\s+([a-z0-9A-Z_]+)\\s+' + info.name + '\\s*;', 'm');
        var sourcePosition = vertex_src.search(re);
        sourcePosition >= 0 || die('couldn\'t find active attribute "' + info.name + '" in source');
        active.push([info.name, sourcePosition]);
      }

      var layout = active.sort(function(a, b) { return a[1] > b[1]; })
                         .map (function(x   ) { return x[0];        });

      for (var i = 0; i < layout.length; i++) {
        gl.bindAttribLocation(program, i, layout[i]);
      }

      continue;
    }

    var variables = this._variables[name] = {};

    var addVariable = function(index, attrib) {
      var info = attrib ? gl.getActiveAttrib (program, i) :
                          gl.getActiveUniform(program, i);

      var name = info.name;

      variables[name] && die('Glod: duplicate variable name: ' + name);

      var location = attrib ? gl.getAttribLocation (program, name) :
                              gl.getUniformLocation(program, name) ;

      var type = info.type;

      var count = type === gl.BYTE           ? 1  :
                  type === gl.UNSIGNED_BYTE  ? 1  :
                  type === gl.SHORT          ? 1  :
                  type === gl.UNSIGNED_SHORT ? 1  :
                  type === gl.INT            ? 1  :
                  type === gl.UNSIGNED_INT   ? 1  :
                  type === gl.FLOAT          ? 1  :
                  type === gl.BOOL           ? 1  :
                  type === gl.SAMPLER_2D     ? 1  :
                  type === gl.SAMPLER_CUBE   ? 1  :

                  type === gl.  INT_VEC2     ? 2  :
                  type === gl.FLOAT_VEC2     ? 2  :
                  type === gl. BOOL_VEC2     ? 2  :

                  type === gl. INT_VEC3      ? 3  :
                  type === gl.FLOAT_VEC3     ? 3  :
                  type === gl. BOOL_VEC3     ? 3  :

                  type === gl.  INT_VEC4     ? 4  :
                  type === gl.FLOAT_VEC4     ? 4  :
                  type === gl. BOOL_VEC4     ? 4  :

                  type === gl.FLOAT_MAT2     ? 4  :
                  type === gl.FLOAT_MAT3     ? 9  :
                  type === gl.FLOAT_MAT4     ? 16 :
                  die('Glod: unknown variable type: ' + type);

      var matrix = type === gl.FLOAT_MAT2 || type === gl.FLOAT_MAT3 || type === gl.FLOAT_MAT4;

      var float = type === gl.FLOAT      ||
                  type === gl.FLOAT_VEC2 || type === gl.FLOAT_VEC3 || type === gl.FLOAT_VEC4 ||
                  type === gl.FLOAT_MAT2 || type === gl.FLOAT_MAT3 || type === gl.FLOAT_MAT4;

      variables[name] = {
        location: location,
        info:     info,
        attrib:   attrib,
        count:    count,
        float:    float,
        matrix:   matrix,
        ready:    false
      };
    }

    var activeUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
    for (var i = 0; i < activeUniforms; i++) addVariable(i, false);
    var activeAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
    for (var i = 0; i < activeAttributes; i++) addVariable(i, true);
  }

  var error = this.gl().getError();
  if (error !== 0) die('unexpected error: ' + error);

  return this;
};

Glod.prototype.variable = function(name) {
  this.assertProgramActive()
  var variable = this._variables[this._activeProgram][name];
  // TODO(ryan): Maybe add a flag for this? It can be useful to know when
  // variables are unused, but also annoying if you want to set them regardless.
  // variable || die('Glod.variable: variable not found: ' + name);
  return variable;
};

Glod.prototype.location = function(name) { return this.variable(name).location; };
Glod.prototype.info     = function(name) { return this.variable(name).info;     };
Glod.prototype.isAttrib = function(name) { return this.variable(name).attrib;   };

Glod.prototype.uploadCCWQuad = function() {
  var positions = new Float32Array([1, -1, 0, 1, 1, 1, 0, 1, -1, 1, 0, 1, -1, 1, 0, 1, -1, -1, 0, 1, 1, -1, 0, 1]);

  return function(name) {
    var gl = this.gl();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo(name));
    gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
    return this;
  };
}();

Glod.prototype.uploadPlaceholderTexture = function() {
  var rgba = new Uint8Array([255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 255]);

  return function(name) {
    var gl  = this.gl();
    var tex = this.texture(name);

    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.bindTexture(gl.TEXTURE_2D, null);

    return this;
  };
}();

Glod.prototype.bindFramebuffer = function(name) {
  var fbo = name === null ? null : this.fbo(name);
  var gl = this.gl();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  return this;
};

// todo:
//   use the vbo's type to determine which target to bind it to
//   support stream and dynamic draw
//   support passing a normal JS array
Glod.prototype.bufferDataStatic = function(targetName) {
  var al  = arguments.length;
  var gl  = this.gl();
  var vbo = this.vbo(targetName);

  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);

  var a;
  if (al === 2) {
    a = arguments[1];
    Array.prototype.isPrototypeOf(a) && (a = new Float32Array(a));
    gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW);
  }
  else if (al === 3) {
    a = arguments[1];
    Array.prototype.isPrototypeOf(a) && (a = new Float32Array(a));
    gl.bufferSubData(gl.ARRAY_BUFFER, a, arguments[2]);
  }
  else {
    die('Glod.bufferData: bad argument count: ' + al);
  }

  return this;
};

// todo:
//   support aperture base and opening
//   support scale factor
Glod.prototype.viewport = function() {
  var gl = this.gl();
  var x, y, w, h;

  var al = arguments.length;
  if (al === 4) {
    x = arguments[0];
    y = arguments[1];
    w = arguments[2];
    h = arguments[3];
  }
  else if (al === 0) {
    var canvas = this.canvas();
    x = y = 0;
    w = canvas.width;
    h = canvas.height;
  }
  else {
    die('Glod.viewport: bad argument count: ' + al);
  }

  gl.viewport(x, y, w, h);
  gl.scissor(x, y, w, h);

  return this;
}

Glod.prototype.begin = function(programName) {
  this.assertInactive().startPreparing();

  this.gl().useProgram(this.program(programName));

  this._activeProgram = programName;
  this._mode = -1;

  var variables = this._variables[programName];

  for (var name in variables) {
    variables[name].ready = false;
  }

  return this;
};

Glod.prototype.ready = function() {
  this.assertPreparing().startDrawing();

  var variables = this._variables[this._activeProgram];

  for (var name in variables) {
    var ov = this._optional[name];
    if (!variables[name].ready && ov) {
      switch(ov.length) {
        case 4: this.value(name, ov[0], ov[1], ov[2], ov[3]); break;
        case 3: this.value(name, ov[0], ov[1], ov[2]       ); break;
        case 2: this.value(name, ov[0], ov[1]              ); break;
        case 1: this.value(name, ov[0]                     ); break;
      }
    }

    variables[name].ready || die('Glod.ready: variable not ready: ' + name);
  }

  return this;
};

Glod.prototype.end = function() {
  this.assertDrawing().startInactive();
  this._activeProgram = null;
  return this;
};

Glod.prototype.manual = function() {
  this.assertProgramActive();
  for (var i = 0; i < arguments.length; i++) {
    this.variable(arguments[i]).ready = true;
  }
  return this;
};

Glod.prototype.value = function(name, a, b, c, d) {
  var v  = this.variable(name);

  // Bail if the variable does not exist.
  if (!v) return this;

  var gl = this.gl();
  var l  = arguments.length - 1;
  var loc = v.location;

  if (v.attrib) {
    l === 1 ? gl.vertexAttrib1f(loc, a         ) :
    l === 2 ? gl.vertexAttrib2f(loc, a, b      ) :
    l === 3 ? gl.vertexAttrib3f(loc, a, b, c   ) :
    l === 4 ? gl.vertexAttrib4f(loc, a, b, c, d) :
              die('Glod.value: bad length: ' + l);
  }
  else {
    var type = v.info.type;
    l === 1 ? (v.float ? gl.uniform1f(loc, a         ) : gl.uniform1i(loc, a         )) :
    l === 2 ? (v.float ? gl.uniform2f(loc, a, b      ) : gl.uniform2i(loc, a, b      )) :
    l === 3 ? (v.float ? gl.uniform3f(loc, a, b, c   ) : gl.uniform3i(loc, a, b, c   )) :
    l === 4 ? (v.float ? gl.uniform4f(loc, a, b, c, d) : gl.uniform4i(loc, a, b, c, d)) :
              die('Glod.value: bad length: ' + l);
  }
  v.ready = true;
  return this;
};

Glod.prototype.valuev = function(name, s, transpose) {
  s || die('Glod.valuev: bad vector: ' + s);

  var v = this.variable(name);

  // Bail if the variable does not exist.
  if (!v) return this;

  var gl = this.gl();
  var l = v.count;
  var loc = v.location;

  if (v.attrib) {
    l === s.length || die('Glod.valuev: bad vector length: ' + s.length);
    gl.disableVertexAttribArray(loc);
    l === 1 ? gl.vertexAttrib1fv(loc, s) :
    l === 2 ? gl.vertexAttrib2fv(loc, s) :
    l === 3 ? gl.vertexAttrib3fv(loc, s) :
    l === 4 ? gl.vertexAttrib4fv(loc, s) :
              die('Glod.valuev: bad length: ' + l);
  }
  else {
    if (v.matrix) {
      l === 4  ? gl.uniformMatrix2fv(loc, !!transpose, s) :
      l === 9  ? gl.uniformMatrix3fv(loc, !!transpose, s) :
      l === 16 ? gl.uniformMatrix4fv(loc, !!transpose, s) :
                 die('Glod.valuev: bad length: ' + l);
    }
    else {
      l === 1 ? (v.float ? gl.uniform1fv(loc, s) : gl.uniform1iv(loc, s)) :
      l === 2 ? (v.float ? gl.uniform2fv(loc, s) : gl.uniform2iv(loc, s)) :
      l === 3 ? (v.float ? gl.uniform3fv(loc, s) : gl.uniform3iv(loc, s)) :
      l === 4 ? (v.float ? gl.uniform4fv(loc, s) : gl.uniform4iv(loc, s)) :
                die('Glod.valuev: bad length: ' + l);
    }
  }

  v.ready = true;

  return this;
};

Glod.prototype.optional = function(name, a, b, c, d) {
  var l = arguments.length - 1;

  if (l === 1 && a === undefined) {
    delete this._optional[name];
    return this;
  }

  var v = this._optional[name] || [];
  this._optional[name] = v;
  v.length = l;

  switch (l) {
    case 4: v[3] = d;
    case 3: v[2] = c;
    case 2: v[1] = b;
    case 1: v[0] = a;
  }

  return this;
};

Glod.prototype.optionalv = function(name, s, transpose) {
  // WARNING: I'm not sure this actually works.
  if (arguments.length === 2 && s === undefined) {
    delete this._optionalv[name];
    return this;
  }

  var v = this._optionalv[name] || [];
  var l = s.length;
  this._optionalv[name] = v;
  v.length = s.length;
  v.TRANSPOSE = !!transpose;
  for (var i = 0; i < l; i++) {
    v[i] = s[i];
  }

  return this;
};

Glod.prototype.pack = function(vboName) {
  var vbo = this.vbo(vboName);
  var gl  = this.gl();

  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);

  arguments.length < 2 && die('Glod.pack: no attribute provided');

  var stride = 0;
  var counts = [];
  var vars = [];
  for (var i = 1; i < arguments.length; i++) {
    var name = arguments[i];
    var v = this.variable(name);
    v.attrib || die('Glod.pack: tried to pack uniform: ' + name);
    v.ready  && die('Glod.pack: variable already ready: ' + name);
    var count = v.count;
    stride += count;
    counts.push(count);
    vars.push(v);
  }

  var offset = 0;
  for (var i = 1; i < arguments.length; i++) {
    var name = arguments[i];
    var v = vars[i - 1];
    var loc = v.location;
    gl.enableVertexAttribArray(loc);
    gl.vertexAttribPointer(loc, v.count, gl.FLOAT, false, stride * 4, offset * 4);
    offset += v.count;
    v.ready = true;
  }

  return this;
};

Glod.prototype.primitive = function(mode) {
  (mode >= 0 && mode <= 6) || die('Glod.mode: bad mode: ' + mode);
  this._mode = mode
  return this;
};

Glod.prototype.points        = function() { this._mode = this._gl.POINTS;         return this; };
Glod.prototype.lines         = function() { this._mode = this._gl.LINES;          return this; };
Glod.prototype.lineLoop      = function() { this._mode = this._gl.LINE_LOOP;      return this; };
Glod.prototype.lineStrip     = function() { this._mode = this._gl.LINE_STRIP;     return this; };
Glod.prototype.triangles     = function() { this._mode = this._gl.TRIANGLES;      return this; };
Glod.prototype.triangleStrip = function() { this._mode = this._gl.TRIANGLE_STRIP; return this; };
Glod.prototype.triangleFan   = function() { this._mode = this._gl.TRIANGLE_FAN;   return this; };

Glod.prototype.drawArrays = function(first, count) {
  var mode = this._mode;
  (mode >= 0 && mode <= 6) || die('Glod.drawArrays: mode not set');
  var gl = this.gl();
  gl.drawArrays(mode, first, count);
  return this;
};

Glod.prototype.clearColor   = function(r, g, b, a) { this.gl().clearColor  (r, g, b, a); return this; };
Glod.prototype.clearDepth   = function(d         ) { this.gl().clearDepth  (d         ); return this; };
Glod.prototype.clearStencil = function(s         ) { this.gl().clearStencil(s         ); return this; };

Glod.prototype.clearColorv = function(s) {
  return this.clearColor(s[0], s[1], s[2], s[3]);
};

Glod.prototype.clear = function(color, depth, stencil) {
  var gl = this.gl();

  var clearBits = 0;
  color   && (clearBits |= gl.  COLOR_BUFFER_BIT);
  depth   && (clearBits |= gl.  DEPTH_BUFFER_BIT);
  stencil && (clearBits |= gl.STENCIL_BUFFER_BIT);

  clearBits && gl.clear(clearBits);
  return this;
};

Glod.prototype.bindArrayBuffer = function(name) {
  var gl = this._gl;
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo(name));
  return this;
};

Glod.prototype.bindElementBuffer = function(name) {
  var gl = this._gl;
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.vbo(name));
  return this;
};

Glod.prototype.bindTexture2D = function(name) {
  var texture = name === null ? null : this.texture(name);
  var gl = this._gl;
  gl.bindTexture(gl.TEXTURE_2D, texture);
  return this;
};

Glod.prototype.activeTexture = function(unit) {
  var gl = this._gl;
  gl.activeTexture(gl.TEXTURE0 + unit);
  return this;
};

Glod.prototype.init = function(id, f) {
  this._initIds[id] || f();
  this._initIds[id] = true;
  return this;
};

Glod.prototype.alloc = function(id, f) {
  this._allocIds[id] || f();
  this._allocIds[id] = true;
  return this;
};

Glod.prototype.allocv = function(id, v, f) {
  if (this._versionedIds[id] !== v) {
    this._versionedIds[id] = v;
    f();
  }
  return this;
};


text:
R.create "ShaderCartesianView",
  propTypes:
    plot: C.Plot
    fns: Array # [{fn, color}]
    isThumbnail: Boolean

  render: ->
    R.div {className: "Shader"}


text:
R.create "GridView",
  propTypes:
    plot: C.Plot
    isThumbnail: Boolean

  _getParams: ->
    params = {}

    canvas = @getDOMNode()

    if @isThumbnail
      scaleFactor = window.innerHeight / canvas.height
    else
      scaleFactor = 1

    minWorld = @plot.toWorld({x: -canvas.width * scaleFactor / 2, y: -canvas.height * scaleFactor / 2})
    maxWorld = @plot.toWorld({x:  canvas.width * scaleFactor / 2, y:  canvas.height * scaleFactor / 2})

    dimensions = @plot.getDimensions()
    params.xMin = numeric.dot(minWorld, dimensions[0])
    params.yMin = numeric.dot(minWorld, dimensions[1])
    params.xMax = numeric.dot(maxWorld, dimensions[0])
    params.yMax = numeric.dot(maxWorld, dimensions[1])

    params.pixelSize = @plot.getPixelSize() * scaleFactor

    # Hacky way to see if we should draw axis labels
    if scaleFactor == 1
      xCoord = dimensions[0].indexOf(1)
      if xCoord < config.dimensions
        params.xLabelColor = config.domainLabelColor
        params.xLabel = "x" + (xCoord+1)
      else
        params.xLabelColor = config.rangeLabelColor
        params.xLabel = "y" + (xCoord+1 - config.dimensions)

      yCoord = dimensions[1].indexOf(1)
      if yCoord < config.dimensions
        params.yLabelColor = config.domainLabelColor
        params.yLabel = "x" + (yCoord+1)
      else
        params.yLabelColor = config.rangeLabelColor
        params.yLabel = "y" + (yCoord+1 - config.dimensions)

    return params

  _draw: ->
    didResize = @_ensureProperSize()
    params = @_getParams()

    if !didResize and _.isEqual(params, @_lastParams)
      return

    @_lastParams = params

    canvas = @getDOMNode()

    ctx = canvas.getContext("2d")

    util.canvas.clear(ctx)

    util.canvas.drawGrid ctx, params

  _ensureProperSize: ->
    # Returns true if we had to resize the canvas.
    canvas = @getDOMNode()
    rect = canvas.getBoundingClientRect()
    if canvas.width != rect.width or canvas.height != rect.height
      canvas.width = rect.width
      canvas.height = rect.height
      return true
    return false

  componentDidMount: ->
    @_draw()

  componentDidUpdate: ->
    @_draw()

  render: ->
    R.canvas {}


text:
R.create "TextFieldView",
  propTypes: {
    value: String
    className: String
    onInput: Function
    onBackSpace: Function
    onFocus: Function
    onBlur: Function
    allowEnter: Boolean
  }
  getDefaultProps: ->
    {
      value: ""
      className: ""
      onInput: (newValue) ->
      onBackSpace: ->
      onEnter: ->
      onFocus: ->
      onBlur: ->
      allowEnter: false
    }

  shouldComponentUpdate: (nextProps) ->
    return @_isDirty or nextProps.value != @props.value

  refresh: ->
    el = @getDOMNode()
    if el.textContent != @value
      el.textContent = @value
    @_isDirty = false

  componentDidMount: -> @refresh()
  componentDidUpdate: -> @refresh()

  handleInput: ->
    @_isDirty = true
    el = @getDOMNode()
    newValue = el.textContent
    @onInput(newValue)

  handleKeyDown: (e) ->
    host = util.selection.getHost()
    if e.keyCode == 37 # left
      if util.selection.isAtStart()
        previousHost = findAdjacentHost(host, -1)
        if previousHost
          e.preventDefault()
          util.selection.setAtEnd(previousHost)

    else if e.keyCode == 39 # right
      if util.selection.isAtEnd()
        nextHost = findAdjacentHost(host, 1)
        if nextHost
          e.preventDefault()
          util.selection.setAtStart(nextHost)

    else if e.keyCode == 8 # backspace
      if util.selection.isAtStart()
        e.preventDefault()
        @onBackSpace()

    else if e.keyCode == 13 # enter
      unless @allowEnter
        e.preventDefault()
        @onEnter()

  handleFocus: ->
    @onFocus()

  handleBlur: ->
    @onBlur()

  selectAll: ->
    el = @getDOMNode()
    util.selection.setAll(el)

  isFocused: ->
    el = @getDOMNode()
    host = util.selection.getHost()
    return el == host

  render: ->
    R.div {
      className: @className
      contentEditable: true
      onInput: @handleInput
      onKeyDown: @handleKeyDown
      onFocus: @handleFocus
      onBlur: @handleBlur
    }

findAdjacentHost = (el, direction) ->
  hosts = document.querySelectorAll("[contenteditable]")
  hosts = _.toArray(hosts)
  index = hosts.indexOf(el)
  return hosts[index + direction]