/*
  Q Light Controller Plus
  balls.js

  Copyright (c) Tim Cullingworth

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

// Development tool access
var testAlgo;

(
  function()
  {
    var algo = new Object;
    algo.apiVersion = 2;
    algo.name = "Balls";
    algo.author = "Tim Cullingworth";
    algo.acceptColors = 1;
    algo.properties = new Array();
    algo.rstepcount = 0;
    algo.gstepcount = 50;
    algo.bstepcount = 100;

    algo.presetSize = 1;
    algo.properties.push("name:presetSize|type:range|display:Size|values:1,20|write:setSize|read:getSize");
    algo.presetNumber = 5;
    algo.properties.push("name:presetNumber|type:range|display:Number|values:1,50|write:setNumber|read:getNumber");
    algo.presetRandom = 1;
    algo.properties.push("name:presetRandom|type:list|display:Random Colour|values:No,Yes|write:setRandom|read:getRandom");
    algo.presetCollision = 1;
    algo.properties.push("name:presetCollision|type:list|display:Self Collision|values:No,Yes|write:setCollision|read:getCollision");
    var util = new Object;
    algo.initialized = false;

    algo.setSize = function(_size)
    {
      algo.presetSize = _size;
    };

    algo.getSize = function()
    {
      return algo.presetSize;
    };

    algo.setNumber = function(_step)
    {
      algo.presetNumber = _step;
      algo.initialized = false;
    };

    algo.getNumber = function()
    {
      return algo.presetNumber;
    };

    algo.setRandom = function(_random)
    {
      if (_random === "Yes") { algo.presetRandom = 0; }
      else if (_random === "No") { algo.presetRandom = 1; }
    };

    algo.getRandom = function()
    {
      if (algo.presetRandom === 0) { return "Yes"; }
      else if (algo.presetRandom === 1) { return "No"; }
    };

    algo.setCollision = function(_colision)
    {
      if (_colision === "Yes") { algo.presetCollision = 0; }
      else if (_colision === "No") { algo.presetCollision = 1; }
    };

    algo.getCollision = function()
    {
      if (algo.presetCollision === 0) { return "Yes"; }
      else if (algo.presetCollision === 1) { return "No"; }
    };

    util.initialize = function(width, height)
    {
      algo.ball = new Array(algo.presetNumber);
      algo.direction = new Array(algo.presetNumber);
      algo.colour = new Array(algo.presetNumber);

      for (var i = 0; i < algo.presetNumber; i++)
      {
        var x = Math.random() * (width - 1); // set random start
        var y = Math.random() * (height - 1); // locations for balls
        algo.ball[i] = [y, x];
        var yDirection = (Math.random() * 2) - 1; // and random directions
        var xDirection = (Math.random() * 2) - 1;
        algo.direction[i] = [yDirection, xDirection];
        do
        {
          var ballR = Math.round(Math.random() * 255);  // Chose random
          var ballG = Math.round(Math.random() * 255);  // colour for
          var ballB = Math.round(Math.random() * 255);  // each Ball
        } while ((ballR + ballG + ballB) < 356);  // if it it to dim try again
        algo.colour[i] = (ballR << 16) + (ballG << 8) + ballB;
      }

     algo.initialized = true;
     return;
    };

    algo.rgbMap = function(width, height, rgb, progstep)
    {
      if (algo.initialized === false)
      {
        util.initialize(width, height);
      }

      var map = new Array(height);	// Clear map data
      for (var y = 0; y < height; y++)
      {
          map[y] = new Array();

          for (var x = 0; x < width; x++)
          {
            map[y][x] = 0;
          }
      }

      for (var i = 0; i < algo.presetNumber; i++)  // for each ball displayed
      {
        if (algo.presetRandom === 0) { rgb = algo.colour[i]; } // use RGB or ball random colour
        var r = (rgb >> 16) & 0x00FF;  // split colour in to
        var g = (rgb >> 8) & 0x00FF;   // separate parts
        var b = rgb & 0x00FF;
        var yx = algo.ball[i];  // ball's location, as float
        var step = algo.direction[i];  // ball's direction / speed, as float
        var my = Math.floor(yx[0]);  // workout closest map location for ball
        var mx = Math.floor(yx[1]);
        var boxSize = Math.round(algo.presetSize / 2);  // area size to draw ball

        for (var ry = my-boxSize; ry < my+boxSize+2; ry++)  // area for faded edges
        {
          for (var rx = mx-boxSize; rx < mx+boxSize+2; rx++) // to display ball
          {
            if (rx < width && rx > -1 && ry < height && ry > -1) // if edges are off the map
            { // dont draw
              var pointRGB = map[ry][rx];	    // get curent colour on the map
              var pointr = (pointRGB >> 16) & 0x00FF;// so that colours mix and don't over
              var pointg = (pointRGB >> 8) & 0x00FF; // write.
              var pointb = pointRGB & 0x00FF;  // splt rgb in to components
              var ballr = r;
              var ballg = g;
              var ballb = b;
              var offx = rx - yx[1];  // calculate the off set differance of map location
              var offy = ry - yx[0];  // to the float location of the ball, using the hypotenuse
              var hyp = 1 - (Math.sqrt( (offx * offx) + (offy * offy))/((algo.presetSize/2)+1));
              if (hyp < 0) { hyp = 0; } // if the distance multiplyed by ball size is negative = 0
              pointr += Math.round(ballr * hyp); // dim mapped ball colours by the distance from
              pointg += Math.round(ballg * hyp); // the ball center ( hyp = 1, full colour / 0, off)
              pointb += Math.round(ballb * hyp); // add the ball colour to the mapped location
              if (pointr > 255) { pointr = 255; } // if addind the colours over saturates
              if (pointg > 255) { pointg = 255; } // reduce it to the maximum
              if (pointb > 255) { pointb = 255; }

              pointRGB = (pointr << 16) + (pointg << 8) + pointb; // combine colours

              map[ry][rx] = pointRGB; // set mapped point
            }
          }
        }
        if (algo.presetCollision === 0)  // if colision detection is on
        {
          // Ball collision detection
          for (var ti = 0; ti < algo.presetNumber; ti++) // check all balls
          {
            if (ti !== i)  // but not the current one
            {
              var disy = (yx[0] + step[0]) - algo.ball[ti][0];  // calculate distance
              var disx = (yx[1] + step[1]) - algo.ball[ti][1];  // to current ball
              var dish = Math.sqrt((disx * disx) + (disy * disy));
              if (dish < (1.414) * (algo.presetSize/2))  // if to close
              {
                var stepy = step[0];  // swap speed / direction of current ball
                var stepx = step[1];  // with ball that is to close
                algo.direction[i][0] = algo.direction[ti][0];
                algo.direction[i][1] = algo.direction[ti][1];
                algo.direction[ti][0] = stepy;
                algo.direction[ti][1] = stepx;
              }
            }
          }
        }

        // edge collision detection
        if (yx[0] <= 0 && step[0] < 0) { step[0] *= -1; } // top edge and moving up
        else if (yx[0] >= height-1 && step[0] > 0) { step[0] *= -1; } // bottom edge and moving down

        if (yx[1] <= 0 && step[1] < 0) { step[1] *= -1; } // left edge and moving left
        else if (yx[1] >= width-1 && step[1] > 0) { step[1] *= -1; } // right edge and moving right

        yx[0] += step[0]; // set ball's next location
        yx[1] += step[1];

        algo.ball[i] = yx; // update location
        algo.direction[i] = step; // and direction / speed
      }

      return map;
    };

    algo.rgbMapStepCount = function(width, height)
    {
      // This make no difference to the script ;-)
      return 2;
    };

    // Development tool access
    testAlgo = algo;

    return algo;
  }
)();
