import { SVG, G } from "./svg.js/src/main.js"

export default {test}

var link_tmp = {node1: null, node2:null}
var line_tmp = null

var draw = SVG().addTo('#svg').size("100%", 500)
draw.mousemove(function(ev){
    if (linking){
	var p = $("#svg").offset()
	if (line_tmp){
	    line_tmp.remove()
	    line_tmp = null
	}
	var x1 = link_tmp.node1.anchor.cx()
	var y1 = link_tmp.node1.anchor.cy()
	var x2 = ev.clientX - p.left
	var y2 = ev.clientY - p.top + $(document).scrollTop()
	line_tmp = draw.line(
	    x1, y1, 0.1 * x1 + 0.9 * x2, 0.1 * y1 + 0.9 * y2
	).stroke({color:"black", width: 2}).attr("z-axis", -10)
    }
})

const centreX = draw.node.clientWidth/2
const centreY = draw.node.clientHeight/2

var meca =  [] // liste des objets mécaniques soumis à des forces
var links = [] // list de liens

const size = {w: 180, h: 36} // taille des étiquettes
const margin = {x: 20, y: 4} // marges des étiquettes

var dirty = true;    // on ne lance pas la mécanique quand dirty est faux
var linking = false; // linking devient vrai quand on est à créer un lien
const seuil = 1.3    // en dessous de ce déplacement, dirty devient faux
const dt = 0.02      // durée en seconde entre deux « tours de mecanique »

var _id = 0           // index pour les identifiants

/**
 * fonction de test : crée seize étiquettes et donne un tour de
 * mecanique
 */
export function test(){
    for (var i = 0; i < 8; i++) {
	new Node(
	    draw,  meca,                     // svg, meca
	    {                                // data
		label: 'mat. inconnue ' + i,
		target: {x: centreX * 1.3, y: centreY},
		color: 'hsl(' + (i*18) + ', 50%, 75%)',
		anchor: "left",
	    }
	)
    }
    for (var i = 0; i < 8; i++){
	new Node(
	    draw, meca,                           // svg, meca
	    {                                     // data
		label: 'option inconnue ' + i,
		target: {x: centreX * 0.7, y: centreY},
		color: 'hsl(' + (180 + i*18) + ', 50%, 75%)',
		anchor: "right",
	    }
	)
    }
    mecanique()
}

/**
 * Noeud du graphe ; seul le premier paramètre est obligatoire
 * le constructeur place un noeud dans une image SVG, sous forme d'un texte,
 * sur un rectangle coloré, sur un rectangle transparent un peu plus grand
 *
 * Paramètres du constructeur :
 *
 * @param svg une instance de SVG
 * @param meca liste de noeuds participant à une simulation "mécanique"
 * @param data un objet avec des propriétés facultatives :
 *  - target: coordonnées {x, y} du centre d'attraction,
 *    centre du canevas par défaut
 *  - label: une étiquette (texte), No title" par défaut
 *  - color une couleur (texte), aléatoire par défaut
 *  - position du noeud {x: abscisse, y: ordonnée}, aléatoire par défaut
 *  - anchor lieu d'attache des liens (top, left, right, ou bottom),
 *    aléatoire par défaut
 **/
class Node {
    constructor (svg, meca, data){
	var l = data.label || "No title"
	var c = data.color || 'hsl(' + (360 * Math.random()) + ', 50%, 75%)'
	var p = data.position || {x: centreX * (0.5 + Math.random()),
				   y : centreY * (0.5 + Math.random())}
	var t = data.target || {x: centreX, y: centreY}
	var a = data.anchor || ["top", "left", "right", "bottom"][Math.floor(4 * Math.random())]

	this.id = "node" + _id++
	this.vx = 0
	this.vy = 0
	this.rect0 = svg.rect(size.w + 2 * margin.x, size.h + 2 * margin.y ).move(p.x, p.y).fill('rgba(255,255,255,0)')
	this.rect = svg.rect(size.w, size.h).move(p.x + margin.x, p.y + margin.y).fill(c).stroke('black')
	this.text = svg.text(l).move(p.x + margin.x + 3, p.y + margin.y + 6)
	
	this.target = {x: t.x - size.w / 2 - margin.x,
		       y: t.y - size.h / 2 - margin.y}
	var ax = 0, ay = 0
	if (a == "top"){
	    ax = size.w / 2 + margin.x
	    ay = margin.y
	}
	if (a == "bottom"){
	    ax = size.w / 2 + margin.x
	    ay = size.h + margin.y
	}
	if (a == "left"){
	    ax = margin.x
	    ay = size.h / 2 + margin.y
	}
	if (a == "right"){
	    ax = size.w + margin.x
	    ay = size.h / 2 + margin.y
	}
	var d = 20 // diamètre
	this.anchor = svg.circle(d).move(p.x + ax - d/2, p.y + ay - d/2).fill("cyan").stroke("black")
	this.anchor.parent = this // lien veurs le noeud
	this.anchor.mouseover(function(){
	    this.fill("red")
	})
	this.anchor.mouseout(function(){
	    this.fill("cyan")
	})
	this.anchor.click(function(ev){
	    if (! linking){
		dirty = false // arrêt des mécaniques
		linking = true
		link_tmp.node1 = this.parent
		$("#linking").show();
		$("#not_linking").hide();
	    } else {
		linking = false
		link_tmp.node2 = this.parent
		$("#linking").hide();
		$("#not_linking").show();
		line_tmp.remove()
		line_tmp = null
		this.parent.vx = 2 * seuil / dt // on bouscule le noeud
		dirty = true; mecanique()       // reprise des mécaniques
		new Link(draw, link_tmp.node1, link_tmp.node2)
	    }
	})
	
	this.group = svg.group()
	this.group.add(this.rect0).add(this.rect).add(this.text)
	this.group.add(this.anchor)
	
	this.group.draggable().on('dragend', function(e) {
	    // relance les « tours de mecanique » après un déplacement
	    // forcé, si on n'est pas en train de tirer un lien
	    if (linking) return
	    dirty = true
	    mecanique()
	})
	this.group.draggable().on('dragstart', function(e) {
	    // stoppe les « tours de mecanique » en début de déplacement
	    dirty = false
	})
	
	if (typeof meca !== 'undefined'){
	    meca.push(this)
	}
    }

    get bbox() {return this.group.bbox}
    get x() {return this.group.x()}
    get y() {return this.group.y()}

    dmove = function(dx, dy){
	this.group.dmove(dx, dy)
    }

    
    /**
     * fonction qui calcule la somme des forces reçues par un noeud,
     * puis affecte son mouvement.
     **/
    force = function(){
	const Kc = 6      // force centripète générale
	const Kv = 5      // frottement fluide
	const Khc = 2.5   // répulsion « hard core »
	const Kp = 0.08   // force périodique pour faire des cases en tableau
	const msize = 1   // multiplicateur pour la taille des cases
	const step = {    // dimension des cases de la grille
	    x: (size.w + 2 * margin.x) * msize,
	    y: (size.h + 2 * margin.y) * msize
	}

	var fx = 0, fy = 0
	
	// force centripète
	var dx = this.x - this.target.x, dy = this.y - this.target.y
	var r = Math.sqrt(dx ** 2 + dy ** 2)
	fx += - Kc * step.x * dx /r
	fy += - Kc * step.y * dy /r

	// frottement fluide
	fx += - Kv * this.vx
	fy += - Kv * this.vy
	
	// répulsion "hardcore" ;
	// direction : d'un rectangle à l'autre
	// intensité : croissante avec l'aire d'intersection
	var self = this
	meca.forEach(function(other){
	    if (other != self) {
		var ddx = self.x - other.x
		var ddy = self.y - other.y
		var area = interArea(self.rect0, other.rect0)
		fx += Khc / step.x * ddx * area
		fy += Khc / step.y * ddy * area
	    }
	})
	
	// force périodique de rappel vers les cases de la grille
	fx += Kp * step.x * (this.x % step.x - step.x / 2)
	fy += Kp * step.y * (this.y % step.y - step.y / 2)
	
	return {x: fx, y: fy}
    }

    /**
     * calcul cinématique : change la position et la vitesse
     * @param f objet {x, y}
     * @param dt intervalle de temps
     **/
    cinematique = function(f,dt){
	// calcul des nouvelles vitesses
	this.vx += f.x * dt // chaque objet possède la masse 1 kg
	this.vy += f.y * dt
	// déplacement de l'objet
	this.dmove(this.vx * dt, this.vy * dt)

	// ajustement des liens
	links.forEach(function(l){
	    adjust(l)
	})
    }
}

/**
 * Lien du graphe ; les trois premiers paramètres sont obligatoires
 * le constructeur crée une ligne entre deux noeuds, et une cible
 * circulaire pour interagir avec le lien
 *
 * Paramètres du constructeur :
 *
 * @param svg une instance de SVG
 * @param node1 premier noeud
 * @param node2 deuxième noeud
 * @param data un objet avec des propriétés facultatives :
 *  - color :  couleur de la cible (jaune par défaut)
 *  - stroke : couleur du tour de cible (bleu marine par défault)
 *  - label :  texte de la cible ("" par défaut)
 **/
class Link{
    constructor (svg, node1, node2, data){
	data = data || {}
	this.id = "link" + _id++
	this.node1 = node1
	this.node2 = node2
	this.fill = data.color || "yellow"
	this.stroke = data.stroke || "navy"
	this.label = data.label || "no label"
	this.line = svg.line(0,0,1,1).stroke({color: this.stroke, width: 2})
	this.target = svg.circle(30).fill(this.fill).stroke({color: this.stroke})
	this.text = null
	this.title = svg.element("title")
	this.group = svg.group()
	this.group.add(this.target).add(this.title)	
	adjust(this) // gère le placement, crée le texte de deux lettres
	var _del_link = function(l){
	    return function(){
		del_link(l.id)
		$("#link-dialog").dialog('close')
	    }
	} (this)
	var _update_label = function(l){
	    return function update_label(){
		l.label = $("#input-label").val()
		adjust(l)
		$("#link-dialog").dialog('close')
	    }
	} (this)
	var _validate = function(ev){
	    if (ev.which == 13){
		event.preventDefault()
		$("#link-dialog .label_button").click()
	    }
	}
	var self = this
	this.group.click(function (){
	    var d = $("#link-dialog")
	    d.find("span.ident").text(self.id)
	    $("#input-label").val(self.label)
	    $("#input-label").off("keypress").on("keypress", _validate )
	    d.find(".del_button").off("click").on("click", _del_link)
	    d.find(".label_button").off("click").on("click", _update_label)
	    d.dialog({width: 500})
	})
	links.push(this)
    }

}

/**
 * réajuste l'affichage d'un lien quand les noeuds ont bougé
 * param l le lien à ajuster
 */
function adjust(l){
    var x1 = l.node1.anchor.cx()
    var y1 = l.node1.anchor.cy()
    var x2 = l.node2.anchor.cx()
    var y2 = l.node2.anchor.cy()
    l.line.attr({x1: x1, y1: y1, x2: x2, y2: y2})
    l.target.attr({cx: (x1 + x2) / 2, cy: (y1 + y2) / 2})
    if (l.text) l.text.remove()
    l.title.node.innerHTML = l.label
    l.text = draw.text(l.label.substring(0,2).toUpperCase())
    l.text.move(l.target.cx() - 11, l.target.cy()-11)
    l.group.add(l.text)

}

/**
 * aire d'intersection de deux rectangles
 * @param r1 premier rectangle
 * @param r2 deuxième rectangle
 * @return aire de l'intersection
 **/
function interArea(r1,r2){
    var top    = Math.max(r1.y(), r2.y())
    var bottom = Math.min(r1.y() + r1.height(), r2.y() + r2.height())
    var left   = Math.max(r1.x(), r2.x())
    var right  = Math.min(r1.x() + r1.width(), r2.x() + r2.width())
    if (bottom > top && right > left){
	return (bottom -top) * (right - left)
    }
    return 0
}

/**
 * fonction qui donne un « tour de mecanique »,
 * et modifie donc par effet de bord les positions et vitesses des objets
 **/
function mecanique(){
    if (! dirty) return
    var vmax = 0
    meca.forEach(function (node){
	node.cinematique(node.force(), dt)
	var v = Math.sqrt(node.vx ** 2 + node.vy ** 2)
	vmax = Math.max(v, vmax)
    })
    if (vmax * dt < seuil) {
	dirty = false
    } else {
	setTimeout(mecanique, 1000*dt)
    }
}

/**
 * suppression d'un lien
 * @param id l'identifiant du lien
 **/
function del_link(id){
    links.forEach(function(l,i){
	if (l.id == id){
	    l.line.remove()
	    l.target.remove()
	    l.text.remove()
	    links.splice(i, 1)
	    l = null
	}
    })
}
