
/** TimelineBuilder Constructor **/
function TimelineBuilder () {

  this.currentFrameIndex = 0;
  this.updateFrameLock   = false;
  this.frames            = new Array();
  this.totalFrameCount   = 0;  // this does not decrement after deleting a frame
  this.currentFormState  = '';  // This will be the serialized form - if changed
                                // then the page will nag on exit.  
  Event.observe(window, 'load', this.initialize.bind(this));

};

/*********************************** 
 
 * Timeline Builder Class Methods *
  
   Currently, these methods are used as class methods b/c
   other objects, who may not have an instance of the TimelineBuilder
   may need to access them.
     
*/

/* Handle the character onscreen counters 
   This expects to be called either as an event handler or
   with a text input id name
*/
TimelineBuilder.counter = function(arg) {
  var textid = arg;
  if ( typeof(arg) == 'object' && arg instanceof Event ) {
    textid = arg.element().identify();
  }
    
  var counterid = textid + '_count';
  var maxid     = textid + '_max';
  var alertid   = textid + '_alert';

  $(counterid).value = $F(textid).length;
  if ( $F(textid).length > $(maxid).value ) {
    $(alertid).style.visibility = 'visible';  
  }
  else {
    $(alertid).style.visibility = 'hidden';
  }
};

/* Return boolean: whether all of the timeline header fields have been
   completed (section/date/slug).
*/
TimelineBuilder.hasCompleteId = function() {
  if ( $F('section') && $F('date') && $F('slug') ) {
    return true;
  }
  return false;
}


/*********************************** 
 *
 * TimelineBuilder Instance Methods 
 *  
 ***********************************/
TimelineBuilder.prototype.toString = function () {
  return 'TimelineBuilder - currentFrameIndex: '+this.currentFrameIndex;
}

/* Initialize the timeline - call after window.onload 
*/
TimelineBuilder.prototype.initialize = function () {

  // initialize Form Validation
  this.topFormValidator = new Validation('topnavigationform', 
                            { 
                              immediate : true, 
                              onSubmit : false
                            }
                          );
  this.mainFormValidator = new Validation('timelineform', 
                             {
                               immediate : true, 
                               onSubmit : false
                             }
                           );

  // Toggle status messages at the top of the page.
  // When the page loads, the status messages are toggled on, but
  // no status messages have been updated.  So, the page appears to
  // have the status messages toggled off.  But, they are actually on.
  // hover over the image and a tooltip will display the current status
  $('statustoggle').observe('click', function (e) {
      var rc = Effect.toggle('statusmessages', 'appear');
      if ( $('statusmessages').next('div').down('img').title.search('Hide') >= 0 ) {
        $('statusmessages').next('div').down('img').title = 'Show status messages';
      }
      else {
        $('statusmessages').next('div').down('img').title = 'Hide status messages';      
      }
    }
  );

  // Initialize character counter event handlers
  $('timelinetitle').observe('change', TimelineBuilder.counter);
  $('timelinetitle').observe( 'keyup', TimelineBuilder.counter);  
  $('introtext').observe('change',     TimelineBuilder.counter);
  $('introtext').observe( 'keyup',     TimelineBuilder.counter);
  TimelineBuilder.counter('timelinetitle');
  TimelineBuilder.counter('introtext');

  // Add next frame button
  $('addnextframe').observe('click', function (e) {
      this.addFrame();
      Event.stop(e);
    }.bind(this)
  );
  
  // Back/forward buttons
  $('backbutton_a').observe('click', this.frameBack.bind(this));
  $('forwardbutton_a').observe('click', this.frameForward.bind(this));
  
  // Select Timeframe type
  $('timeframetype').observe('change', function (e) {
      this.updateAllTimeframeSelectors();
      this.generateFrameNavigation();
    }.bind(this)
  );
  
  // Image Uploader buttons
  $('uploadimages').observe('click', function (e) {
      this.showUploader()
      Event.stop(e);
    }.bind(this) 
  );
  $('uploadimages2').observe('click', function (e) {
      this.showUploader()
      Event.stop(e);
    }.bind(this) 
  );

  // Image Bank 
  this.imagebank = new ImageBank();

  /*** Image Bank buttons ***/
  // select image
  $('btn_imagebank_sel').observe('click', function (e) {
      var src = this.imagebank.getHighlightedImageSrc();
      if ( src ) {
        this.imagebank.clearHighlight();
        this.frames[this.currentFrameIndex].selectImage(src);
        this.frames[this.currentFrameIndex].updateFrameNavigationCell();
      }
      Event.stop(e);
    }.bind(this)
  );

  // delete one image
  $('btn_imagebank_del').observe('click', function (e) {
      var src = this.imagebank.deleteImage();
      this.updateAllMatchingSelectedImages(src, null);
      Event.stop(e);
    }.bind(this)
  );
  
  // delete all images
  $('btn_imagebank_bye').observe('click', function (e) {
      this.imagebank.deleteAllImages();
      this.updateAllSelectedImages(null);
      Event.stop(e);
    }.bind(this)
  );
  
  // Save timeline buttons
  $('savetimeline0').observe('click', function (e) { 
      this.saveTimeline();
      
      //this.sortNavigationRow();
      
      Event.stop(e);
    }.bind(this)
  ); 
  $('savetimeline1').observe('click', function (e) { 
      this.saveTimeline();
      Event.stop(e);
    }.bind(this)
  ); 

  // Preview timeline buttons
  $('previewtimeline0').observe('click', function (e) { 
      this.previewTimeline();
      Event.stop(e);
    }.bind(this)
  ); 
  $('previewtimeline1').observe('click', function (e) { 
      this.previewTimeline();
      Event.stop(e);
    }.bind(this)
  ); 
  
  // Publish timeline buttons
  $('publishtimeline0').observe('click', function (e) { 
      this.saveTimeline();
      Event.stop(e);
    }.bind(this)
  ); 
  $('publishtimeline1').observe('click', function (e) { 
      this.saveTimeline();
      Event.stop(e);
    }.bind(this)
  );   

  // Refresh/Exit from browser - beforeunload
  // This nags the user when the user tries to navigate away from the 
  // page and the form has changed.  
  this.currentFormState = $('timelineform').serialize(false);
  
  window.onbeforeunload = function () {
    var warning = 'The timeline has changed.  Your changes will '
                  + 'be lost if you press OK.';  
    var newContents = $('timelineform').serialize(false);
    if ( newContents != this.currentFormState ) {    
      return warning;
    }
  }.bind(this);



  this.initializeFrames();
   
  if ( this.totalFrameCount == 0 ) {  
    this.addFrame();
  }
  else {
    this.generateFrameNavigation();
    this.updateAllTimeframeSelectors();    
  }
};

/* initializeFrames -- When a saved timeline is loaded from the server,
*/
TimelineBuilder.prototype.initializeFrames = function () {
  this.updateFrameLock = true;

  var frameIds = $$('div.frameinstancecontainer');

  for ( var i=0; i<frameIds.length; i++ ) {
    var frame = new TimelineFrame(frameIds[i].identify());
    frame.initialize(this);
    this.frames.push(frame);
  }
  this.activateFrame(0);
  this.totalFrameCount = frameIds.length;
  this.updateFrameLock = false;
}

/* addFrame()
   Create the frame object instance and make an ajax call
   for the frame page 
*/
TimelineBuilder.prototype.addFrame = function () {
  if ( this.updateFrameLock ) {
    alert('Please wait. Frames are initializing.'); return false; 
  }
  
  if ( this.frames.length < Conf.maxFrameCount ) {
    this.updateFrameLock = true;
    
    var frame = new TimelineFrame('frame' + this.totalFrameCount);
    var frame_url = Conf.Path.appRoot + '/' + Conf.Path.controller + '/'
                    + 'frame/?frame_id=' + this.totalFrameCount;
                    
    this.frames.push(frame);
    
    var self=this;  // maintain context of this in the callback function
    new Ajax.Updater(
      'timelineframecontainer',
      frame_url,
      {
        method : "get",
        onComplete : function () {
          frame.initialize(self);
          frame.setFrameLabel('Frame ' + self.frames.length);
          self.activateFrame(self.frames.length-1);
          self.generateFrameNavigation();
          frame.updateTimeFrameSelection();
          self.totalFrameCount += 1;
          self.currentFormState = $('timelineform').serialize(false);
          self.updateFrameLock = false;
        },
        insertion: Insertion.Bottom
      }  
    );
  }
  else {
    alert("Timelines are limited to "+Conf.maxFrameCount+" frames.");
  }
};


/* Update all timeframe selections */
TimelineBuilder.prototype.updateAllTimeframeSelectors = function () {
  for (var i=0; i < this.frames.length; i++) {
    this.frames[i].updateTimeFrameSelection();
  }
}

/* activateFrame
   Show the frame + hide the other active frame.
   Also, hide or show back/forward buttons as necessary
*/
TimelineBuilder.prototype.activateFrame = function (frameIndex) {

  if ( frameIndex >= 0 && frameIndex < this.frames.length ) {
    this.frames[this.currentFrameIndex].hide();
    this.frames[frameIndex].show();
    this.currentFrameIndex = frameIndex;
  }
  
  // Handle arrow button changes
  if ( frameIndex <= 0 ) {
    $('backbutton_img').hide();
  }
  else {
    $('backbutton_img').show();
  }
  if ( frameIndex < this.frames.length-1 ) {
    $('forwardbutton_img').show();
  }
  else {
    $('forwardbutton_img').hide();
  }
};

TimelineBuilder.prototype.frameBack = function () {
  var next = parseInt(this.currentFrameIndex) - 1;
  this.activateFrame( next );
};

TimelineBuilder.prototype.frameForward = function () {
  var next = parseInt(this.currentFrameIndex) + 1;
  this.activateFrame( next );
};

// Delete the current frame
TimelineBuilder.prototype.deleteFrame = function () {

  if ( ! confirm('Delete this frame?') ) {
    return null;
  }

  var old_frame;
  var delete_frame_index = this.currentFrameIndex;
   
  // activate new frame + splice out old frame
  if ( this.currentFrameIndex == 0 ) {
    old_frame = this.frames.splice(0, 1);
    this.activateFrame(0); 
  }
  else {
    this.activateFrame(this.currentFrameIndex - 1);  
    old_frame = this.frames.splice(delete_frame_index, 1);
  }

  // remove html from old frame
  $(old_frame[0].frameId).innerHTML = '';
    
  if ( this.frames.length > 0 ) {  
    //update frame labels
    for (var i=0; i<this.frames.length; i++) {
      this.frames[i].setFrameLabel('Frame ' + (i+1)); 
    }
  
    // regenerate the frame navigation
    this.generateFrameNavigation();
  }
  else {
    this.addFrame();
  }
};

// Generate the frame navigation based on the current frames
TimelineBuilder.prototype.generateFrameNavigation = function () {
      
  var nav_row = '';
  for ( var i=0; i < this.frames.length; i++ ) {
    nav_row += this.frames[i].frameNavigationCell(i);
  }
  $('navigationrow').innerHTML = nav_row;
  
  this.updateFrameNavigationHandlers();
};

// Setup event handlers for the navigation cells
TimelineBuilder.prototype.updateFrameNavigationHandlers =
  function (frameIndex) {
    for ( var i=0; i < this.frames.length; i++ ) {
      this.frames[i].getFrameNavigationCell().observe( 'click',
        function (e) {
          var cell;
          if ( e.element().match('td') ) {
            cell = $F(e.element().down('input').identify());
          }
          else {
            cell = $F(e.element().up('td').down('input').identify());
          }
          this.activateFrame( cell );
        }.bindAsEventListener(this)
      );
    } 
  }

/* showUploader
   request a new uploader.html + display it
*/
TimelineBuilder.prototype.showUploader = function () {

  if ( ! TimelineBuilder.hasCompleteId ) {
    return false;
  }

  Uploader.showUploader($F('section'), $F('date'), $F('slug'));
}


/* Save Timeline - this posts the form
   The onComplete parameter is a function, and will be called when the
   Ajax.Updater completes.
*/
TimelineBuilder.prototype.saveTimeline = function (onComplete) {

  var timelineKeyOK = this.topFormValidator.validate();
  this.mainFormValidator.validate();

  if ( TimelineBuilder.hasCompleteId() && timelineKeyOK ) {
    var saveUrl = Conf.Path.appRoot + '/' + Conf.Path.controller
                       + '/savetimeline/'
                       + $F('section') + '/' 
                       + $F('date')    + '/'
                       + $F('slug')    + '/';
                       
    this.currentFormState = $('timelineform').serialize(false);
    var params = {
                   method : "post",
                   parameters : $('timelineform').serialize(true)
                 };
                 
    if ( onComplete != null ) {
      params.onComplete = onComplete;
    }
    
    new Ajax.Updater( 'statusmessages', saveUrl, params );  
    window.scrollTo(0,0);
  }
}

/* Preview Timeline - first save the form, then open a new window with the 
   flash component pointed to the saved xml document
*/
TimelineBuilder.prototype.previewTimeline = function () {

  var preview_url = Conf.Path.appRoot + '/' + Conf.Path.controller 
                    + '/preview/' + $F('section')
                    + '/' + $F('date') + '/' + $F('slug') + '/';  
                    
  var param = "toolbar=no,location=yes,status=no,scrollbars=no,"
            + "resizable=no,width=604,height=455,left=100,top=100";

  this.saveTimeline(
    function () {
      window.open( preview_url, 'Timeline Preview', param );
    }
  );
}

/* Update all matching selected images
   Update all of the selected images in all frames with newImage
   where the selected image equals the given oldImage.
   This also updates the navigation image.
*/
TimelineBuilder.prototype.updateAllMatchingSelectedImages = 
function (oldImage, newImage) {
  for ( var i=0; i < this.frames.length; i++ ) {
    if ( this.frames[i].getSelectedImage() == oldImage ) {
      this.frames[i].selectImage(newImage);
    }
  }
}

/* Update All Selected Images 

*/
TimelineBuilder.prototype.updateAllSelectedImages = 
function (newImage) {
  for ( var i=0; i < this.frames.length; i++ ) {
    this.frames[i].selectImage(newImage);
  }
}


/* sortNavigationRow - this takes all of the navigation thumbnails and
   sorts by date - undefined dates go to the end.
   This is currently not being used.  Frames are sorted in the order
   they are created.  If the timeline is refreshed from the server,
   the frames will come back sorted by time.
*/
TimelineBuilder.prototype.sortNavigationRow = function () {
  var cells = $('navigationrow').childElements();
  var dateRegex = /(\d{1,2}[\/\\]\d{1,2}[\/\\]\d{4})/;
  var timeRegex = /(\d{1,2}:\d{2} (?:AM|PM))/;
  var dateStr;
  
  if ( $F('timeframetype') == 'ymd' ) {
  
    cells.sort( function(a,b) {
        var aa = a.innerHTML.match(dateRegex);
        var ba = b.innerHTML.match(dateRegex);
        if ( aa == null && ba == null ) {
          return 0;
        } else if ( aa == null ) {
          return 1;
        } else if ( ba == null ) {
          return -1;
        } else {
          var atime = Date.parse(dateStr + aa[1]);
          var btime = Date.parse(dateStr + ba[1]);
          return atime-btime;
        }
      }
    );
    
    for(var i=0; i<cells.length; i++ ) {
      $('navigationrow').appendChild(cells[i]);
    }
  }
  else {
    // This sort has not yet been implemented
  }
}




