// Jello Madness (jM) module
// jelloMadness.js
   console.log('JM version 0.0');
// 3:51 PM Wed June 22, 2022
// Written by: James D. Miller

/*
Dependencies:
   gwModule.js (gW.)
   constructorsAndPrototypes.js (cP.)
   utilities.js
*/

var jM = (function() {
   "use strict";
   
   // Names starting with m_ indicate module-scope globals.
   var m_reported;
   var m_tangleTimer_s;
   var m_timerAtDetangle_s;
   var m_verifyingDeTangle;
   // An array for use in testing for tangled jello.
   var m_jelloPucks;
   
   initializeModule();
   
   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
   ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
   
   function initializeModule() {
      m_reported = false;
      m_tangleTimer_s = 0;
      m_timerAtDetangle_s = 0;
      m_verifyingDeTangle = false;
      // An array for use in testing for tangled jello.
      m_jelloPucks = [];
   }
   
   function setUpPreGameHelp() {
      m_reported = true;
      m_tangleTimer_s = 0.0;
      // For 6.a or 6.d or any capture based on them, run them like the Jello game.
      if ((gW.getDemoVersion().slice(0,3) == "6.a") || (gW.getDemoVersion().slice(0,3) == "6.d")) {
         gW.messages['jelloTimer'].loc_px = {'x':15,'y': 40};
         gW.messages['win'].loc_px =  {'x':15,'y':135};
         
         gW.messages['help'].loc_px = {'x':15,'y': 75};
         gW.messages['help'].newMessage("Detangle the jello:\\    Try the f key. Try right-click mouse drags.", 3.0);
         
         gW.messages['gameTitle'].newMessage("Jello Madness", 1.0);
         gW.messages['gameTitle'].loc_px = {'x':15,'y':200};
         gW.messages['gameTitle'].popAtEnd = false;
         
         m_reported = false;
         m_verifyingDeTangle = false;
      }
   }
   
   function makeJello( pars) {
      let pinned = setDefault( pars.pinned, false);
      let gridsize = setDefault( pars.gridsize, 4);
      let addToJello = setDefault( pars.addToJello, true);
      let offset_2d_m = setDefault( pars.offset_2d_m, new cP.Vec2D(2.0, 2.0));
      let restitution = setDefault( pars.restitution, 0.7);

      let spacing_factor_m = 0.9;
      
      let v_init_2d_mps = new cP.Vec2D(0.0, 0.0);
      
      let puckParms = {'radius_m':0.20, 'density':5.0, 'jello':true, 'restitution':restitution, 'restitution_fixed':true};
      
      let springParms = {
         'unstretched_width_m': 0.07,
         'strength_Npm': 350.0,          
         'length_m': spacing_factor_m * 1.0,
         'damper_Ns2pm2': 5.0};

      // a local jello array for baking the jello
      let jelloPucks = [];

      // Grid of pucks.
      for (let j = 0; j < gridsize; j++) {
         for (let k = 0; k < gridsize; k++) {
            if ((j==2) && (k==2)) {
               puckParms.color = "orange";
            } else {
               puckParms.color = undefined;  // use default
            }
            let pos_2d_m = new cP.Vec2D( spacing_factor_m * j, spacing_factor_m * k);
            pos_2d_m.addTo( offset_2d_m);
            jelloPucks.push( new cP.Puck( Object.assign({}, pos_2d_m), Object.assign({}, v_init_2d_mps), Object.assign({}, puckParms)));
         }
      }
      // Horizontal springs (between neighbors)
      for (let m = 0; m < gridsize*(gridsize-1); m++) {
         springParms.color = "blue";
         // Note: Object.assign is used here to make a copy of the springParms object (mutable). This avoids the multiple reference to springParms
         // and any associated mutation side effects (from this and the following color changes) when the state is captured.
         new cP.Spring(jelloPucks[m], jelloPucks[m+gridsize], Object.assign({}, springParms));
      }
      // Vertical springs
      for (let m = 0; m < gridsize-1; m++) {
         for (let n = 0; n < gridsize; n++) {
            let o_index = m + (n * gridsize);
            springParms.color = "blue";
            new cP.Spring(jelloPucks[o_index], jelloPucks[o_index+1], Object.assign({}, springParms));
         }
      }
      // Diagonal springs (yellow)
      for (let m = 0; m < gridsize-1; m++) {
         for (let n = 1; n < gridsize; n++) {
            let o_index = m + (n * gridsize);
            springParms.color = "yellow";
            springParms.length_m = spacing_factor_m * 1.41;  // A diagonal
            new cP.Spring(jelloPucks[o_index], jelloPucks[o_index-(gridsize-1)], Object.assign({}, springParms));
         }
      }
      // Diagonal springs (perpendicular to the other diagonals)
      for (let m = 0; m < gridsize-1; m++) {
         for (let n = 0; n < gridsize-1; n++) {
            let o_index = m + (n * gridsize);
            springParms.color = "yellow";
            springParms.length_m = spacing_factor_m * 1.41; // A diagonal
            new cP.Spring(jelloPucks[o_index], jelloPucks[o_index+(gridsize+1)], Object.assign({}, springParms));
         }
      }
      
      // Add two pinned springs.
      if (pinned) {
         let corner_puck = (gridsize * gridsize) - 1;
         new cP.Spring(jelloPucks[ 0], new cP.Pin( new cP.Vec2D( 0.5, 0.5), {radius_px:4}), {strength_Npm:800.0, unstretched_width_m:0.3, color:'brown',damper_Ns2pm2:5.0});
         new cP.Spring(jelloPucks[ corner_puck], new cP.Pin( new cP.Vec2D( 9.0, 9.0), {radius_px:4}), {strength_Npm:800.0, unstretched_width_m:0.3, color:'brown',damper_Ns2pm2:5.0});
      }
      
      // Add this new jello to the global jello array. This allows multiple pieces of jello to
      // be used in the 6a and 6d detangle games.
      if (addToJello) {
         for (let j = 0, len = jelloPucks.length; j < len; j++) {
            m_jelloPucks.push( jelloPucks[j]);
         }
      }
   }
   
   /*
   Tried using the B2D contact listener to detect tangle. But this
   approach fails to deal with a tangled state where the balls are not quite
   touching... So the approach below is used.
   
   function checkForJelloTangle2() {
      if (c.contactCounter > 0) {
         c.jello.tangleTimer_s += c.deltaT_s;
      }
      ctx.font = "30px Arial";
      ctx.fillStyle = 'yellow';
      ctx.fillText(c.jello.tangleTimer_s.toFixed(2),10,50);
   }
   */
   
   function checkForJelloTangle() {
      // Determine if tangled by looking for balls that are fairly close to 
      // each other. This does not require puck contact to detect a tangle.
      
      // A looping structure that avoids self reference and repeated puck-otherpuck references.
      let stillTangled = false;
      
      for (let j = 0, len = m_jelloPucks.length; j < len; j++) {
         for (let k = j+1; k < len; k++) {
            // Check distance between j and k pucks.
            let diff_2d_m = m_jelloPucks[j].position_2d_m.subtract( m_jelloPucks[k].position_2d_m);
            
            // Square of the vector length.
            let lenSquared = diff_2d_m.length_squared();
            
            // Make the separation test a little more than the sum of the radii (add 30% of the radius of the smaller puck).
            // Then square it for comparison with the length squared.
            let radiiSum_m = m_jelloPucks[j].radius_m + m_jelloPucks[k].radius_m;
            let minRadius_m = Math.min( m_jelloPucks[j].radius_m, m_jelloPucks[k].radius_m );
            let separation_check = Math.pow(radiiSum_m + (minRadius_m * 0.30), 2);
            
            if (lenSquared < separation_check) {
               // This one is too close to be in a non-tangled jello block.
               stillTangled = true;
               m_tangleTimer_s += gW.getDeltaT_s();
               j = k = 10000; // break out of the two loops.
            }
         }
      }
      
      gW.messages['jelloTimer'].newMessage( m_tangleTimer_s.toFixed(2), 0.2);
      
      if ( ! stillTangled) {
         // Get a timestamp for use in verification.
         if ( ! m_verifyingDeTangle) {
            m_timerAtDetangle_s = m_tangleTimer_s;
         }
         // Wait 1.000 seconds and verify (that there has been no timer change).
         if ( ( ! m_reported) && ( ! m_verifyingDeTangle)) {
            m_verifyingDeTangle = true;
            window.setTimeout( function() { 
               // If the timer hasn't advanced, must still be detangled.
               if (m_tangleTimer_s == m_timerAtDetangle_s) {
                  if ( ! m_reported) {
                     
                     // leaderboard stuff
                     cP.Client.applyToAll( client => { 
                        client.addScoreToSummary( m_tangleTimer_s.toFixed(2), gW.getDemoIndex(), pP.getNpcSleepUsage());
                     });
                     lB.reportGameResults();
                     // Send a score for each human player to the leaderboard. Build leaderboard report at the end.
                     lB.submitScoresThenReport();
                     // Open up the multi-player panel so you can see the leader-board report.
                     if (!gW.dC.multiplayer.checked) {  
                        $('#chkMultiplayer').trigger('click');
                     }
                     // Make sure this gets reported only once (per demo #6 start).
                     m_reported = true;
                     gW.messages['win'].newMessage("That's better. Thank you.", 3.5);
                     gW.clients['local'].winCount += 1;
                  }
               } else {
                  console.log('not sustainably detangled...');
               }
               m_verifyingDeTangle = false;
            }, 1000);
         }
      }
   }
   
   function puckCount() { 
      return m_jelloPucks.length;
   }
   
   function removeDeletedPucks() {
      // Filter out references to pucks that are marked as deleted.
      m_jelloPucks = m_jelloPucks.filter( function( eachPuck) {
         // Keep these (those NOT deleted)
         return ( ! eachPuck.deleted);
      });
   }
   
   function addPuck( newPuck) {
      m_jelloPucks.push( newPuck);
   }
   
   // see comments before the "return" section of gwModule.js
   return {
      // Objects
      
      // Variables
      
      // Methods
      initializeModule: initializeModule,
      setUpPreGameHelp: setUpPreGameHelp,
      makeJello: makeJello,
      checkForJelloTangle: checkForJelloTangle,
      puckCount: puckCount,
      removeDeletedPucks: removeDeletedPucks,
      addPuck: addPuck
   };

})();