   calculateIntermediatePoints(innerPoints, shiftedInnerPoints, outerPoints, percentage)
 
 
-innerStarPoints = (innerRadius) ->
-  outer = outerPoints()
-  inner = innerPoints(innerRadius)
-
-  return [outer, inner]
-
-linearStarPoints = innerStarPoints
-quadraticStarPoints = innerStarPoints
-
-innerAndIntermediatePoints = (innerRadius, percentage) ->
-  outer = outerPoints()
-  inner = innerPoints(innerRadius)
-  [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, percentage)
-
-  return [outer, inner, intermediate1, intermediate2]
-
-starWithQuadraticTipsPoints = innerAndIntermediatePoints
-
-cubicStarPoints = (innerRadius, controlPercentage) ->
-  [outer, inner, control1, control2] = innerAndIntermediatePoints(innerRadius, controlPercentage)
-  return [inner, control1, control2]
-
-starWithCircularTipsPoints = (innerRadius, straightPercentage) ->
-  [outer, inner, intermediate1, intermediate2] = innerAndIntermediatePoints(innerRadius, straightPercentage)
-  radius = calculateRadius(intermediate1[0], outer[0], intermediate2[0])
-
-  return [inner, intermediate1, intermediate2, radius]
-
-starWithCubicTipsPoints = (innerRadius, straightPercentage, controlPercentage) ->
-  outer = outerPoints()
-  inner = innerPoints(innerRadius)
-  [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, straightPercentage)
-  [control1, control2] = calculateIntermediatePoints(intermediate1, intermediate2, outer, controlPercentage)
-
-  return [inner, intermediate1, intermediate2, control1, control2]
-
-crossingCubicPoints = (controlAngle, controlDistance) ->
-  outer = outerPoints()
-
-  controlAngle = (controlAngle / 2) * (Math.PI / 180)
-  angles = outerAngles()
-  controlAngles1 = angles.map (angle) ->
-    angle + Math.PI - controlAngle
-  controlAngles2 = angles.map (angle) ->
-    angle + Math.PI + controlAngle
-
-  displacement1 = controlAngles1.map (angle) -> polarToCartesian(angle, controlDistance)
-  displacement2 = controlAngles2.map (angle) -> polarToCartesian(angle, controlDistance)
-  controlPoints1 = _.zip(outer, displacement1).map(addPoints)
-  controlPoints2 = _.zip(outer, displacement2).map(addPoints)
-
-  [outer, controlPoints1, controlPoints2]
-
-
-linearStarPath = (innerRadius) ->
-  [outer, inner] = linearStarPoints(innerRadius)
-
-  points = _.flatten(_.zip(inner, outer))
-  return "M " + points.join(" L ") + " Z"
-
-quadraticStarPath = (innerRadius) ->
-  [outer, inner] = quadraticStarPoints(innerRadius)
-
-  first = inner[0]
-  inner.push(inner.shift())
-  points = _.zip(outer, inner)
-
-  sectionStrings = points.map ([outer, inner]) ->
-    "Q #{ outer } #{ inner }"
-
-  return "M #{ first } " + sectionStrings.join(" ") + " Z"
-
-cubicStarPath = (innerRadius, controlPercentage) ->
-  [inner, control1, control2] = cubicStarPoints(innerRadius, controlPercentage)
-
-  first = inner[0]
-  inner.push(inner.shift())
-  points = _.zip(control1, control2, inner)
-
-  sectionStrings = points.map ([control1, control2, inner]) ->
-    "C #{ control1 } #{ control2 } #{ inner }"
-
-  return "M #{ first } " + sectionStrings.join(" ") + " Z"
-
-starWithCircularTipsPath = (innerRadius, straightPercentage) ->
-  if straightPercentage == "100"
-    return linearStarPath(innerRadius)
-
-  [inner, intermediate1, intermediate2, radius] = starWithCircularTipsPoints(innerRadius, straightPercentage)
-  points = _.zip(inner, intermediate1, intermediate2)
-
-  sectionStrings = points.map ([inner, intermediate1, intermediate2]) ->
-    "#{ inner } L #{ intermediate1 } A #{ radius } #{ radius } 0 0 1 #{ intermediate2 }"
-  return "M " + sectionStrings.join(" L ") + " Z"
-
-starWithQuadraticTipsPath = (innerRadius, straightPercentage) ->
-  if straightPercentage == "0"
-    return quadraticStarPath(innerRadius)
-  if straightPercentage == "100"
-    return linearStarPath(innerRadius)
-
-  [outer, inner, intermediate1, intermediate2] = starWithQuadraticTipsPoints(innerRadius, straightPercentage)
-  points = _.zip(inner, intermediate1, outer, intermediate2)
-
-  sectionStrings = points.map ([inner, intermediate1, outer, intermediate2]) ->
-    "#{ inner } L #{ intermediate1 } Q #{ outer } #{ intermediate2 }"
-  return "M " + sectionStrings.join(" L ") + " Z"
-
-starWithCubicTipsPath = (innerRadius, straightPercentage, controlPercentage) ->
-  if straightPercentage == "0"
-    return cubicStarPath(innerRadius, controlPercentage)
-  if straightPercentage == "100"
-    return linearStarPath(innerRadius)
-
-  [inner, intermediate1, intermediate2, control1, control2] = starWithCubicTipsPoints(innerRadius, straightPercentage, controlPercentage)
-  points = _.zip(inner, intermediate1, control1, control2, intermediate2)
-
-  sectionStrings = points.map ([inner, intermediate1, control1, control2, intermediate2]) ->
-    "#{ inner } L #{ intermediate1 } C #{ control1 } #{ control2 } #{ intermediate2 }"
-  return "M " + sectionStrings.join(" L ") + " Z"
-
-crossingCubicStarPath = (controlAngle, controlDistance) ->
-  [outer, control1, control2] = crossingCubicPoints(controlAngle, controlDistance)
-  "M #{outer[0]} C #{control1[0]} #{control2[2]} #{outer[2]}
-                 C #{control1[2]} #{control2[4]} #{outer[4]}
-                 C #{control1[4]} #{control2[1]} #{outer[1]}
-                 C #{control1[1]} #{control2[3]} #{outer[3]}
-                 C #{control1[3]} #{control2[0]} #{outer[0]}
-                 Z"
-
-
-setStarPath = (path) ->
-  $("#star").attr("d", path)
-  return
-
-drawStarWithCircularTips = () ->
-  innerRadius = controls["inner-radius"].val()
-  straightPercentage = controls["straight-percentage"].val()
-  path = starWithCircularTipsPath(innerRadius, straightPercentage)
-  setStarPath(path)
-
-drawStarWithQuadraticTips = () ->
-  innerRadius = controls["inner-radius"].val()
-  straightPercentage = controls["straight-percentage"].val()
-  path = starWithQuadraticTipsPath(innerRadius, straightPercentage)
-  setStarPath(path)
-
-drawStarWithCubicTips = () ->
-  innerRadius = controls["inner-radius"].val()
-  straightPercentage = controls["straight-percentage"].val()
-  controlPercentage = controls["control-percentage"].val()
-  path = starWithCubicTipsPath(innerRadius, straightPercentage, controlPercentage)
-  setStarPath(path)
-
-drawCrossingCubicStar = () ->
-  controlAngle = controls["control-angle"].val()
-  controlDistance = controls["control-distance"].val()
-  path = crossingCubicStarPath(controlAngle, controlDistance)
-  setStarPath(path)
+controlVals = (controlNames...) ->
+  for name in controlNames
+    controls[name].val()
+
+class Star
+  constructor: (@name, @controls) ->
+
+  refreshPath: () =>
+    pathString = @path(controlVals(@controls...)...)
+    $("#star").attr("d", pathString)
+    return
+
+  use: () =>
+    currentStar = this
+    showControls(@controls...)
+    updateUrlQuery("s", @name)
+    @refreshPath()
+
+  path: (inputs...) ->
+    points = @points(inputs...)
+    @constructPath(points...)
+
+class InnerAndIntermediateStar extends Star
+  points: (innerRadius, percentage) ->
+    outer = outerPoints()
+    inner = innerPoints(innerRadius)
+    [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, percentage)
+
+    [inner, intermediate1, intermediate2, outer]
+
+class LinearStar extends Star
+  constructor: () ->
+    super("linear", ["inner-radius"])
+
+  points: (innerRadius) ->
+    [innerPoints(innerRadius), outerPoints()]
+
+  constructPath: (inner, outer) ->
+    points = _.flatten(_.zip(inner, outer))
+    return "M " + points.join(" L ") + " Z"
+
+class StarWithCircularTips extends InnerAndIntermediateStar
+  constructor: () ->
+    super("circular", ["inner-radius", "straight-percentage"])
+
+  path: (innerRadius, straightPercentage) ->
+    if straightPercentage == "100"
+      return linearStar.path(innerRadius)
+    else
+      super(innerRadius, straightPercentage)
+
+  points: (innerRadius, straightPercentage) ->
+    [inner, intermediate1, intermediate2, outer] = super(innerRadius, straightPercentage)
+    radius = calculateRadius(intermediate1[0], outer[0], intermediate2[0])
+
+    return [inner, intermediate1, intermediate2, radius]
+
+  constructPath: (inner, intermediate1, intermediate2, radius) ->
+    points = _.zip(inner, intermediate1, intermediate2)
+
+    sectionStrings = points.map ([inner, intermediate1, intermediate2]) ->
+      "#{ inner } L #{ intermediate1 } A #{ radius } #{ radius } 0 0 1 #{ intermediate2 }"
+    return "M " + sectionStrings.join(" L ") + " Z"
+
+class QuadraticStar extends Star
+  constructor: () ->
+    super(null, ["inner-radius"])
+
+  points: (innerRadius) ->
+    [innerPoints(innerRadius), outerPoints()]
+
+  constructPath: (inner, outer) ->
+    first = inner[0]
+    inner.push(inner.shift())
+    points = _.zip(outer, inner)
+
+    sectionStrings = points.map ([outer, inner]) ->
+      "Q #{ outer } #{ inner }"
+
+    return "M #{ first } " + sectionStrings.join(" ") + " Z"
+
+class StarWithQuadraticTips extends InnerAndIntermediateStar
+  constructor: () ->
+    super("quadratic", ["inner-radius", "straight-percentage"])
+
+  path: (innerRadius, straightPercentage) ->
+    if straightPercentage == "100"
+      return linearStar.path(innerRadius)
+    if straightPercentage == "0"
+      return quadraticStar.path(innerRadius)
+
+    super(innerRadius, straightPercentage)
+
+  constructPath: (inner, intermediate1, intermediate2, outer) ->
+    points = _.zip(inner, intermediate1, outer, intermediate2)
+
+    sectionStrings = points.map ([inner, intermediate1, outer, intermediate2]) ->
+      "#{ inner } L #{ intermediate1 } Q #{ outer } #{ intermediate2 }"
+    return "M " + sectionStrings.join(" L ") + " Z"
+
+class CubicStar extends InnerAndIntermediateStar
+  constructor: () ->
+    super(null, ["inner-radius", "control-percentage"])
+
+  points: (innerRadius, controlPercentage) ->
+    [inner, control1, control2, outer] = super(innerRadius, controlPercentage)
+    [inner, control1, control2]
+
+  constructPath: (inner, control1, control2) ->
+    first = inner[0]
+    inner.push(inner.shift())
+    points = _.zip(control1, control2, inner)
+
+    sectionStrings = points.map ([control1, control2, inner]) ->
+      "C #{ control1 } #{ control2 } #{ inner }"
+    return "M #{ first } " + sectionStrings.join(" ") + " Z"
+
+class StarWithCubicTips extends Star
+  constructor: () ->
+    super("cubic", ["inner-radius", "straight-percentage", "control-percentage"])
+
+  path: (innerRadius, straightPercentage, controlPercentage) ->
+    if straightPercentage == "100"
+      return linearStar.path(innerRadius)
+    if straightPercentage == "0"
+      return cubicStar.path(innerRadius, controlPercentage)
+
+    super(innerRadius, straightPercentage, controlPercentage)
+
+  points: (innerRadius, straightPercentage, controlPercentage) ->
+    outer = outerPoints()
+    inner = innerPoints(innerRadius)
+    [intermediate1, intermediate2] = calculateIntermediatePointsComingAndGoing(inner, outer, straightPercentage)
+    [control1, control2] = calculateIntermediatePoints(intermediate1, intermediate2, outer, controlPercentage)
+
+    return [inner, intermediate1, intermediate2, control1, control2]
+
+  constructPath: (inner, intermediate1, intermediate2, control1, control2) ->
+    points = _.zip(inner, intermediate1, control1, control2, intermediate2)
+
+    sectionStrings = points.map ([inner, intermediate1, control1, control2, intermediate2]) ->
+      "#{ inner } L #{ intermediate1 } C #{ control1 } #{ control2 } #{ intermediate2 }"
+    return "M " + sectionStrings.join(" L ") + " Z"
+
+class CrossingCubicStar extends Star
+  constructor: () ->
+    super("crossingcubic", ["control-angle", "control-distance"])
+
+  points: (controlAngle, controlDistance) ->
+    outer = outerPoints()
+
+    controlAngle = (controlAngle / 2) * (Math.PI / 180)
+    angles = outerAngles()
+    controlAngles1 = angles.map (angle) ->
+      angle + Math.PI - controlAngle
+    controlAngles2 = angles.map (angle) ->
+      angle + Math.PI + controlAngle
+
+    displacement1 = controlAngles1.map (angle) -> polarToCartesian(angle, controlDistance)
+    displacement2 = controlAngles2.map (angle) -> polarToCartesian(angle, controlDistance)
+    controlPoints1 = _.zip(outer, displacement1).map(addPoints)
+    controlPoints2 = _.zip(outer, displacement2).map(addPoints)
+
+    [outer, controlPoints1, controlPoints2]
+
+  constructPath: (outer, control1, control2) ->
+    "M #{outer[0]} C #{control1[0]} #{control2[2]} #{outer[2]}
+                   C #{control1[2]} #{control2[4]} #{outer[4]}
+                   C #{control1[4]} #{control2[1]} #{outer[1]}
+                   C #{control1[1]} #{control2[3]} #{outer[3]}
+                   C #{control1[3]} #{control2[0]} #{outer[0]}
+                   Z"
+
+
+linearStar = new LinearStar()
+starWithCircularTips = new StarWithCircularTips()
+quadraticStar = new QuadraticStar()
+starWithQuadraticTips = new StarWithQuadraticTips()
+cubicStar = new CubicStar()
+starWithCubicTips = new StarWithCubicTips()
+crossingCubicStar = new CrossingCubicStar()

  refreshBackground($("#bg-color-picker").spectrum("get"))


## Math functions for calculating star shapes

class Point
  constructor: (@x, @y) ->

    $("#swatch").css("background-color", "")


updateUrlQuery = (key, value) ->
  uri.search (queryParams) ->
    queryParams[key] = value
