<script>

// https://brm.io/matter-js/docs/classes/Engine.html
// import {
//   // Engine,
//   // Render,
//   // World,
//   Bodies,
//   Body,
//   Events,
//   Composite,
//   // Composites,
//   // Constraint,
//   // Vertices,
//   // Mouse,
//   // MouseConstraint,
//   // Query,
//   // Common
// } from "matter-js";

import Matter from "matter-js";
import MatterAttractors from "matter-attractors";
// Matter.use(MatterAttractors);
// import polydecomp from "poly-decomp";

import paper from 'paper';

// import { easeInOutQuad, easeInOutQuart } from 'easing-utils';
import Tween from "@tweenjs/tween.js";

import { mapState, mapActions } from 'pinia'
import { ConcernementsStore } from '@/stores/concernements'
import { CommonStore } from '@/stores/common'

export default {
  inject: ['canvasMap', 'matterEngine'],
  data() {
    return {
      id: null,
      entities: null,
      canvas: null,
      ctx: null,
      pos : {
        x: 0,
        y: 0
      },
      ray: 100,
      time: 0,
      salientPoints: [],
      scale: 1,
      prev_scale: 1,
      opacity: 0,
      tween: null,
      body: null,
      body_parts: [],
      constraint: null,
      is_hover: false,
      //
      paper_main_object: {},
      paper_groups: {}
    }
  },
  props: ['concernement', 'is_opened'],
  computed: {
    ...mapState(ConcernementsStore,['map_mode']),
    ...mapState(ConcernementsStore,['concernementsByID']),
    ...mapState(ConcernementsStore,['opened_concernement']),
    ...mapState(ConcernementsStore,['opened_entite_id']),
    ...mapState(CommonStore,['hover_elmt']),
    ...mapState(CommonStore,['map_item_ray']),
    ...mapState(CommonStore,['cartouch_width']),
    ...mapState(CommonStore,['paper_symbol_definitions'])
  },
  created () {
    // console.log(`ConcernementsMapItem ${this.concernement.id} created`, this.canvasMap, this.matterEngine);
    this.id = this.concernement.id
    this.entites = this.concernement.entites
    this.entites_byid = this.concernement.entites_byid
    
    // disable concernement if less than 3 entite
    if(this.entites.length < 3){
      this.hideShowConcernement(this.concernement.id, false);
    } else{
      // record canvas and ctx for rendering (drawing)
      this.canvas = this.canvasMap.canvas
      this.ctx = this.canvasMap.ctx
      
      // define the ray regarding the width and height of screen
      this.ray = this.map_item_ray;
      console.log(`this.ray: ${this.ray}`);
      // 
      this.parsePoints()
      // this.getSalientPoints()
      this.getJarvisEnvelopeConvexe()

      // if (this.canvasMap) {
        // define init position of the item
        this.pos = this.getRandomPos();
        //
        this.initMatterBody()
        //
        this.initPaperObjects()
      // }  
    }
  },
  // mounted() {
  //   console.log(`ConcernementsMapItem ${this.concernement.id} mounted`, this.canvasMap.canvas);
    
  // },
  watch: {
    // canvasMap (n, o) {
    //   console.log("concernementItem watch canvasMap", o, n);
    // }
    canvasMap: {
      handler (n, o){
        // console.log("concernementItem watch canvasMap.ctx", typeof this.canvas, o, n);
        if (!this.canvas) {
          this.initCanvasMap()
        }
      },
      deep: true
    },
    is_opened: {
      handler (n, o) {
        if(n){ // opened
          this.openClose(true);
        }else{ // closed
          this.openClose(false);
        }
      },
      deep: true
    },
    opened_concernement: {
      handler (n, o) {
        if (!n) {
          this.applyShuffleForces();
        }
      },
      deep: true
    },
    map_mode: {
      handler (n, o) {
        // console.log('watch map_mode', o, n);
        if (n === 'terraindevie' && !this.opened_concernement) {
          this.applyShuffleForces(); // apply a little force to check the map when returning to terrain de vie
        }
      },
      deep: true
    },
    hover_elmt: {
      handler (n, o) {
        // console.log(`watch hover_elmt ${this.id}`, o, n);
        if (n && n.type === 'concernement' && n.id === this.id) {
          this.is_hover = true;
        } else {
          this.is_hover = false;
        }
      },
      deep: true
    }
  },
  methods: {
    ...mapActions(CommonStore,['setHoverElmt']),
    ...mapActions(ConcernementsStore,['openCloseConcernements']),
    ...mapActions(ConcernementsStore,['hideShowConcernement']),
    // getResponsiveRay(){
    //   return Math.min(this.canvas.width, this.canvas.height) * 0.08;
    // },
    parsePoints (){
      // converts data (menace/maintien, actuel/future, prise) into atcual position x,y
      for (let i = 0; i < this.entites.length; i++) {
        let entite = this.entites[i]
        // console.log('entite', entite);
        
        this.entites[i].display = {
          alpha: null,
          ray: null
        }
        // RAYON
        // https://stackoverflow.com/questions/5731863/mapping-a-numeric-range-onto-another
        // slope = (output_end - output_start) / (input_end - input_start)
        // output = output_start + slope * (input - input_start)
        // from range 0 -> 100 to range 0 -> this.ray
        let init_max = 100
        let slope = this.ray / init_max
        this.entites[i].display.ray = slope * (init_max - entite.prise);
        // if (this.concernement.id === 28) {
        //   console.log(`entity prise: ${entite.prise} | ray: ${this.entites[i].display.ray}`);
        // }
        

        // ANGLE
        // -90 <= mm <= 90
        if (entite.actuelfuture) {
          // future en haut : 180 <= a <= 360
          // from -90 -> 90 to range 180 -> 360
          this.entites[i].display.alpha = entite.menacemaintien + 270
        } else {
          // actuel: en bas : O <= a <= 180
          // from -90 -> 90 to range 180 -> 0
          this.entites[i].display.alpha = -1 * entite.menacemaintien + 90
        }

        // POSITION X Y (par rapport au centre du concernement)
        this.entites[i].display.pos = {
          x: this.entites[i].display.ray * Math.cos(this.entites[i].display.alpha * (Math.PI/180)),
          y: this.entites[i].display.ray * Math.sin(this.entites[i].display.alpha * (Math.PI/180))
        }

        this.entites_byid[entite.entite.id].display = this.entites[i].display;
      }
    },
    getSalientPoints_OLD() {
      // debugger
      // console.log(this.entites);
      let arc = 360/30;
      // loop through arcs
      // for (let i = 360/arc; i >= 0 ; i--) {
      for (let i = 0; i <= 360/arc ; i++) {
        // loop through entities to find the farest on the arc
        let max_r = 0;
        let farest = null;
        for (let j = 0; j < this.entites.length; j++) {
          let entite = this.entites[j];
          if(arc*i <= entite.display.alpha && entite.display.alpha <= arc*i+arc) { // if entity is in arc
            if (entite.display.ray > max_r) { //  && entite.display.ray > this.ray/2 // and farest from minimu
              // if entity is farest from precedent one 
              max_r = entite.display.ray;
              // recalcul x & y to get a little padding between entite and contour by increasing ray
              farest = {
                alpha: entite.display.alpha,
                ray: entite.display.ray,
                pos: {
                  x: (entite.display.ray + 3) * Math.cos(entite.display.alpha * (Math.PI/180)),
                  y: (entite.display.ray + 3) * Math.sin(entite.display.alpha * (Math.PI/180))
                }
              };
            }
          }
        }
        if (farest) {
          this.salientPoints.push(farest)
        }
      }
      // console.log(`this.salientPoints ${this.concernement.id}`, this.salientPoints);
    },
    getJarvisEnvelopeConvexe(){
      // console.log(`getJarvisEnvelopeConvexe ${this.id}`, this.entites.length);
      // https://www.geeksforgeeks.org/convex-hull-using-jarvis-algorithm-or-wrapping/
      // find the most left point
      let l, min_x = null;
      for (let i = 0; i < this.entites.length; i++) {
        let entite = this.entites[i];
        let x = entite.display.ray * Math.cos(entite.display.alpha * (Math.PI/180));
        if(!min_x || min_x > x){
          l = i;
          min_x = x;
        }
      }
      // Start from leftmost point, keep moving clockwise
      // until reach the start point again.  This loop runs O(h)
      // times where h is number of points in result or output.
      let p = l, q;
      do {
        // console.log(`do while ${this.id}`, p);
        // Add current point to result
        let entite = this.entites[p];
        let farest = {
          alpha: entite.display.alpha,
          ray: entite.display.ray,
          pos: {
            x: (entite.display.ray) * Math.cos(entite.display.alpha * (Math.PI/180)),
            y: (entite.display.ray) * Math.sin(entite.display.alpha * (Math.PI/180))
          }
        };
        this.salientPoints.push(farest);


        // Search for a point 'q' such that 
        // orientation(p, q, x) is clockwise 
        // for all points 'x'. The idea is to keep 
        // track of last visited most clockwise
        // point in q. If any point 'i' is more 
        // clockwise than q, then update q.
        q = (p + 1) % this.entites.length;
        for (let i = 0; i < this.entites.length; i++) {
          let p_x = (this.entites[p].display.ray) * Math.cos(this.entites[p].display.alpha * (Math.PI/180));
          let p_y = (this.entites[p].display.ray) * Math.sin(this.entites[p].display.alpha * (Math.PI/180));
          let i_x = (this.entites[i].display.ray) * Math.cos(this.entites[i].display.alpha * (Math.PI/180));
          let i_y = (this.entites[i].display.ray) * Math.sin(this.entites[i].display.alpha * (Math.PI/180));
          let q_x = (this.entites[q].display.ray) * Math.cos(this.entites[q].display.alpha * (Math.PI/180));
          let q_y = (this.entites[q].display.ray) * Math.sin(this.entites[q].display.alpha * (Math.PI/180));
          
          let val = (i_y - p_y) * (q_x - i_x) - (i_x - p_x) * (q_y - i_y);
          
          // If i is more clockwise than current q, then update q
          if (val > 0){
            q = i;
          }
        }

        // Now q is the most clockwise with
        // respect to p. Set p as q for next iteration, 
        // so that q is added to result 'hull'
        p = q;
      } while (p != l);

    },
    getRandomPos(){
      let pad = 200;
      // if (this.concernement.id === 56) {
      //   return {
      //     x: Math.random() > 0.5 ? pad : this.canvas.width - pad,
      //     y: pad + this.ray/2 + Math.random()*(this.canvas.height - this.ray - pad)
      //   };
      // } else {
        return {
          x: pad + this.getGaussianRandom()*(this.canvas.width - pad*2),
          y: pad + this.getGaussianRandom()*(this.canvas.height - pad*2)
        };
      // }
    },
    getGaussianRandom(){
      // INFO https://stackoverflow.com/a/49434653
      let u = 0, v = 0;
      while(u === 0) u = Math.random(); //Converting [0,1) to (0,1)
      while(v === 0) v = Math.random();
      let num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
      num = num / 10.0 + 0.5; // Translate to 0 -> 1
      if (num > 1 || num < 0) return this.getGaussianRandom() // resample between 0 and 1
      return num
    },
    // MATTER BODY
    initMatterBody (){
      
      // MATTER
      // create the matter body and add it to the engine
      if (!this.body) {
        // console.log('concernementItem creating body');

        // INFO https://github.com/liabru/matter-attractors/issues/8        
        // INFO https://github.com/liabru/matter-attractors/blob/master/index.js
        // INFO https://github.com/liabru/matter-attractors/blob/master/build/matter-attractors.js#L180
        MatterAttractors.Attractors.gravityConstant = -20;

        // Create parts of the body : main big circle & entities
        // INFO map a range of numbers to another range of numbers https://stackoverflow.com/a/46462321
        let entite_range = [3, 100];
        let ray_range = [this.ray*0.8,this.ray*1.2];
        let ray = ray_range[0] + (this.entites.length - entite_range[0]) * (ray_range[1] - ray_range[0]) / (entite_range[1] - entite_range[0]);
        this.body_parts = [
          Matter.Bodies.circle(0, 0, ray, {
            item_type: 'concernement',
            id: this.concernement.id,
          })
        ];
        
        // INFO map a range of numbers to another range of numbers https://stackoverflow.com/a/46462321
        let mass_range = [5,15];
        let mass = mass_range[0] + (this.entites.length - entite_range[0]) * (mass_range[1] - mass_range[0]) / (entite_range[1] - entite_range[0]);

        // create the body
        this.body = Matter.Body.create({
          parts: this.body_parts,
          item_type: 'concernement',
          id: this.concernement.id,
          frictionAir: 0,
          // mass: Math.pow(3, this.entites.length),
          // mass: 10,
          mass: mass,
          restitution: 0.06,
          collisionFilter: {
            group: -1
          },
          plugin: {
            attractors: [
              // // there is a built in helper function for Newtonian gravity!
              // // you can find out how it works in index.js
              MatterAttractors.Attractors.gravity
              
              // function(bodyA, bodyB) {
              //   var force = {
              //     x: (bodyA.position.x - bodyB.position.x) * 1e-6,
              //     y: (bodyA.position.y - bodyB.position.y) * 1e-6
              //   }
              //   // apply force to both bodies
              //   Matter.Body.applyForce(bodyA, bodyA.position, force);
              //   Matter.Body.applyForce(bodyB, bodyB.position, Matter.Vector.neg(force));
              // }

              // INFO https://github.com/liabru/matter-attractors/blob/master/build/matter-attractors.js#L192
              // function (bodyA, bodyB){
              //   // use Newton's law of gravitation
              //   var bToA = Matter.Vector.sub(bodyB.position, bodyA.position),
              //       distanceSq = Matter.Vector.magnitudeSquared(bToA) || 0.0001,
              //       normal = Matter.Vector.normalise(bToA),
              //       magnitude = -MatterAttractors.Attractors.gravityConstant * (bodyA.mass * bodyB.mass / distanceSq),
              //       force = Matter.Vector.mult(normal, magnitude);

              //   // to apply forces to both bodies
              //   Matter.Body.applyForce(bodyA, bodyA.position, Matter.Vector.neg(force));
              //   Matter.Body.applyForce(bodyB, bodyB.position, force);
              // }
            ]
          }
        });
        Matter.Body.setPosition(this.body, this.pos);

        // add init velocity
        this.setInitBodyVelocity()
        // console.log('concernementItem mass', this.body.mass);
        Matter.Composite.add(this.matterEngine.world, this.body);
        // console.log('concernement body', this.body);

        // listen for afterUpdate event from Matter.Engine object
        Matter.Events.on(this.matterEngine, "beforeUpdate", this.onBeforeEngineUpdate);
        Matter.Events.on(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
      }
    },
    setInitBodyVelocity(){
      let delta = 50;
      Matter.Body.setVelocity(this.body, {
        x: -delta + Math.random()*delta*2,
        y: -delta + Math.random()*delta*2
      });
    },
    // PAPER OBJECTS
    initPaperObjects(){
      // the main paper group containing all paper graphical items for one concernement
      this.paper_main_object = new paper.Group({
        pivot: new paper.Point(this.pos),
        cid: this.id
      });

      // the sub items for one concernement
      this.addNewPaperSymbolInstance('boussole_bg');
      this.paper_main_object.addChild(this.setPaperContour());
      this.paper_main_object.addChild(this.setPaperEntites());

      if (this.concernement.has_puissancedagir) {
        this.addNewPaperSymbolInstance('puissanceagir_bg');
        this.addNewPaperSymbolInstance('puissanceagir_icon');
        this.paper_main_object.addChild(this.setPaperPuissanceagirBesoins());
      }
      if (this.concernement.has_agissantes) {
        this.paper_main_object.addChild(this.setPaperAgissantes());
      }
      if (this.concernement.has_doleance) {
        this.addNewPaperSymbolInstance('doleance_bg');
        this.addNewPaperSymbolInstance('doleance_icon');
        this.paper_main_object.addChild(this.setPaperDoleanceSteps());
      }
      // console.log(`initPaperObjects ${this.id}`, this.paper_main_object);

      this.initPaperEvents()
    },
    addNewPaperSymbolInstance(name){
      let instance = new paper.SymbolItem(this.paper_symbol_definitions[name]); // , {x:0,y:0}
      instance.name = name;
      // instance.pivot = new paper.Point({x:0,y:0});
      instance.position = this.pos;
      instance.scale = this.scale;
      // instance.locked = true;
      this.paper_main_object.addChild(instance);
    },
    setPaperContour(){
      // console.log(`setPaperContour ${this.concernement.id}`);
      let getPaddedRoundedSegments = (b,a,c,d) => {
        const ac  = { x: c.x - a.x, y: c.y - a.y } // get ac vecteur
        const lac = Math.sqrt(Math.pow(ac.x, 2) + Math.pow(ac.y, 2)); // get ac longueur ac
        const ab  = { x: b.x - a.x, y: b.y - a.y } // get ab vecteur
        const lab = Math.sqrt(Math.pow(ab.x, 2) + Math.pow(ab.y, 2)); // get ab longeur
        const vab = { x: ab.x/lab, y: ab.y/lab } // get unit vecteur ab
        const an  = { x: vab.x*lac, y: vab.y*lac } // get an vecteur
        const n   = { x: a.x + an.x, y: a.y+an.y } // get n point
        const m   = { x: (c.x + n.x)/2, y: (c.y + n.y)/2 } // get nc midle point
        const ma  = { x:a.x - m.x, y: a.y - m.y } // get ma vecteur
        const lma = Math.sqrt(Math.pow(ma.x, 2)+Math.pow(ma.y, 2)) // get longeur m->a
        const vma = { x: ma.x/lma, y: ma.y/lma } // get ma vecteur unitaire
        // console.log(`vma x:${vma.x}, y:${vma.y}`);
        const pad = 4; // exterior padding
        // the final padded point
        const pa  = [
          this.pos.x+(a.x+vma.x*pad)*this.scale,
          this.pos.y+(a.y+vma.y*pad)*this.scale
        ]
        
        // handles
        const delta = 0.05;
        // handle IN
        const hli   = Math.abs(lab)*delta;            // handle longeur
        const vnai  = { x: -vma.y, y: vma.x }     // get the ma normal unit vector IN
        const hai   = [ vnai.x*hli, vnai.y*hli ]; // get the handleIn point
        // handle OUT
        const hlo   = Math.abs(lac)*delta;            // handle longeur
        const vnao  = { x: vma.y, y: -vma.x }     // get the ma normal vector Out
        const hao   = [ vnao.x*hlo, vnao.y*hlo ]; // get the handleOut point

        return new paper.Segment({
          point: pa,
          handleIn: hai,
          handleOut: hao
        })
        
      }
      const first_point = getPaddedRoundedSegments(
        this.salientPoints[this.salientPoints.length-1].pos,
        this.salientPoints[0].pos,
        this.salientPoints[1].pos
      );
      let segments = [first_point]; 
      for (let j = 1; j < this.salientPoints.length-1; j++) {
        // segments.push([this.pos.x+this.salientPoints[j].pos.x*this.scale*gap, this.pos.y+this.salientPoints[j].pos.y*this.scale*gap])
          segments.push(getPaddedRoundedSegments(
            this.salientPoints[j-1].pos,
            this.salientPoints[j].pos,
            this.salientPoints[j+1].pos
          ))
      }
      const last_point = getPaddedRoundedSegments(
        this.salientPoints[this.salientPoints.length-2].pos,
        this.salientPoints[this.salientPoints.length-1].pos,
        this.salientPoints[0].pos
      );
      segments.push(last_point)
      segments.push(first_point)
      

      const contrs = new paper.Path({
        name: 'contours',
        segments: segments,
        fillColor: 'rgba(255,255,255,0.4)',
        // selected: true,
        strokeColor: '#fff',
        strokeWidth: 1,
        pivot: new paper.Point(this.pos),
        cid: this.id
      });

      return contrs;
    },
    setPaperEntites(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'entites'
      });
      for (let i = 0; i < this.entites.length; i++) {
        // use paper symbol
        let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
        instance.name = 'entite';
        instance.position = new paper.Point([this.pos.x + this.entites[i].display.pos.x, this.pos.y + this.entites[i].display.pos.y]);
        instance.fillColor = '#000';
        instance.item_id = this.entites[i].entite.id;
        instance.item_type = 'entite';
        instance.is_symbol_instance = true;
        g.addChild(instance)
      }
      return g;
    },
    setPaperAgissantes(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'agissantes'
      });
      for (let i = 0; i < this.entites.length; i++) {
        if (this.entites[i].entite.agissante) {
          let instance = new paper.SymbolItem(this.paper_symbol_definitions['entite']);
          instance.name = 'entite';
          instance.position = new paper.Point([this.pos.x + this.entites[i].display.pos.x, this.pos.y + this.entites[i].display.pos.y]);
          instance.fillColor = '#000';
          instance.item_id = this.entites[i].entite.id;
          instance.item_type = 'entite';
          instance.is_symbol_instance = true;
          g.addChild(instance)
        }
      }
      return g;
    },
    setPaperPuissanceagirBesoins(){
      let g = new paper.Group({
        pivot: new paper.Point(this.pos),
        name: 'puissanceagir_besoins'
      });

      let res_fields = ['qui','quoi','ou','avec'];
      let arc = (360 / 16);               // unit arc
      let r = (this.ray * this.scale)/5;  // unit ray
      let br = r - r/3;                   // besoin ray

      for (let i = 0; i < this.concernement.besoins.length; i++) {
        let start_a = arc * i;              // angle depart (for reponses)
        let center_a = start_a + arc/2;     // angle central
        let x = Math.cos(center_a*(Math.PI/180)) * br;
        let y = Math.sin(center_a*(Math.PI/180)) * br;

        // use paper symbol
        let besoin = new paper.SymbolItem(this.paper_symbol_definitions['besoin']);
        besoin.position = new paper.Point([this.pos.x + x, this.pos.y + y]);
        besoin.item_id = this.concernement.besoins[i].id;
        besoin.item_cid = this.concernement.id;
        besoin.item_type = 'besoin';
        besoin.is_symbol_instance = true;
        g.addChild(besoin)

        let res_arc = arc / (1 + this.concernement.besoins[i].reponses.length); // unit arc for responses depending responses number
        for (let j = 0; j < this.concernement.besoins[i].reponses.length; j++) {
          let res_a = start_a + res_arc * (j+1); // angle for response line
          for (let f = 0; f < res_fields.length; f++) { // loop through all 4 fields, keep only the last one filled
            if( this.concernement.besoins[i].reponses[j][res_fields[f]] // if field filled
              && (f === res_fields.length -1 || !this.concernement.besoins[i].reponses[j][res_fields[f+1]]) // and is last field or last field filled
              ){
              let rr = this.ray * this.scale - r*f - r/2; // reponse field ray
              let rx = Math.cos(res_a*(Math.PI/180)) * rr;
              let ry = Math.sin(res_a*(Math.PI/180)) * rr;

              // use paper symbol
              let reponse = new paper.SymbolItem(this.paper_symbol_definitions['reponse']);
              reponse.position = new paper.Point([this.pos.x + rx, this.pos.y + ry]);
              reponse.item_id = this.concernement.besoins[i].reponses[j].id;
              reponse.item_bid = this.concernement.besoins[i].id;
              reponse.item_cid = this.concernement.id;
              reponse.item_type = 'reponse';
              reponse.is_symbol_instance = true;
              g.addChild(reponse)
            }
          }
        }
      }

      return g;
    },
    setPaperDoleanceSteps(){
      let g = new paper.Group({
        pivot: new paper.Point({x:0,y:0}),
        name: 'doleance_steps'
      });
      let doleance = this.concernement.doleances[0];
      let all_fields = [
        [
          'leprobleme',
          'lenquete',
          {
            fieldname: 'groupesinterets',
            fields: [
                    'groupe_interets',
                    'accorder_interets',
                    'formuler',
                    ] 
          }
        ],
        [
          'entite_addresse_doleance',
          'comment_ennonce_doleance',
          'aqui_addresse_doleance',
          {
            fieldname: 'reception_traitement',
            fields: [
                    'entite_adressee',
                    'doleance_formulee',
                    'traite_doleance',
                    'entite_recoit_doleance',
                    ]
          }
        ],
        [
          'entites_decisionnaires',
          'decision_formule',
          {
            fieldname: 'mise_en_oeuvre_decision',
            fields: [
                    'entite_adresse_decision',
                    'formule_decision',
                    'entite_metenoeuvre_decisio',
                    ]
          }
        ],
        [
          'entite_adresse_application',
          'aqui_adresse_decision',
          'comment_formule_decision',
          {
            fieldname: 'receptions_et_applications',
            fields: [
                    'applique_decision',
                    'formule_decision_applic',
                    'entite_recoit_decision',
                    ]
          },
          'probleme_initial_resolu',
          'oui_nouvelle_situation',
          'non_adresse_doleance',
        ]
      ];
      
      var r = this.ray * this.scale * 0.8;  // ray
      var dr = r/2;                         // demi ray
      // var pcr = 2*this.scale;               // petits cercle rayon

      // https://fr.wikipedia.org/wiki/Trigonom%C3%A9trie#/media/Fichier:Unit_circle_angles_color.svg
      // https://fr.wikipedia.org/wiki/Identit%C3%A9_trigonom%C3%A9trique_pythagoricienne#Preuve_utilisant_le_cercle_unit%C3%A9
      // radians = degrees * (pi/180)
      // degrees = radians * (180/pi)
      // Points for 45° axes
      let m = Math.sin(45*(Math.PI/180)) * r;   // x = y for rayon
      let n = Math.sin(45*(Math.PI/180)) * r/2; // x = y for demi rayon

      let o = Math.cos(22.5*(Math.PI/180)) * r; // x @ 22.5° for rayon
      let p = Math.sin(22.5*(Math.PI/180)) * r; // y @ 22.5° for rayon
      let o_d = Math.cos(22.5*(Math.PI/180)) * r/2; // x @ 22.5° for demi rayon
      let p_d = Math.sin(22.5*(Math.PI/180)) * r/2; // y @ 22.5° for demi rayon

      // let fontsize = 4;
      
      // //
      // // POINTS CARDINAUX
      // //
      // // le problème
      // //
      // g.addChild(new paper.Path.Circle({
      //   center: [this.pos.x, this.pos.y + r],
      //   radius: 0.5,
      //   style: {
      //     fillColor: '#fff'
      //   }
      // }));
      // g.addChild(new paper.Path.Line({
      //   from: [this.pos.x, this.pos.y + r],
      //   to: [this.pos.x, this.pos.y + r + 8],
      //   strokeColor: '#fff'
      // }));
      // g.addChild(new paper.PointText({
      //     point: [this.pos.x, this.pos.y + r + 10],
      //     content: "Début du cercle\nLe problème (injustice, indignation, plainte...)",
      //     fontSize: fontsize,
      //     fillColor: '#000',
      //     justification: 'center'
      // }));
      // g.addChild(new paper.PointText({
      //   point: [this.pos.x, this.pos.y + r + 16],
      //   content: doleance['leprobleme'],
      //   fontSize: 2.3,
      //   fillColor: '#fff',
      //   justification: 'center'
      // }));
      
      //
      // CAMENBERT STATIQUES
      //
      // l'enquete
      //
      // camenbert
      let cam = new paper.Path({
        strokeColor: '#fff',
        strokeWidth: 2,
        fillColor: "rgba(255, 255, 255, 0.4)",
        item_type: 'doleance_step',
        item_id: 'lenquete'
      });
      cam.add({x: this.pos.x , y: this.pos.y + dr});
      cam.lineTo({x: this.pos.x, y: this.pos.y + r});
      cam.arcTo({x: this.pos.x - p, y: this.pos.y + o}, {x: this.pos.x - m, y: this.pos.y + m});
      cam.lineTo({x: this.pos.x - n, y: this.pos.y + n});
      cam.arcTo({x: this.pos.x - p_d, y: this.pos.y + o_d}, {x: this.pos.x , y: this.pos.y + dr});
      // texte
      // enquete.addChild(new paper.PointText({
        //   point: {
          //     x:this.pos.x + Math.sin(22.5*(Math.PI/180)) * r*0.75,
          //     y:this.pos.y + Math.cos(22.5*(Math.PI/180)) * r*0.75
          //   },
          //   content: doleance['lenquete'],
          //   fontSize: fontsize,
      //   fillColor: '#fff',
      //   justification: 'center',
      //   locked: true
      // }));
      g.addChild(cam);
      
      //
      // probleme_initial_resolu
      //
      // camenbert
      let rescam = new paper.Path({
        strokeColor: '#fff',
        strokeWidth: 2,
        fillColor: "rgba(255, 255, 255, 0.4)",
        closed: true,
        item_type: 'doleance_step',
        item_id: 'probleme_initial_resolu',
      });
      rescam.add({x: this.pos.x + n, y: this.pos.y + n});
      rescam.lineTo({x: this.pos.x + m , y: this.pos.y + m});
      rescam.arcTo({x: this.pos.x + p, y: this.pos.y + o}, {x: this.pos.x, y: this.pos.y + r});
      rescam.lineTo({x: this.pos.x, y: this.pos.y + dr});
      rescam.arcTo({x: this.pos.x + p_d, y: this.pos.y + o_d}, {x: this.pos.x + n, y: this.pos.y + n});
      // texte
      // resolution.addChild(new paper.PointText({
        //   point: {
          //     x:this.pos.x - Math.sin(22.5*(Math.PI/180)) * r*0.75,
          //     y:this.pos.y + Math.cos(22.5*(Math.PI/180)) * r*0.75
          //   },
          //   content: doleance['probleme_initial_resolu'] ? doleance['oui_nouvelle_situation'] : doleance['non_adresse_doleance'],
          //   fontSize: fontsize,
          //   fillColor: '#fff',
          //   justification: 'center'
          // }));
      g.addChild(rescam)
      
      //
      // MULTIPLE FIELDS
      //
      let multiple_fields = [
        { field_name: 'groupesinterets',            arc: 45, decalage: -45 },
        { field_name: 'reception_traitement',       arc: 90, decalage: -90 },
        { field_name: 'mise_en_oeuvre_decision',    arc: 90, decalage: -180 },
        { field_name: 'receptions_et_applications', arc: 45, decalage: 90 }
      ]

      multiple_fields.forEach((mf, j) => {
        for (let i = 0, l = doleance[mf.field_name].length, a = mf.arc/l; i < l; i++) {
          // let gi = new paper.Group({
          //   item_type: 'doleance_step',
          //   item_id: 'lenquete',
          //   item_field: mf.field_name,
          //   item_index: i
          // });
          //camenbert
          let x1=  this.pos.x + Math.sin((mf.decalage- a*i)*(Math.PI/180)) * dr,
              y1=  this.pos.y + Math.cos((mf.decalage- a*i)*(Math.PI/180)) * dr;
          let x2=  this.pos.x + Math.sin((mf.decalage- a*i)*(Math.PI/180)) * r,
              y2=  this.pos.y + Math.cos((mf.decalage- a*i)*(Math.PI/180)) * r;
          let x3=  this.pos.x + Math.sin((mf.decalage- a*(i+1))*(Math.PI/180)) * r,
              y3=  this.pos.y + Math.cos((mf.decalage- a*(i+1))*(Math.PI/180)) * r;
          let x3t= this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r,
              y3t= this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r;
          let x4=  this.pos.x + Math.sin((mf.decalage- a*(i+1))*(Math.PI/180)) * dr,
              y4=  this.pos.y + Math.cos((mf.decalage- a*(i+1))*(Math.PI/180)) * dr;
          let x4t= this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * dr,
              y4t= this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * dr;
          let x5=  this.pos.x + Math.sin((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r*0.75,
              y5=  this.pos.y + Math.cos((mf.decalage- a*(i+0.5))*(Math.PI/180)) * r*0.75;
          let p = new paper.Path({
            strokeColor: '#fff',
            strokeWidth: 2,
            fillColor: "rgba(255, 255, 255, 0.4)",
            item_type: 'doleance_step',
            item_id: `${mf.field_name}-${i}`,
            item_field: mf.field_name
          });
          p.add([x1,y1]);
          p.lineTo([x2,y2]);
          p.arcTo([x3t,y3t], [x3,y3]);
          p.lineTo([x4,y4]);
          p.arcTo([x4t,y4t], [x1,y1]);
          g.addChild(p);
          // gi.addChild(p);
          // // text
          // gi.addChild(new paper.PointText({
          //   point: {x:x5,y:y5},
          //   content: `${j}-${i}`,
          //   fontSize: fontsize,
          //   fillColor: '#fff',
          //   justification: 'center'
          // }))

          // g.addChild(gi)
        }
      });

      return g;
    },
    // PAPER VISIBILITY
    handlePaperVisibility(){
      // contours focused
      if (!this.isFocused()){
        this.paper_main_object.children['contours'].fillColor = "rgba(255,255,255,0.1)";
      }else{
        this.paper_main_object.children['contours'].fillColor = "rgba(255,255,255,0.4)";
        if (this.is_hover) {
          this.paper_main_object.children['contours'].strokeColor = "#01ffe2";
          this.paper_main_object.children['contours'].strokeWidth = 2;
        }else{
          this.paper_main_object.children['contours'].strokeColor = "#fff";
          this.paper_main_object.children['contours'].strokeWidth = 1;
        }
      }

      // contours visibility
      if (!this.is_opened 
        || (this.is_opened && this.map_mode !== "puissancedagir" && this.map_mode !== "doleancer")) {
        this.paper_main_object.children['contours'].visible = true;
      } else {
        this.paper_main_object.children['contours'].visible = false;
      }

      // backgrounds
      if (this.is_opened) {
        // hide all bgs
        this.paper_main_object.children.boussole_bg.visible = false;
        if (this.concernement.has_puissancedagir) {
          this.paper_main_object.children.puissanceagir_bg.visible = false;
        }
        if (this.concernement.has_doleance) {
          this.paper_main_object.children.doleance_bg.visible = false;
        }
        // choose wich one to show, if one
        switch (this.map_mode) {
          case 'terraindevie':
            this.paper_main_object.children.boussole_bg.visible = true;
            break;
          case 'puissancedagir':
            if (this.concernement.has_puissancedagir) {
              this.paper_main_object.children.puissanceagir_bg.visible = true;
            }
            break;
          case 'doleancer':
            if (this.concernement.has_doleance) {
              this.paper_main_object.children.doleance_bg.visible = true;
            }
            break;
        }
      }else{
        this.paper_main_object.children.boussole_bg.visible = false;
        if (this.concernement.has_puissancedagir) {
          this.paper_main_object.children.puissanceagir_bg.visible = false;
        }
        if (this.concernement.has_doleance) {
          this.paper_main_object.children.doleance_bg.visible = false;
        }
      }

      // entites
      if (this.is_opened
        && this.map_mode !== 'puissancedagir'
        && this.map_mode !== 'doleancer' 
        && this.map_mode !== 'action' ) {
          this.paper_main_object.children.entites.visible = true;
      } else {
        this.paper_main_object.children.entites.visible = false;
      }


      // puissance d'agir     
      if (this.concernement.has_puissancedagir) {
        if (this.map_mode === "puissancedagir") {
          if (!this.is_opened) {
            this.paper_main_object.children.puissanceagir_icon.visible = true; // if not opened and has_puissancedagir draw the puissance d'agir icone
            this.paper_main_object.children.puissanceagir_besoins.visible = false;
          } else {
            this.paper_main_object.children.puissanceagir_icon.visible = false;
            this.paper_main_object.children.puissanceagir_besoins.visible = true;
            // this.drawBesoins();
          }
        } else {
          this.paper_main_object.children.puissanceagir_icon.visible = false;
          this.paper_main_object.children.puissanceagir_besoins.visible = false;
        }
      }

      // agissantes
      // console.log('this.concernement.has_agissantes', this.concernement.has_agissantes);
      if (this.concernement.has_agissantes) {
        if (this.map_mode === "action") {
          this.paper_main_object.children.agissantes.visible = true;
        } else {
          this.paper_main_object.children.agissantes.visible = false;
        }  
      }
      
      // doleance     
      if (this.concernement.has_doleance) {
        if (this.map_mode === "doleancer") {
          if (!this.is_opened) {
            this.paper_main_object.children.doleance_icon.visible = true;
            this.paper_main_object.children.doleance_steps.visible = false;
          } else {
            this.paper_main_object.children.doleance_icon.visible = false;
            this.paper_main_object.children.doleance_steps.visible = true;
          }
        } else {
          this.paper_main_object.children.doleance_icon.visible = false;
          this.paper_main_object.children.doleance_steps.visible = false;
        }
      }
    
    },
    // PAPER EVENTS
    initPaperEvents(){

      this.paper_main_object.onMouseEnter = function(event){
        if (!this.opened_concernement && this.isFocused()) { // only if no concernement is opened and is this focused
          this.setHoverElmt({
            type: 'concernement',
            id: this.id
          });
          document.body.style.cursor = "pointer";
        }
      }.bind(this);
      
      this.paper_main_object.onMouseLeave = function(event){
        if (!this.opened_concernement && this.isFocused()) { // only if no concernement is opened
          this.resetHoverElmt();
          document.body.style.cursor = "auto";
        }
      }.bind(this);

      this.paper_main_object.onMouseMove = function(event){
        // console.log(`onmousemove ${this.id}`);
        // TODO besoins & actions & doleances
        if (this.is_opened) {
          
          // lets define some options regarding the map_mode
          let paper_group_tohit;
          switch (this.map_mode) {
            case "terraindevie":
              paper_group_tohit = 'entites';
              break;
            case "action":
              paper_group_tohit = 'agissantes';
              break;
            case "puissancedagir":
              paper_group_tohit = 'puissanceagir_besoins';
              break;
            case "doleancer":
              paper_group_tohit = 'doleance_steps';
              break;
          }

          let result = this.paper_main_object.children[paper_group_tohit].hitTest(event.point);
          // console.log('move result', result);
          if (result && result.item.item_id) {
            // console.log('move has result', result);
            let new_hover_elmt = {
              paper_id: result.item.id,
              type: result.item.item_type,
              id: result.item.item_id
            }; 
            switch (result.item.item_type) {
              case "reponse":
                new_hover_elmt.bid = result.item.item_bid;
                new_hover_elmt.cid = result.item.item_cid;
                break;
            }
            if (!this.hover_elmt || new_hover_elmt.paper_id !== this.hover_elmt.paper_id) {
              // console.log(`before setHoverElmt ${this.id}`);
              this.setHoverElmt(new_hover_elmt);
            }
            document.body.style.cursor = "pointer";
          } else {
            // console.log('move no result');
            this.resetHoverElmt();
            document.body.style.cursor = "auto";
          }
        }
      }.bind(this);
      
      this.paper_main_object.onClick = function(event){
        console.log('paper concernement onClick');
        if (!this.is_opened) {
          if (!this.opened_concernement) {
            console.log(`Open me ${this.id}`);
            // open/close all concernements
            this.openCloseConcernements(this.id)
              // push route (keep the hash for map_mode)
            this.$router.push({
              name: 'concernement',
              hash: `#${this.map_mode}`,
              params: {id: this.id}
            });
            // reset the mousehover
            this.resetHoverElmt();
          }
        } else {
          // lets define some options regarding the map_mode
          // debugger;
          let op = {pg: null};
          switch (this.map_mode) {
            case "terraindevie":
              op = {
                pg: 'entites',  // paper group to hittest
              }
              break;
            case "action":
              op = {
                pg: 'agissantes', // paper group to hittest
              }
              break;
          }
          if (op.pg) {
            let result = this.paper_main_object.children[op.pg].hitTest(event.point);
            // console.log('click result', result);
            if (result) {
              // we have clicked on an entite
              this.$router.push({
                name: 'concernement',
                hash: `#${this.map_mode}`,
                params: {id: this.opened_concernement.id, eid: result.item.item_id}
              });
            } else {
              // otherwise we close the entite and come back to the concernement
              this.$router.push({
                name: 'concernement',
                hash: `#${this.map_mode}`,
                params: {id: this.id}
              });
              // reset the mousehover
              this.resetHoverElmt();
            }
          }
        }
      }.bind(this);

    },
    resetHoverElmt(){
      // console.log('resetHoverElmt');
      setTimeout(()=>{
        this.setHoverElmt(null);
      }, 100);
    },
    openClose(open) {
      // console.log(`ConcernementsMapItem ${this.concernement.id} openClose: ${open}`);
      if (this.tween) {
        this.tween.stop();
      }
      if (open) {
        // paper bring to front
        this.paper_main_object.bringToFront();
        // calcul opened size regarding window size
        // let ch = this.canvas.height;
        // let s = this.canvas.height / (this.ray*2.8)
        let s = Math.min(this.canvas.height, this.canvas.width - this.cartouch_width) / (this.ray*2.8)
        

        // opening tweening
        this.tween = new Tween.Tween({s: this.scale, x: this.pos.x, y: this.pos.y, o: 0})
          .to({
            s: s,
            x: (this.canvas.width - this.cartouch_width) / 2,
            y: this.canvas.height / 2,
            o: 0.8
          }, 800)
          .onUpdate((obj) => {
            // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
            // revert to the original size (by reverting the previous scale)
            Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
            this.paper_main_object.scale(1 / this.scale);
            // then scale again to new scale
            Matter.Body.scale(this.body, obj.s, obj.s)
            this.paper_main_object.scale(obj.s);

            // record new scale
            this.prev_scale = this.scale;
            this.scale = obj.s;
            this.opacity = obj.o;
            
            Matter.Body.setPosition(this.body, {x:obj.x, y:obj.y});
            this.pos = {x:obj.x, y:obj.y};
          })
          .onComplete((obj) => {
            this.constraint = Matter.Constraint.create({
              pointA: this.pos,
              bodyB: this.body,
              stiffness: 1,
              damping: 0,
              length: 0
            });
            Matter.Composite.add(this.matterEngine.world, [this.body, this.constraint]);
            this.prev_scale = this.scale;
          });
          // recreate the matter engine event to get it a the end of the events stack
          Matter.Events.off(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
          Matter.Events.on(this.matterEngine, "afterUpdate", this.onAfterEngineUpdate);
      } else {
        // closing
        if(this.constraint){
          Matter.Composite.remove(this.matterEngine.world, this.constraint);
        }
        this.tween = new Tween.Tween({s: this.scale, o: 1})
          .to({s: 1, o: 0}, 500)
          .onUpdate((obj) => {
            // https://github.com/liabru/matter-js/issues/986#issuecomment-812488873
            // revert to the original size (by reverting the previous scale)
            Matter.Body.scale(this.body, 1 / this.scale, 1 / this.scale)
            this.paper_main_object.scale(1 / this.scale);
            // then scale again to new scale
            Matter.Body.scale(this.body, obj.s, obj.s)
            this.paper_main_object.scale(obj.s);
            
            // record new scale
            this.prev_scale = this.scale;
            this.scale = obj.s;
            this.opacity = obj.o;
          })
          .onComplete((obj) => {
            this.prev_scale = this.scale = 1;
          });
      }
      this.tween.easing(Tween.Easing.Quadratic.InOut).start();
    },
    // ENGINE UPDATE
    onBeforeEngineUpdate (event) {
      // update the opening/closing tweening
      if (this.tween) {
        this.tween.update();
      }

      if (this.opened_concernement) {
        if (this.opened_concernement.id !== this.id) {
          this.pushAside()
        }
      } else if (this.map_mode === 'puissancedagir' 
        || this.map_mode === 'action'
        || this.map_mode === 'doleancer'){ // apply focus forces : move unfocused on the sides and focused on the center
        this.applyFocusForces(); //
      } else {
        this.applyNormalForces(); // if terrain de vie apply force to gently reject items from the sides and so create a continuasly mvmt
      }
      
      // reset all matter rotation forces otherwise items will spin when colide 
      // Matter.Body.setAngle(this.body, 0);
      // Matter.Body.setAngularSpeed(this.body, 0);
    },
    applyFocusForces(){
      if(!this.isFocused()) {
        this.pushAside()
      }else{
        this.bringToCenter()
      }
    },
    isFocused(){
      return this.map_mode === 'terraindevie'
          || (this.map_mode === 'action' && this.concernement.has_agissantes)
          || (this.map_mode === 'puissancedagir' && this.concernement.has_puissancedagir)
          || (this.map_mode === 'doleancer' && this.concernement.has_doleance);
    },
    pushAside(){
      // console.log('pushAside', this.opened_concernement);
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      // get the direction to the closest side
      let dir = this.pos.x > pseudo_center_x
        ? 1 // to the right
        : -1; // to the left
      // max and min item position
      let minp = 0;
      let maxp = dir < 0 
        ? pseudo_center_x 
        : this.canvas.width - pseudo_center_x;
      // max and min force
      let minf = 0;
      let maxf = 6;
      // scale factor
      let scale = (maxf-minf) / (maxp-minp);
      // get the inversed distance
      let dist = dir < 0 
        ? this.pos.x 
        : this.canvas.width - this.pos.x; // get the distance from the side
      // // calculate the force
      // let x_force = Math.pow(dist/700,100) * dir;
      // calculate the logarithmic force
      let x_force = Math.exp(minf + scale*(dist-minp)) * dir;

      let ori_pos = {x:pseudo_center_x, y:this.body.position.y};

      Matter.Body.applyForce(
        this.body, 
        ori_pos, 
        {
          x: x_force,
          y: 0
        }
      );

      this.body.frictionAir = 0.01;
    },
    bringToCenter(){
      // bring to the centre
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy inversed proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      let dir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let dist = dir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x; 

      if (dist > this.canvas.width/6) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max & min item position
        let minp = 0;
        let maxp = dir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;
        
        // X force decrsinsing near the center
        // max and min force
        let xminf = 0;
        let xmaxf = 1 + Math.random()*4;
        // scale factor
        let xscale = (xmaxf-xminf) / (maxp-minp);
        let x_force = Math.exp(xminf + xscale*(dist-minp)) * dir;
        let ori_pos = dir < 0 
          ? {x:this.canvas.width, y:this.body.position.y} 
          : {x:0, y:this.body.position.y};
        Matter.Body.applyForce(
          this.body, 
          ori_pos, 
          {
            x: x_force,
            y: 0
          }
        );
        
        // Friction air fa (increasing near the center)
        let fa_minf = 0;
        let fa_maxf = 6;
        // scale factor
        let fa_scale = (fa_maxf-fa_minf) / (maxp-minp);
        let fa = Math.exp(fa_minf + fa_scale*(dist-minp)*-1);
        this.body.frictionAir = fa;
        
        console.log(`bringToCenter dist:${dist}, x_force:${x_force}, friction air:${fa}`);        
      } else { // in the ceneter zone let items floats freely
        let fa = 0.01;
        let velocity = Matter.Body.getVelocity(this.body);
        if (velocity.x) {
          let velocityx = Math.abs(velocity.x);
          // map a range of numbers to another range of numbers
          // INFO https://stackoverflow.com/a/46462321
          let velocity_range = [50, 1000];
          let fa_range = [0,3];
          fa = fa_range[0] + (velocityx - velocity_range[0]) * (fa_range[1] - fa_range[0]) / (velocity_range[1] - velocity_range[0]);
          console.log(`bringToCenter velocityx:${velocityx}, fa:${fa}, velocity`, velocity);
        } else {
          console.warn(`bringToCenter no velocity`, velocity);
        }

        this.body.frictionAir = fa;
      }

    },
    applyShuffleForces() {
      // console.log('applyShuffleForces');
      // var dist, dir, x_velocity;
      // dir = this.pos.x > this.canvas.width/2 ? -1 : 1; // get the direction to the centre
      // dist = (dir < 0 ? this.pos.x - this.canvas.width/2 : this.canvas.width/2 - this.pos.x); // get the distance from the side
      // x_velocity = Math.pow(dist/650,10) * dir;
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      // get the direction to the centre
      let dir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let dist = dir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x;

      dist = dist > 1000 ? 1000 : dist;
      
      if (dist > this.canvas.width/6) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max & min item position
        let minp = 0;
        let maxp = dir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;

        // X max and min force
        let xminf = 0;
        let xmaxf = 4 + Math.random()*3;
        // scale factor
        let xscale = (xmaxf-xminf) / (maxp-minp);
        
        let x_velocity = Math.exp(xminf + xscale*(dist-minp))*dir;

        // let y_velocity = 30 + Math.random() * 50;
        // y_velocity = Math.random() > 0.5 ? -y_velocity : y_velocity;
        // X max and min force
        let yminf = 1;
        let ymaxf = 2 + Math.random()*4;
        // scale factor
        let yscale = (ymaxf-yminf) / (maxp-minp);
        
        let y_velocity = Math.exp(yminf + yscale*(dist-minp)) * (Math.random() > 0.5 ? 1 : -1);       // let ori_pos = dir < 0 

        //   ? {x:this.canvas.width, y:this.body.position.y} 
        //   : {x:0, y:this.body.position.y};
        // let x_force = Math.pow(dist/800,10) * dir;
        console.log(`applyShuffleForces dir:${dir}, maxp:${maxp}, dist:${dist}, x_velocity:${x_velocity}, y_velocity:${y_velocity}`);
        Matter.Body.setVelocity(this.body, {x: x_velocity, y: y_velocity});
      } else { // if in center zone real shuffle velocity
        // x_velocity = -50 + Math.random()*100;
        // y_velocity = -50 + Math.random()*100;
        // this.setInitBodyVelocity();
      }
      
      this.body.frictionAir = 0.01;
    },
    applyNormalForces () {
      // bring gently to the centre
      // apply a force in direction of one side or an other depending of the start position
      // the force is exponentialy inversed proportional to the distance from the side
      // INFO logarithmic force : https://stackoverflow.com/questions/846221/logarithmic-slider/846249#846249 
      // TODO cartouch width should change regarding actual cartouch is opened or not (and how many cartouch are opened) 
      // X
      let pseudo_center_x = this.opened_concernement 
        ? (this.canvas.width - this.cartouch_width) / 2
        : this.canvas.width / 2;
      let xdir = this.pos.x > pseudo_center_x 
        ? -1 // to left 
        : 1; // to right
      // get the inversed distance from the side
      let xdist = xdir < 0 
        ? this.pos.x - pseudo_center_x 
        : pseudo_center_x - this.pos.x; 

      // Y
      let pseudo_center_y = this.canvas.height / 2;
      let ydir = this.pos.y > pseudo_center_y 
        ? -1 // to top
        : 1; // to bottom
      // get the inversed distance from the side
      let ydist = ydir < 0 
        ? this.pos.y - pseudo_center_y 
        : pseudo_center_y - this.pos.y; 

      let k = 4;
      if (xdist > this.canvas.width/k || ydist > this.canvas.height/k) { // apply decreasing forces and increasing friction air only out of center to let the items float in the center zone
        // max and min force
        let minf = 0;
        let maxf = 0.1;

        // X
        // max & min item position
        let xminp = 0;
        let xmaxp = xdir < 0
          ? this.canvas.width - pseudo_center_x
          : pseudo_center_x;
        // X force decrsinsing near the center
        // scale factor
        let xscale = (maxf-minf) / (xmaxp-xminp);
        let x_force = Math.exp(minf + xscale*(xdist-xminp)) * xdir;
        let xori_pos = xdir < 0 
          ? {x:this.canvas.width, y:this.body.position.y} 
          : {x:0, y:this.body.position.y};
        Matter.Body.applyForce(
          this.body, 
          xori_pos, 
          {
            x: x_force,
            y: 0
          }
        );

        // Y
        // max & min item position
        let yminp = 0;
        let ymaxp = ydir < 0
          ? this.canvas.height - pseudo_center_y
          : pseudo_center_y;
        // X force decrsinsing near the center
        // scale factor
        let yscale = (maxf-minf) / (ymaxp-yminp);
        let y_force = Math.exp(minf + yscale*(ydist-xminp)) * ydir;
        let yori_pos = ydir < 0 
          ? {x:this.body.position.x, y:this.canvas.height} 
          : {x:this.body.position.x, y:0};
        Matter.Body.applyForce(
          this.body, 
          yori_pos, 
          {
            x: 0,
            y: y_force
          }
        );
        
        // // Friction air fa (increasing near the center)
        // let fa_minf = 0;
        // let fa_maxf = 6;
        // // scale factor
        // let fa_scale = (fa_maxf-fa_minf) / (maxp-minp);
        // let fa = Math.exp(fa_minf + fa_scale*(dist-minp)*-1);
        // this.body.frictionAir = fa;
        
        // console.log(`bringToCenter dist:${xdist}, x_force:${x_force}`);        
      } else { // in the ceneter zone let items floats freely
        // let velocity = Matter.Body.getVelocity(this.body);
        // let velocityx = Math.abs(velocity.x);
        // // map a range of numbers to another range of numbers
        // // INFO https://stackoverflow.com/a/46462321
        // let velocity_range = [50, 1000];
        // let fa_range = [0,3];
        // let fa = fa_range[0] + (velocityx - velocity_range[0]) * (fa_range[1] - fa_range[0]) / (velocity_range[1] - velocity_range[0]);
        // console.log(`bringToCenter velocity.x:${velocity.x}, fa:${fa}`);

        // this.body.frictionAir = fa;
        this.body.frictionAir = 0.1;
      }

    },
    respawn() {
      // respawn element if outside screen
      if(this.pos.x <= 0
        || this.pos.x >= this.canvas.width
        || this.pos.y <= 0
        || this.pos.y >= this.canvas.height){
          this.pos = this.getRandomPos()
          Matter.Body.setPosition(this.body, {x:this.pos.x, y:this.pos.y});
          this.setInitBodyVelocity();
        }
      
    },
    onAfterEngineUpdate (event) {
      this.respawn();

      this.paper_main_object.position = this.pos = this.body.position;

      // this.draw()
      this.handlePaperVisibility()
    },
  },
  render() {
    // console.log('render()', this.ctx);
  },
}

</script>