/* Curve constructor */

function Curve(p, first) {
    var refinement = Curve.eq_diff
    if (first)
        refinement = Curve.eq_prom

    if (p && ( typeof(p[0]) == "undefined" || typeof(p[0][0]) == "undefined"))
        throw "New curve with no data."
    this.closed_loop = p[0][0] == p[p.length-1][0] && p[0][1] == p[p.length-1][1]

    this.p = new PointSet(p)
    this.t = new PointSet(refinement(this.p.d, this.closed_loop, this.p))
    this.n = new PointSet(Curve.eq_diff(this.t.d, this.closed_loop))
    this.k = [0]
    this.r = [0]
    this.KA = [0]
    this.KD = [0]
    this.e = [this.p.p[0]]
    for (var i = 0; i < this.t.d.length - 1; i++) {
        this.k[i + 1] = this.t.s[i] / this.p.s[i]
        this.r[i + 1] = 1 / this.k[i + 1]
        this.KD[i + 1] = this.KD[i] + this.t.s[i]
        this.KA[i + 1] = this.KD[i + 1] / this.p.s[i] / (i + 1)
        this.e[i + 1] = Vector.mul(this.r[i + 1], this.n.p[i + 1]).add(this.p.p[i + 1])
    }
    /*
    alert(  "\nCurvature Deviation: " + this.KD[this.KD.length-1] +
            "\nAverage Curvature: " + this.KA[this.KA.length-1] )
    */
    this.focus()
}

Curve.eq_diff = function(org, closed_loop) {
    var tan = []
    if (closed_loop) 
        tan[0] = Vector.mul(0.5,
            Vector.add(org[org.length-1].unit(), org[0].unit()))
    else
        tan[0] = org[0].unit()

    for (var i = 0; i < org.length - 1; i++)
        tan[i + 1] = Vector.mul(0.5,
            Vector.add(org[i + 1].unit(), org[i].unit()))
    
    if (closed_loop) 
        tan.push(tan[0])
    else
        tan.push(org[org.length - 1].unit())

    return tan
}

Curve.eq_prom = function(org, closed_loop, pointset) {
    var tan = []
    var alpha
    if (closed_loop) 
        tan[0] = Vector.mul(0.5,
            Vector.add(org[org.length-1].unit(), org[0].unit())).unit()
    else
        tan[0] = org[0].unit()
    
    var sum = 0
    for (var i = 0; i < org.length - 1; i++)
        sum += (org[i+1][0] - org[i][0]) * (org[i+1][1] + org[i][0])
    var ccw = sum < 0

    for (var i = 0; i < org.length - 1; i++) {
        var a1 = org[i].angle()
        var a2 = org[i+1].angle()
        var beta 
        if (ccw)
            beta = a2 - a1
        else
            beta = a1 + 2 * Math.PI - a2
       
        var ratio = pointset.s[i] / pointset.s[i+1]
        var E = 0.1
    
        for (alpha=0; alpha<2*Math.PI; alpha+=0.0005) {
            e = Math.tan(alpha) / Math.tan(beta-alpha) - ratio

            if ( Math.abs(e) < E)
                break
        }
        var total_angle = org[i+1].angle() + (ccw? (-alpha) : alpha)
        if (total_angle > 2 * Math.PI)
            total_angle -= Math.PI * 2
        tan[i + 1] = new Vector(1, 0).rotate( total_angle )
    }
    
    if (closed_loop) 
        tan.push(tan[0])
    else
        tan.push(org[org.length - 1].unit())

    return tan
}

/*
 * Refine a curve by making Beziers in given curve points.
 * When 4 given points intersect an order 2 Bezier is made,
 * otherwise a 3 degree Bezier is calculated.
 * t: Number of intermediate points (t>1) or refinment factor (t<1)
 */
Curve.prototype.refine = function(t, sf) {
    sf = sf || 0.5
    while (sf > 1)
        sf /= 100
    var b = []
    if (t > 1)
        t = 1 / t

    for (var i = 0; i < this.p.p.length - 1; i++) {
        var m1 = new Segment(this.p.p[i], Point.add(this.p.p[i], this.t.p[i]))
        var m2 = new Segment(Point.subs(this.p.p[i + 1], this.t.p[i+1]), this.p.p[i + 1])
        var z = new Bezier(m1, m2, sf).trace(t)
        b = b.concat(z)
    }
    b.push(this.p.p[this.p.p.length - 1])

    return new Curve(b)
}

// Equally space points on the curve
Curve.prototype.walk = function(step) {
    var footprints = []
    var S = this.p.S
    var walked = 0

    for (var i = 0; i < S.length; i++)
        if (S[i] - walked > step) {
            walked = S[i]
            footprints.push(i)
        }

    return footprints
}

/* This algorithm looks ahead at what distance the tooth (height between punches) is reached.
 * Then finds out the depression at that given distance due to the curvature.
 * Finally establishes the reduced distance (rd) at which the tooth height minus the curvature is reached.
 * r: Punch radius
 * h: height between punches
 * i: interior or exterior punch
 */
Curve.prototype.punch = function(r, h, i) {
    var footprints = []
    var S = this.p.S
    var walked = 0
    var distance_ref = r * Math.sin(Math.acos(1 - h / r))
    var distance = distance_ref
    var rd /* Reduced distance */
    var step = 1
    var K
    var K_LIM = h / 4
    var last_K = 0
    var d_min = this.p.d[1]
    var max = 0 /* Maximum tooth height */
    var c_h

    for (var i = 0; i < S.length; i = step) {
        footprints.push(i)
        distance = distance_ref
        K = this.KD[PointSet.index(S, S[i] + distance, i)]
        if (K - last_K > K_LIM)
            distance = (S[PointSet.index(this.KD, this.KD[i] + K_LIM, i)] - S[i])
        rd = 2 * distance
        step = PointSet.index(S, S[i] + rd, i)
        c_h = last_K = this.KD[step]
        c_h -= this.KD[i]
        c_h += r * (1 - Math.cos(Math.asin((S[step] - S[i]) / 2 / r)))
        if (c_h > max)
            max = c_h
        if (step < 1)
            step = 1
    }

    // alert( "Max tooth: " + max )
    // alert(footprints.length)
    return footprints
}

Curve.prototype.points_at = function(indices) {
    var footprints = []
    for (var i = 0; i < indices.length; i++)
        footprints.push(this.p.p[indices[i]])
    return footprints
}

Curve.prototype.evolute_at = function(indices) {
    var footprints = []
    for (var i = 0; i < indices.length; i++)
        footprints.push(this.e[indices[i]])
    return footprints
}

Curve.prototype.focus = function() {
    var set = this.p.p
    var xmin = set[0][0]
    var xmax = xmin
    var ymin = set[0][1]
    var ymax = ymin

    for (var i = 1; i < set.length; i++) {
        if (set[i][0] < xmin)
            xmin = set[i][0]
        if (set[i][0] > xmax)
            xmax = set[i][0]
        if (set[i][1] < ymin)
            ymin = set[i][1]
        if (set[i][1] > ymax)
            ymax = set[i][1]
    }
    return this.boundary = [xmin, xmax, ymin, ymax]
}