/* Canvas class */
function Canvas(html) {
    this.html = html
    this.width = html.width
    this.height = html.height
    this.xc = 0     /* Origin of logic window (not canvas) */
    this.yc = 0     /* */
    this.pen = this.html.getContext("2d")
    this.scale = 1400
    this.specimen = {}

    this.listen("wheel")
    this.listen("mousedown")
    this.listen("mouseup")
    this.listen("mousemove")
    this.xc0
    this.yc0
    this.xm0
    this.ym0
    this.dragging = false
    this.click = false
}

/* Translate from window to canvas coordinates */
Canvas.prototype.X = function(x) {
    return this.scale * (x - this.xc) + this.width / 2
}
Canvas.prototype.Y = function(y) {
    return this.height / 2  - this.scale * (y - this.yc)
}

Canvas.prototype.focus = function(xmin, xmax, ymin, ymax) {
    var anch = Math.abs(xmax - xmin)
    var alt = Math.abs(ymax - ymin)
    var xr = anch / this.width
    var yr = alt / this.height
    var r = xr > yr ? xr : yr
    this.scale = 0.85 / r
    this.xc =  (xmin + anch / 2)
    this.yc =  (ymin + alt / 2)
}

Canvas.prototype.get_visible_height = function() {
    return this.height / this.scale
}

Canvas.prototype.get_visible_width = function() {
    return this.width / this.scale
}

Canvas.prototype.draw_axes = function() {

    var xwidth = this.get_visible_width() 
    var xmin = this.xc - xwidth / 2
    var xmax = xmin + xwidth
    var yheight = this.get_visible_height() 
    var ymin = this.yc - yheight / 2
    var ymax = ymin + yheight

    if (ymin <= 0 && ymax >= 0) {
        /* Print t x axis */
        this.pen.beginPath()
        this.pen.strokeStyle = "#7BC3EF"
        this.pen.moveTo(this.X(xmin), this.Y(0))
        this.pen.lineTo(this.X(xmax), this.Y(0))
        this.pen.stroke()

        /* Set Marks */
        var pot = Math.log10(xwidth)
        var deltaX = Math.pow(10, parseInt(pot)) / 10
        this.pen.beginPath()
        var x0 = (10 * deltaX) * parseInt (parseInt( (xmin) - 10 * deltaX) / (10 * deltaX) )
        for (var c = 0, i=x0; i < xmin + xwidth; i= x0 + ++c * deltaX) {
            var p = i
            var d = deltaX
            while (d < 1) {
                p *= 10
                d *= 10
            }
            var ext = (p / d) % 5 == 0 ? 5 : 0
            ext += (p / d ) % 10 == 0 ? 5 : 0
            this.pen.moveTo(this.X(i), this.Y(0) - (5 + ext))
            this.pen.lineTo(this.X(i), this.Y(0) + (5 + ext))
        }
        this.pen.stroke()

        /* Set Letters*/
        this.pen.fillStyle = "#7BC3EF"
        this.pen.textAlign = "center"
        for (var i = (10 * deltaX) * parseInt( (xmin - 10 * deltaX) / (10 * deltaX) ); i < xmin + xwidth; i+=5*deltaX) 
            if (i != 0)
                this.pen.fillText("" + i, this.X(i), this.Y(0) - 20)           
    }
     
     if (xmin <= 0 && xmax >= 0) {
        /* Print t y axis */
        this.pen.beginPath()
        this.pen.strokeStyle = "#7BC3EF"
        this.pen.moveTo(this.X(0), this.Y(ymin))
        this.pen.lineTo(this.X(0), this.Y(ymax))
        this.pen.stroke()

         /* Set Marks */
        var deltaY = Math.pow(10, parseInt(pot)) / 10
        this.pen.beginPath()
        var y0 = (10 * deltaY) * parseInt (parseInt( (ymin) - 10 * deltaY) / (10 * deltaY) )
        for (var c = 0, i=y0; i < ymin + yheight; i= y0 + ++c * deltaY) {
            var p = i
            var d = deltaY
            while (d < 1) {
                p *= 10
                d *= 10
            }
            var ext = (p / d) % 5 == 0 ? 5 : 0
            ext += (p / d ) % 10 == 0 ? 5 : 0
            this.pen.moveTo(this.X(0) - (5 + ext), this.Y(i))
            this.pen.lineTo(this.X(0) + (5 + ext), this.Y(i))
        }
        this.pen.stroke()

        /* Set Letters*/
        this.pen.fillStyle = "#7BC3EF"
        this.pen.textAlign = "center"
        for (var i = (10 * deltaY) * parseInt( (ymin - 10 * deltaY) / (10 * deltaY) ); i < ymin + yheight; i+=5*deltaY) 
            if (i != 0)
                this.pen.fillText("" + i, this.X(0) - 20, this.Y(i))   
    } 
}

Canvas.prototype.draw_grid = function() {
    this.draw_axes()
    var max_width = this.get_visible_width()
    var pot = Math.log10(max_width)
    var deltaX = Math.pow(10, parseInt(pot)) / 10
    var x0 = parseInt(-this.xc / deltaX * this.scale) * deltaX / this.scale
    this.pen.beginPath()
    this.pen.strokeStyle = "#338DC6"
    for (var i = 0; i * deltaX < max_width; i++) {
        this.pen.moveTo(x0 + this.X(i * deltaX), 0)
        this.pen.lineTo(x0 + this.X(i * deltaX), this.height)
    }
    this.pen.stroke()

}

Canvas.prototype.origin_sign = function(x, y) {
    var l = 40
    var r = 20
    this.pen.save()
    this.pen.beginPath()
    this.pen.arc(this.X(x), this.Y(y), r, 0, Math.PI * 2, true)
    this.pen.clip()
    this.pen.fillStyle = "#7BC3EF"
    this.pen.fillRect(this.X(x), this.Y(y), l, l)
    this.pen.fillRect(this.X(x) - l, this.Y(y) - l, l, l)
    this.pen.restore()
}

Canvas.prototype.set_mark = function(p) {
    this.pen.arc(this.X(p[0]), this.Y(p[1]), 4, 0, 2 * Math.PI)
}

Canvas.prototype.circle = function(p, r) {
    this.pen.arc(this.X(p[0]), this.Y(p[1]), this.scale * r, 0, 2 * Math.PI)
}

Canvas.prototype.draw_segment = function(a, color) {
    color = color || "white"
    this.pen.beginPath()
    this.pen.strokeStyle = color
    this.pen.moveTo(this.X(a.org[0]), this.Y(a.org[1]))
    this.pen.lineTo(this.X(a.end[0]), this.Y(a.end[1]))
    this.pen.stroke()
}


Canvas.prototype.mark = function(p) {
    /* Set a mark on each and every point */
    for (var i = 0; i < p.length; i++) {
        this.pen.beginPath()
        this.pen.fillStyle = "#CCCCFF"
        this.set_mark(p[i])
        this.pen.fill()
    }
}

Canvas.prototype.encircle = function(p, r) {
    /* Set a mark on each and every point */
    for (var i = 0; i < p.length; i++) {
        this.pen.beginPath()
        this.pen.fillStyle = "rgba(0,0,102, 0.15)" //"#000066"
        this.circle(p[i], r)
        this.pen.fill()
    }
}

Canvas.prototype.vec_view_in = function(o, d, color) {
    if (!o.length || !d.length)
        return
    color = color || "red"
    this.pen.beginPath()
    this.pen.strokeStyle = color
    this.pen.moveTo(this.X(o[0]), this.Y(o[1]))
    this.pen.lineTo(this.X(o[0] + d[0]), this.Y(o[1] + d[1]))
    this.pen.stroke()
}

Canvas.prototype.graph = function(f, color) {
    if (!f.length)
        return
    color = color || "white"
    this.pen.beginPath()
    this.pen.strokeStyle = color
    this.pen.lineWidth = this.width / f.length / 2
    for (var i = 0; i < f.length; i++) {
        this.pen.moveTo(this.width * i / f.length, this.height)
        this.pen.lineTo(this.width * i / f.length, this.height - f[i] / 10 * this.height)
    }
    this.pen.stroke()
    this.pen.lineWidth = 1
}

Canvas.prototype.view = function(p, color) {
    if (!p.length)
        return
    color = color || "white"
        /* Lay a line between points */
    this.pen.beginPath()
    this.pen.strokeStyle = color
    this.pen.lineWidth = 2
    this.pen.moveTo(this.X(p[0][0]), this.Y(p[0][1]))
    for (var i = 1; i < p.length; i++)
        this.pen.lineTo(this.X(p[i][0]), this.Y(p[i][1]))
    this.pen.stroke()
    this.pen.lineWidth = 1
}

Canvas.prototype.view_in_place = function(where, v, color) {
    if (!where.length || !v.length)
        return
    color = color || "#7BC3EF"
    this.pen.beginPath()
    this.pen.lineWidth = 1
    this.pen.strokeStyle = color
    for (var i = 0; i < v.length; i++) {
        this.pen.moveTo(this.X(where[i][0]), this.Y(where[i][1]))
        this.pen.lineTo(this.X(where[i][0] + v[i][0]), this.Y(where[i][1] + v[i][1]))
    }
    this.pen.stroke()
}


Canvas.prototype.do_onwheel = function(event, that) {
    if (event.deltaY > 0)
        this.scale /= 1.2
        else
        this.scale *= 1.2
    
    this.redraw()
}

Canvas.prototype.do_onmousedown = function(event) {
    if (this.dragging) {
        this.click = true
        this.xc0 = this.xc
        this.yc0 = this.yc
        this.xm0 = event.screenX
        this.ym0 = event.screenY
    }
}

Canvas.prototype.do_onmouseup = function(event) {
    if (this.dragging) {
        this.click = false
        this.xc = this.xc0 - (event.screenX - this.xm0) / this.scale
        this.yc = this.yc0 + (event.screenY - this.ym0) / this.scale
        this.redraw()
    }
}

Canvas.prototype.do_onmousemove = function(event) {
    if (this.dragging && this.click) {
        this.xc = this.xc0 - (event.screenX - this.xm0) / this.scale
        this.yc = this.yc0 + (event.screenY - this.ym0) / this.scale
        this.redraw()
    }
}

Canvas.prototype.listen = function(event_type) {

    var obj = this
    var f = (function() {
        var that = obj
        return function(ev) {
            return Canvas.prototype["do_on" + event_type].apply(that, arguments)
        }
    })()
    this.html.addEventListener(event_type, f)
}

Canvas.prototype.clear = function() {
    this.pen.clearRect(0, 0, this.width, this.height)
}

Canvas.prototype.add = function(name, action, config) {
    if ( typeof (action) != "function")
        throw "Error registering function pointer: " + name
    this.specimen[name] = action
}

Canvas.prototype.remove = function(name) {
    if (!this.specimen[name])
        return /* Name is not taken */
    delete this.specimen[name]
}

Canvas.prototype.reset = function () {
    this.specimen = {}
}

Canvas.prototype.toggle = function(name, action, config) {
    if (this.specimen[name])
        return this.remove(name)
    return Canvas.prototype.add.apply(this, arguments)
}

Canvas.prototype.redraw = function() {
    this.clear()
    this.draw_axes()
    for (var s in this.specimen)
        this.specimen[s]()
}