var film2d = use('film2d')

var math = use('math/radians')

var line_proto = {
  type: 'mesh2d',
  
  set_pos: function(x, y) {
    this.pos.x = x
    this.pos.y = y
    this._dirty = true
    return this
  },
  
  set_points: function(points) {
    this.points = points
    this._dirty = true
    this._rebuild()
    return this
  },
  
  set_point: function(i, x, y) {
    if (i >= 0 && i < length(this.points)) {
      this.points[i].x = x
      this.points[i].y = y
      this._dirty = true
      this._rebuild()
    }
    return this
  },
  
  set_width: function(w) {
    this.width = w
    this._dirty = true
    this._rebuild()
    return this
  },
  
  set_widths: function(widths) {
    this.widths = widths
    this._dirty = true
    this._rebuild()
    return this
  },
  
  _rebuild: function() {
    var result = build_polyline_mesh(this)
    this.verts = result.verts
    this.indices = result.indices
    this._cumulative_lengths = result.cumulative_lengths
    this._total_length = result.total_length
  },
  
  destroy: function() {
    film2d.unregister(this._id)
  }
}

function build_polyline_mesh(line) {
  var points = line.points || []
  if (length(points) < 2) return {verts: [], indices: [], cumulative_lengths: [], total_length: 0}
  
  var width = line.width || 10
  var widths = line.widths
  var closed = line.closed || false
  var join = line.join || 'miter'
  var cap = line.cap || 'butt'
  var miter_limit = line.miter_limit || 4
  
  var uv_mode = line.uv ? (line.uv.mode || 'repeat') : 'repeat'
  var u_per_unit = line.uv ? (line.uv.u_per_unit || (1 / 16)) : (1 / 16)
  var u_offset = line.uv ? (line.uv.u_offset || 0) : 0
  var v_scale = line.uv ? (line.uv.v_scale || 1) : 1
  var v_offset = line.uv ? (line.uv.v_offset || 0) : 0
  
  var pos = line.pos || {x: 0, y: 0}
  var points_space = line.points_space || 'world'
  
  // Transform points if in local space
  var pts = []
  var i = 0
  var p = null
  var dx = 0
  var dy = 0

  for (i = 0; i < length(points); i++) {
    p = points[i]
    if (points_space == 'local') {
      push(pts, {x: p.x + pos.x, y: p.y + pos.y})
    } else {
      push(pts, {x: p.x, y: p.y})
    }
  }

  // Calculate cumulative distances
  var cumulative = [0]
  for (i = 1; i < length(pts); i++) {
    dx = pts[i].x - pts[i-1].x
    dy = pts[i].y - pts[i-1].y
    push(cumulative, cumulative[i-1] + math.sqrt(dx*dx + dy*dy))
  }
  var total_length = cumulative[length(cumulative) - 1]

  // Build triangle strip mesh
  var verts = []
  var indices = []

  // Get width at point i
  function get_width(idx) {
    if (widths && length(widths) > idx) return widths[idx]
    return width
  }

  // Get U coordinate at point i
  var seg_idx = 0
  var seg_len = 0
  function get_u(idx) {
    if (uv_mode == 'stretch') {
      return total_length > 0 ? cumulative[idx] / total_length : 0
    } else if (uv_mode == 'per_segment') {
      if (idx == 0) return 0
      seg_idx = idx - 1
      seg_len = cumulative[idx] - cumulative[idx-1]
      return 1 // Each segment ends at u=1
    } else {
      // repeat (default)
      return cumulative[idx] * u_per_unit + u_offset
    }
  }

  // Calculate normals at each point
  var normals = []
  var prev = null
  var curr = null
  var next = null
  var n = null
  var d1x = 0
  var d1y = 0
  var d2x = 0
  var d2y = 0
  var len1 = 0
  var len2 = 0
  var n1x = 0
  var n1y = 0
  var n2x = 0
  var n2y = 0
  var nlen = 0
  var dot = 0
  var miter_scale = 0
  var len = 0

  for (i = 0; i < length(pts); i++) {
    prev = i > 0 ? pts[i-1] : (closed ? pts[length(pts)-1] : null)
    curr = pts[i]
    next = i < length(pts)-1 ? pts[i+1] : (closed ? pts[0] : null)

    n = {x: 0, y: 0}

    if (prev && next) {
      // Middle point - average normals
      d1x = curr.x - prev.x
      d1y = curr.y - prev.y
      d2x = next.x - curr.x
      d2y = next.y - curr.y

      len1 = math.sqrt(d1x*d1x + d1y*d1y)
      len2 = math.sqrt(d2x*d2x + d2y*d2y)

      if (len1 > 0.0001) { d1x /= len1; d1y /= len1 }
      if (len2 > 0.0001) { d2x /= len2; d2y /= len2 }

      // Normals (perpendicular)
      n1x = -d1y
      n1y = d1x
      n2x = -d2y
      n2y = d2x

      // Average
      n.x = n1x + n2x
      n.y = n1y + n2y
      nlen = math.sqrt(n.x*n.x + n.y*n.y)
      if (nlen > 0.0001) { n.x /= nlen; n.y /= nlen }

      // Miter correction
      dot = n1x * n.x + n1y * n.y
      if (dot > 0.0001) {
        miter_scale = 1 / dot
        if (miter_scale > miter_limit) miter_scale = miter_limit
        n.x *= miter_scale
        n.y *= miter_scale
      }
    } else if (next) {
      // Start point
      dx = next.x - curr.x
      dy = next.y - curr.y
      len = math.sqrt(dx*dx + dy*dy)
      if (len > 0.0001) { dx /= len; dy /= len }
      n.x = -dy
      n.y = dx
    } else if (prev) {
      // End point
      dx = curr.x - prev.x
      dy = curr.y - prev.y
      len = math.sqrt(dx*dx + dy*dy)
      if (len > 0.0001) { dx /= len; dy /= len }
      n.x = -dy
      n.y = dx
    }

    push(normals, n)
  }

  // Generate vertices (2 per point - left and right of line)
  var w = 0
  var u = 0
  for (i = 0; i < length(pts); i++) {
    p = pts[i]
    n = normals[i]
    w = get_width(i) * 0.5
    u = get_u(i)

    // Left vertex (v=0)
    push(verts, {
      x: p.x + n.x * w,
      y: p.y + n.y * w,
      u: u,
      v: v_offset,
      r: 1, g: 1, b: 1, a: 1
    })

    // Right vertex (v=1)
    push(verts, {
      x: p.x - n.x * w,
      y: p.y - n.y * w,
      u: u,
      v: v_scale + v_offset,
      r: 1, g: 1, b: 1, a: 1
    })
  }

  // Generate indices (triangle strip as triangles)
  var base = 0
  for (i = 0; i < length(pts) - 1; i++) {
    base = i * 2
    // First triangle
    push(indices, base + 0)
    push(indices, base + 1)
    push(indices, base + 2)
    // Second triangle
    push(indices, base + 1)
    push(indices, base + 3)
    push(indices, base + 2)
  }

  // Handle closed path
  var last = 0
  if (closed && length(pts) > 2) {
    last = (length(pts) - 1) * 2
    push(indices, last + 0)
    push(indices, last + 1)
    push(indices, 0)
    push(indices, last + 1)
    push(indices, 1)
    push(indices, 0)
  }
  
  // Add round caps if requested
  if (!closed && cap == 'round') {
    add_round_cap({verts: verts, indices: indices, p: pts[0], n: normals[0], width: get_width(0), u: get_u(0), v_offset: v_offset, v_scale: v_scale, is_start: true})
    add_round_cap({verts: verts, indices: indices, p: pts[length(pts)-1], n: normals[length(pts)-1], width: get_width(length(pts)-1), u: get_u(length(pts)-1), v_offset: v_offset, v_scale: v_scale, is_start: false})
  } else if (!closed && cap == 'square') {
    add_square_cap({verts: verts, indices: indices, p: pts[0], n: normals[0], width: get_width(0), u: get_u(0), v_offset: v_offset, v_scale: v_scale, is_start: true, adjacent: pts[1]})
    add_square_cap({verts: verts, indices: indices, p: pts[length(pts)-1], n: normals[length(pts)-1], width: get_width(length(pts)-1), u: get_u(length(pts)-1), v_offset: v_offset, v_scale: v_scale, is_start: false, adjacent: pts[length(pts)-2]})
  }
  
  return {
    verts: verts,
    indices: indices,
    cumulative_lengths: cumulative,
    total_length: total_length
  }
}

function add_round_cap(opts) {
  var verts = opts.verts
  var indices = opts.indices
  var p = opts.p
  var n = opts.n
  var rc_width = opts.width
  var u = opts.u
  var v_offset = opts.v_offset
  var v_scale = opts.v_scale
  var is_start = opts.is_start

  var w = rc_width * 0.5
  var segments = 8
  var base_idx = length(verts)

  // Direction along the line
  var dx = is_start ? -n.y : n.y
  var dy = is_start ? n.x : -n.x

  // Center vertex
  push(verts, {
    x: p.x,
    y: p.y,
    u: u,
    v: 0.5 * v_scale + v_offset,
    r: 1, g: 1, b: 1, a: 1
  })

  // Arc vertices
  var start_angle = is_start ? math.arc_tangent(n.y, n.x) : math.arc_tangent(-n.y, -n.x)
  var i = 0
  var angle = 0
  var cx = 0
  var cy = 0
  for (i = 0; i <= segments; i++) {
    angle = start_angle + (i / segments) * 3.14159
    cx = math.cosine(angle)
    cy = math.sine(angle)

    push(verts, {
      x: p.x + cx * w,
      y: p.y + cy * w,
      u: u,
      v: (0.5 + cy * 0.5) * v_scale + v_offset,
      r: 1, g: 1, b: 1, a: 1
    })
  }

  // Fan triangles
  for (i = 0; i < segments; i++) {
    push(indices, base_idx)
    push(indices, base_idx + 1 + i)
    push(indices, base_idx + 2 + i)
  }
}

function add_square_cap(opts) {
  var verts = opts.verts
  var indices = opts.indices
  var p = opts.p
  var n = opts.n
  var sc_width = opts.width
  var u = opts.u
  var v_offset = opts.v_offset
  var v_scale = opts.v_scale
  var is_start = opts.is_start
  var adjacent = opts.adjacent

  var w = sc_width * 0.5
  var base_idx = length(verts)

  // Direction along the line (away from adjacent point)
  var dx = p.x - adjacent.x
  var dy = p.y - adjacent.y
  var len = math.sqrt(dx*dx + dy*dy)
  if (len > 0.0001) { dx /= len; dy /= len }

  // Extend by half width
  var ext = w
  var ex = p.x + dx * ext
  var ey = p.y + dy * ext

  // Four corners of the cap
  push(verts, {x: p.x + n.x * w, y: p.y + n.y * w, u: u, v: v_offset, r: 1, g: 1, b: 1, a: 1})
  push(verts, {x: p.x - n.x * w, y: p.y - n.y * w, u: u, v: v_scale + v_offset, r: 1, g: 1, b: 1, a: 1})
  push(verts, {x: ex + n.x * w, y: ey + n.y * w, u: u, v: v_offset, r: 1, g: 1, b: 1, a: 1})
  push(verts, {x: ex - n.x * w, y: ey - n.y * w, u: u, v: v_scale + v_offset, r: 1, g: 1, b: 1, a: 1})
  
  push(indices, base_idx + 0)
  push(indices, base_idx + 1)
  push(indices, base_idx + 2)
  push(indices, base_idx + 1)
  push(indices, base_idx + 3)
  push(indices, base_idx + 2)
}

var defaults = {
  type: 'mesh2d',
  
  // routing
  plane: 'default',
  layer: 0,
  groups: [],
  visible: true,
  
  // transform
  pos: {x: 0, y: 0},
  points_space: 'world',
  
  // geometry
  points: [],
  closed: false,
  
  // thickness
  width: 10,
  widths: null,
  
  // join/cap
  join: 'miter',
  cap: 'butt',
  miter_limit: 4,
  
  // material
  image: null,
  tint: {r: 1, g: 1, b: 1, a: 1},
  opacity: 1,
  blend: 'alpha',
  filter: 'linear',
  
  // UV behavior
  uv: {
    space: 'world',
    mode: 'repeat',
    u_per_unit: 1 / 16,
    u_offset: 0,
    v_scale: 1,
    v_offset: 0,
    rotate: 0
  },
  
  // dashes
  dash_len: 0,
  gap_len: 0,
  dash_offset: 0
}

function make_line(props) {
  var data = object(defaults, props)
  
  // Ensure groups is array
  if (!data.groups) data.groups = []
  if (is_text(data.groups)) data.groups = [data.groups]
  
  var line = meme(line_proto, data)
  line._rebuild()
  film2d.register(line)
  return line
}

var line2d = {
  polyline: function(props) {
    return make_line(props)
  },
  
  line: function(from, to, props) {
    var p = props || {}
    p.points = [{x: from.x, y: from.y}, {x: to.x, y: to.y}]
    return make_line(p)
  }
}

return line2d
