//  westinghouse_sign.js
/*  This is an exercise in programming the canvas element. In this case, to
 *  create an animated Westinghouse Sign, like the one in Pittsburgh and the
 *  lesser one on the LIE many years ago. The Pittsburgh sign had 9 copies
 *  of the Westinghouse logo; the LIE sign had just three. Each logo consists
 *  of a circle divided into four arcs, and a stylized W in the center.
 *  There are three balls on top of the four arms that make up the W, and a bar
 *  underneath the W. Each of the 4 arcs, 3 balls, 4 arms, and the bar (12
 *  shapes all together) can be illuminated independently. Wikipedia says there
 *  were only two separate arcs (top and bottom) on the Pittsburgh sign, but
 *  I remember 4-5 segments on the LIE sign. I'm going for four.
 *
 *  2008-04-25
 *    Draw 9 circles in a box.
 */

 Core.start(
  (function()
    {
      var startStopButton = null;
      var run             = true;
      var fasterButton    = null;
      var slowerButton    = null;
      var speedFactor     = 1.0;
      var startText       = document.createTextNode("Resume");
      var stopText        = document.createTextNode("Pause");
      var theCanvas       = null;
      var context         = null;
      //  Nine logo objects for Pittsburgh
      var logos           = [];

      //  As many frame objects as needed
      var frames          = [];
      var currentFrame    = 0;
      var frameCount      = 0;  // Used for on-the-fly modifications

      //  Shorthand for element arrays
      var allOn   = [true,true,true,true,true,true,true,true,true,true,true,true];
      var allOff  = [false,false,false,false,false,false,false,false,false,false,false,false];

      //  Logo objects
      //  =================================================================================
      /*  Each logo has to know which one it is so it knows where to draw itself.
       *  It also maintains a list of the current state of each of its elements so it
       *  knows whether to draw or erase each element when updated.
       */
      function Logo(whichLogo)
      {
        this.whichLogo = whichLogo;
        this.currentStates = [false,false,false,false,false,false,false,false,false,false,false,false];

        //  Logo.update()
        //  -------------------------------------------------------------------------------
        /*  Given an array of 12 logo elements, draw each one in the foreground or
         *  background color as appropriate.
         */
        this.update = function(newStates)
        {

          //  Elements 0-3: four arcs
          for (var whichArc = 0; whichArc < 4; whichArc++)
          {
            if (newStates[whichArc] !== this.currentStates[whichArc])
            {
              context.strokeStyle = newStates[whichArc] ? '#99f' : '#fff';
              //context.lineWidth = newStates[whichArc] ? 4 : 5;
              context.lineWidth = 5;
              context.beginPath();
              context.arc(116*this.whichLogo+64, 64, 50, -whichArc * Math.PI/2, -(whichArc + 1) * Math.PI/2, true);
              context.stroke();
              this.currentStates[whichArc] = newStates[whichArc];
            }
          }

          // Elements 4-6: Three balls
          for (var whichBall = 4; whichBall < 7; whichBall++)
          {
            if (newStates[whichBall] !== this.currentStates[whichBall])
            {
              context.fillStyle = newStates[whichBall] ? '#99f' : '#fff';
              context.beginPath();
              context.arc((116*this.whichLogo)+42+((whichBall-4) * 23), 51, 6, 0, Math.PI*2, true);
              context.fill();
              this.currentStates[whichBall] = newStates[whichBall];
            }
          }

          // Elements 7-10: Four sticks
          var stickX = 116*this.whichLogo+35;
          var stickY = 86;
          for (var whichStick = 7; whichStick < 11; whichStick++)
          {
            stickX += 10;
            stickY = (stickY === 59) ? 86 : 59;
            if (newStates[whichStick] !== this.currentStates[whichStick])
            {
              context.strokeStyle = newStates[whichStick] ? '#99f' : '#fff';
              //context.lineWidth = newStates[whichStick] ? 4 : 5;
              context.lineWidth = 4;
              context.lineCap = "butt";
              context.beginPath();
              context.moveTo(stickX, stickY);
              context.lineTo(stickX + 10, (stickY === 59) ? 86 : 59);
              context.stroke();
              this.currentStates[whichStick] = newStates[whichStick];
            }
          }

          // Element 11: Horizontal bar
          if (newStates[11] !== this.currentStates[11])
          {
            context.strokeStyle = newStates[whichArc] ? '#99f' : '#fff';
            //context.lineWidth = newStates[whichArc] ? 5 : 6;
            context.lineWidth = 6;
            context.lineCap = "round";
            context.beginPath();
            context.moveTo(116*this.whichLogo+48, 91);
            context.lineTo(116*this.whichLogo+83, 91);
            context.stroke();
            this.currentStates[11] = newStates[11];
          }
        }
        //  Logo.toString()
        //  --------------------------------------------------------------------------------
        this.toString = function()
        {
          var retVal = "{ "+this.whichLogo + ": [ ";
          for (var i=0; i< this.currentStates.length; i++) retVal += this.currentStates[i] + " ";
          return retVal + "] }";


        }
      }

      //  Frame objects
      //  ==================================================================================
      /*  There is an array of 12 element visibility values for each logo that is to change
       *  during this frame. To construct a Frame, you have to specify frame duration, in
       *  milliseconds.
       *  Then you can use set() to supply the list of elements that are to be updated for
       *  a logo in the frame.
       */
      //  Constructor
      //  ----------------------------------------------------------------------------------
      function Frame(duration)
      {
        this.duration = duration;
        this.logos=[];

        //  Frame.set()
        //  --------------------------------------------------------------------------------
        this.set = function(whichLogo, whichElements)
        {
          this.logos[whichLogo] = whichElements;
        }
        //  Frame.toggleElement()
        //  --------------------------------------------------------------------------------
        /*  Can be used to introduce variability into a script.
         */
        this.toggleElement = function(whichLogo, whichElement)
        {
          this.logos[whichLogo][whichElement] = ! this.logos[whichLogo][whichElement];
        }
        //  Frame.toString()
        //  --------------------------------------------------------------------------------
        this.toString = function()
        {
          var retVal = "[ ";
          for (var i=0; i< this.logos.length; i++)
          {
            if (typeof this.logos[i] !== "undefined")
            {
              retVal += i + ": " + this.logos[i] + " ";
            }
          }
          retVal += "]";
          return retVal;
        }
      }

      //  newFrame()
      //  -------------------------------------------------------------------------------------------
      /*  Updates the display at the end of each frame.
       */
      function newFrame()
      {
        for (var logo = 0; logo < logos.length; logo++)
        {
          if (typeof frames[currentFrame].logos[logo] !== "undefined")
          {
            logos[logo].update(frames[currentFrame].logos[logo]);
//  modify script on the fly
if (Math.random() < 0.1) frames[currentFrame].toggleElement(logo, frameCount % 12);
          }
        }

      frameCount++;
      currentFrame = (currentFrame + (Math.random()<0.3 ? +1 : -1)) % frames.length;
      if (currentFrame < 0) currentFrame = frames.length -1;
      if (run) setTimeout(newFrame, frames[currentFrame].duration * speedFactor);
      }

      //  Allow user to pause/resume the animation
      //  ---------------------------------------------------------------------------------
      function startStop(evt)
      {
        run = !run;
        startStopButton.removeChild(startStopButton.firstChild);
        startStopButton.appendChild(run ? stopText : startText);
        if (run) setTimeout(newFrame, 0);
      }

      //  Speed control
      //  ---------------------------------------------------------------------------------
      /*  Adjust the speedFactor to a value between 0.1 (fastest frame rate) and
       *  10 (slowest). Below 1.0, the delta is 0.1; above it, the delta is 1.0.
       */
      function adjustSpeed(direction)
      {
        var currentSpeed = 1.0 / speedFactor;
        if ((currentSpeed > 0.9) && (direction > 0) || (currentSpeed > 1.1) && (direction < 0))
        {
          currentSpeed = 1 / (1 / (currentSpeed + direction));
        }
        else
        {
          currentSpeed = currentSpeed + (direction / 10);
        }
        fasterButton.disabled = slowerButton.disabled = false;
        if (currentSpeed < 0.2)
        {
          slowerButton.disabled = true;
          currentSpeed = 0.1;
        }
        if (currentSpeed > 9.9)
        {
          fasterButton.disabled = true;
          currentSpeed = 10.0;
        }
        speedFactor = 1 / currentSpeed;
        document.getElementById('speedometer').firstChild.nodeValue =
                            "Current Speed: " + Math.round(100*currentSpeed)/100 + "X";
      }
      function decreaseSpeed(evt)
      {
        adjustSpeed( -1);
      }
      function increaseSpeed(evt)
      {
        adjustSpeed( +1);
      }

      //  init()
      //  ---------------------------------------------------------------------------------
      return {
        init: function()
        {
          startStopButton = document.getElementById('start-stopButton');
          slowerButton = document.getElementById('slowerButton');
          fasterButton = document.getElementById('fasterButton');

          Core.addEventListener(startStopButton, 'click', startStop);
          Core.addEventListener(slowerButton, 'click', decreaseSpeed);
          Core.addEventListener(fasterButton, 'click', increaseSpeed);

          theCanvas = document.getElementById('the-canvas');
          if ( typeof theCanvas.getContext != "function" )
          {
            theCanvas.outerHTML =
              "<h2>This browser cannot display the sign.</h2><p>Google Chrome, Safari, Opera, and Firefox all work.</p>";
            return;
          }
          theCanvas.width = "1062";
          theCanvas.height = "128";
          context = theCanvas.getContext('2d');
          context.strokeStyle = '#99f';
          context.lineWidth = '2';
          context.strokeRect(3,3,1056,122);
          context.lineWidth = '4';
          for (var i=0; i<9; i++)
          {
            logos[i] = new Logo(i);
          }

//  Generate a script: frame x displays logo x
frames[0] = new Frame(200);
frames[0].set(0, allOn);
for (var frame=1; frame<9; frame++)
{
  frames[frame] = new Frame(200);
  frames[frame].set(frame-1, allOff);
  frames[frame].set(frame, allOn);
}
frames[0].set(8, allOff);

          currentFrame = 0;
          setTimeout(newFrame, 0);
        }
      };
    }
  )()
);

