/**
 * generic_list_control.js
 * Description: GenericList class for LernOCP
 * Author: John Hughes
 * Date: 08/03/2008
 */

/**
 * GenericList is the javascript component to GenericList
 */
GenericList = Class.create();
GenericList.prototype = {
  
  /**
   * Initializes the control
   * @constructor
   * @member GenericListControl
   */
  initialize: function() {
    // Tell ReallySimplHistory to use PrototypeJS' JSON functions
    this.lists = {};
    this.historySeq = 0;
  },
  
  /**
   * Signals to the control that all lists have been registered. Initializes
   * the history control, and updates all lists if page is loaded for the first
   * time.
   */
  ready: function() {
    dhtmlHistory.initialize();

    if(dhtmlHistory.isFirstLoad()) {
    	this.historyChange(dhtmlHistory.getCurrentLocation(), null);
    	
      Object.keys(this.lists).each(function(key) {
        this.updateList(key);
      }.bind(this));
    }
    
    // This is wrapped in an anonymous function because RSH calls the listener function from null scope (window)
    dhtmlHistory.addListener(function(newLocation, historyData) {
      this.historyChange(newLocation, historyData);
    }.bind(this));
  },
  
  /**
   * Triggered at the start of an asynchronous action
   * @private
   */
  onActionStart: function() {
    WaitControl.busy();
  },
  
  /**
   * Triggered at the end of an asynchronous action
   * @private
   */
  onActionEnd: function() {
    WaitControl.ready();
  },
  
  /**
   * Fetches a list from the collection
   * @param {String} list_name The name of the list to fetch
   * @return {List} The list object
   * @private
   */
  getList: function(list_name) {
    list = this.lists[list_name];
    if(list)
      return list;
    else {
      alert("List '" + list_name + "' not found!");
      return null;
    }
  },
  
  /**
   * This method is called by the dhtmlHistory framework when it detects
   * that a history change has occurred in the browser.
   * @param {String} newLocation The location changed to
   * @param {String} historyData The data associated with this history, if any
   * @private
   */
  historyChange: function(newLocation, historyData) {
    var data = null;

    paramstring = unescape(newLocation);
    if(paramstring) {
      if(!paramstring.empty() && paramstring.isJSON()) {
        params = paramstring.evalJSON(true);
        thisSeq = params.seq;
        thisList = params.list;
        this.setParams(params);
      }
	  else
	  {
	    thisList = "";  
		thisSeq = "";
	  }
    
      // Check to see if we have matching data for these parameters
      if(historyStorage.hasKey(thisList + thisSeq)) {
        data = historyStorage.get(thisList + thisSeq); // List content is cached in the browser, so use it.
        this.setListData(thisList, data, true);
      }
    } else {
      // See if we are browsing back to 0 (beginning)
      thisSeq = 0;
      Object.keys(this.lists).each(function(list) {
        if(historyStorage.hasKey(list + thisSeq)) {
          data = historyStorage.get(list + thisSeq);
          this.setListData(list, data, true);
        }
      }.bind(this));
      
    }

    if(!data) { // We need to fetch fresh content from the server.
      Object.keys(this.lists).each(function(key) {
        this.updateList(key);
      }.bind(this));
    }
  },
  
  /**
   * Gets the params for all lists
   * @return {Object}
   * @private
   */
  getParams: function() {
    params = {};
    Object.keys(this.lists).each(function(key) {
      params[key] = this.getListParams(key);
    }.bind(this));
    return params;
  },
  
  /**
   * Sets the params from a history change for all lists
   * @private
   */
  setParams: function(params) {
    Object.keys(params).each(function(key) {
      if(key.strip().match(/^(seq|list)$/)==null) {
        list = this.getList(key);
        list.column = params[key].column;
        list.direction = params[key].direction;
        list.page = params[key].page;
        if(params[key].filters)
          list.filters = params[key].filters;
      }
    }.bind(this));
  },
  
  /**
   * Registers a list with the control
   * @param {String} list_name The name of the list
   * @param {Object} options The options hash to initialize the list
   */
  registerList: function(list_name, options) {
    this.lists[list_name] = {
      column: 'id',
      direction: null,
      page: null,
      container: $(list_name + '-GenericList-container'),
      edit_pane: $(list_name + '-GenericList-edit-pane'),
      busy_message: $(list_name + '-GenericList-busy-message'),
      data: null,
      controller_url: '/Controller/GenericListController.cfc',
      update_method: 'remoteGetRenderedList',
      delete_method: 'remoteDeleteRecord',
      editform_method: 'remoteGetRenderedEditForm',
      edit_method: 'remoteEditRecord',
      default_filters: { list: {}, edit: {}, rem: {} },
      filters: {},
      item_name: null
    };
    
    if(options && Object.keys(options).length > 0)
      Object.extend(this.lists[list_name], options);
    
    if(!this.lists[list_name].container)
      alert("Couldn't find the container for list " + list_name);
  },
  
  /**
   * Registers the edit form for a given list. This is called by the edit partial
   * when it gets inserted into the document.
   * @param {String} list_name The name of the list
   * @see #showListRecordEditForm
   */
  registerEditForm: function(list_name) {
    list = this.getList(list_name);
    list.editForm = $(list_name + '-edit-form');
    Event.observe(list.editForm, 'submit', function(event) {
      Event.stop(event);
      this.submitListRecordEditForm(list_name);
    }.bind(this));
    
    list.editCancelButton = $(list_name + '-cancel-button');
    Event.observe(list.editCancelButton, 'click', function(event) {
      Event.stop(event);
      this.hideListRecordEditForm(list_name);
    }.bind(this));
  },

  /**
   * Sets content for a given list.
   * @param {String} list_name The name of the list
   * @param {String} data The content to set for the list
   * @param {Boolean} noStore Whether to store the data in historyStorage or not 
   * @private
   */
  setListData: function(list_name, data, noStore) {
    list = this.getList(list_name);
    list.data = data;
    list.container.update(data);
    if(!noStore)
      historyStorage.put(list_name + (this.historySeq), data);
  },

  /**
   * Updates the list using ajax. Calls {@link #setListData} to
   * update the content of the list, and save it in {@link historyStorage}
   * @param {String} list_name The name of the list to update
   */
  updateList: function(list_name) {
    list = this.getList(list_name);
    params = $H(this.getListParams(list_name)).merge(Object.extend(list.default_filters.list || {}, list.filters));
    new Ajax.Request(
      list.controller_url, {
        method: 'post',
        parameters: params.merge({method: list.update_method, list: list_name})
                          .toQueryString(),
        onCreate: this.onActionStart(),
        onComplete: this.onActionEnd(),
        onSuccess: function(transport) {
          response = transport.responseText.strip();
          this.setListData(list_name, response);
        }.bind(this),
        onFailure: function(transport) { $('debug').innerHTML = transport.responseText.strip().replace(/\r|\n/g, ''); }
      }
    );
  },
  
  /**
   * Deletes a recording using an ajax call to the controller. Calls
   * {@link #updateList} after the remote call to update the table.
   * @param {String} list The name of the list to delete from
   * @param {Integer} id The ID of the record to delete.
   */
  deleteListRecord: function(list_name, id, filter) {
    list = this.getList(list_name);
    if(confirm("Click OK to delete this " + (list.item_name?list.item_name:list_name.capitalize()) + ". You will not be able to undo this action.")) {
      new Ajax.Request(
        list.controller_url, {
          method: 'post',
          parameters: $H({method: list.delete_method, list: list_name, id: id})
                        .merge(Object.extend(list.default_filters.rem || {}, filter || {}))
                        .toQueryString(),
          onCreate: this.onActionStart,
          onComplete: this.onActionEnd,
          onSuccess: function(transport) {
            this.updateList(list_name); // Update the list after deleting
          }.bind(this),
          onFailure: function(transport) { $('debug').innerHTML = transport.responseText.strip().replace(/\r|\n/g, ''); }
        }
      );
    }
  },
  
 /**
   * Fetches a rendered edit form for a given list, optionally for a
   * given record ID, and displays the content in the edit-pane div.
   * @param {String} list_name The list to show an edit form for
   * @param {Integer} id The optional record ID to edit
   */
  showListRecordEditForm: function(list_name, id, filter) {
    list = this.getList(list_name);
    new Ajax.Request(
      list.controller_url, {
        method: 'post',
        parameters: $H({method: list.editform_method, list: list_name, id: (id?id:0)})
                      .merge(Object.extend(list.default_filters.edit, filter || {}))
                      .toQueryString(),
        onCreate: this.onActionStart,
        onComplete: this.onActionEnd,
        onSuccess: function(transport) {
          response = transport.responseText.strip();
          edit_pane = list.edit_pane;
          edit_pane.update(response);
          //Effect.BlindDown(edit_pane);
          edit_pane.show();
          
          media_pane = $('media-panel');
          if(media_pane)
            Effect.BlindDown(media_pane);
          
          Nifty("div."+$w(edit_pane.className)[0],"same-height big");
        }.bind(this),
        onFailure: function(transport) {
          $('debug').innerHTML = transport.responseText.strip().replace(/\r|\n/g, '');
        }
      }
    );
    return false;
  },
  
 /** THIS IS A SPECIAL FUNCTION USED ONLY FOR QUESTION AND ANSWER EDITING
   * Fetches a rendered edit form for a given list, optionally for a
   * given record ID, and displays the content in the edit-pane div.
   * @param {String} list_name The list to show an edit form for
   * @param {Integer} id The optional record ID to edit
   */
  showListQuestionEditForm: function(list_name, id, filter) {
    list = this.getList(list_name);
    new Ajax.Request(
      list.controller_url, {
        method: 'post',
        parameters: $H({method: list.editform_method, list: list_name, id: (id?id:0)})
                      .merge(Object.extend(list.default_filters.edit, filter || {}))
                      .toQueryString(),
        onCreate: this.onActionStart,
        onComplete: this.onActionEnd,
        onSuccess: function(transport) {
          response = transport.responseText.strip();
          edit_pane = list.edit_pane;
          edit_pane.update(response);
          //Effect.BlindDown(edit_pane);
          edit_pane.show();
          
          media_pane = $('media-panel');
          if(media_pane)
            Effect.BlindDown(media_pane);
          
          Nifty("div."+$w(edit_pane.className)[0],"same-height big");
		  
		  var answer_inputs = $A($('answers').getElementsByTagName('input'));
		  
		  answer_inputs.each(function(node)
		  {
			if(node.name == "data.questions.answerbody")
			{
				Event.observe(node,'keyup',function(event){
					node.value = node.value.replace(/,/g,";");
				});
			}
		  });
		  
        }.bind(this),
        onFailure: function(transport) {
          $('debug').innerHTML = transport.responseText.strip().replace(/\r|\n/g, '');
        }
      }
    );
    return false;
  },
  
  /**
   * Submit the previously-shown edit form via AJAX. Updates the edit-pane
   * with the response from the controller.
   * @param {String} list_name The name of the list whos edit form to submit
   * @see #showListRecordEditForm
   */
  submitListRecordEditForm: function(list_name) {
    list = this.getList(list_name);
    
    // Ensure tinymce saves all editor content back to the form variables.
    tinyMCE.triggerSave();
    
    params = $H(Form.serialize(list.editForm, true)).merge(list.default_filters.edit);
    new Ajax.Request(
      list.controller_url, {
        method: 'post',
        parameters: params.merge({method: list.edit_method, list: list_name}).toQueryString(),
        onCreate: this.onActionStart,
        onComplete: this.onActionEnd,
        onSuccess: function(transport) {
          response = transport.responseText.strip();
          edit_pane = list.edit_pane;
          edit_pane.update(response);
          Nifty("div."+$w(edit_pane.className)[0],"same-height big");
        }.bind(this),
        onFailure: function(transport) {
          $('debug').innerHTML = transport.responseText.strip().replace(/\r|\n/g, '');
        }
      }
    );
  },
  
  /**
   * Hides the previously shown edit form.
   * @param {String} list_name The name of the list whos edit form to hide
   * @see #showListRecordEditForm
   */
  hideListRecordEditForm: function(list_name) {
    list = this.getList(list_name);
    Effect.BlindUp(list.edit_pane);
    list.edit_pane.update();
    
    media_pane = $('media-panel');
    if(media_pane)
      Effect.BlindUp(media_pane);
  },
  
  /**
   * Builds a select object of parameters suitable for submitting via
   * AJAX.
   * @param {String} list_name The list to build a parameter object for
   * @return {Object}
   * @private
   */
  getListParams: function(list_name) {
    list = this.getList(list_name) 
    ret = {};
    if(list.column)
      ret.column = list.column;
      
    if(list.direction)
      ret.direction = list.direction;
      
    if(list.page)
      ret.page = list.page;
      
    if(list.filters)
      ret.filters = list.filters;
      
    return ret;
  },
  
  /**
   * Called after a parameter changes on a list. Stores the current list
   * parameters as a History point. Calls {@link #updateList} with the
   * new parameters.
   * @param {String} list_name The list whos parameters have changed
   * @private
   */
  parameterChanged: function(list_name) {
    this.historySeq++;
    params = $H(this.getParams()).merge({seq: this.historySeq, list: list_name});

    history_value = Object.toJSON(params);
    dhtmlHistory.add(escape(history_value));
    
    this.updateList(list_name);
  },
  
  /**
   * Sets the sort column on a given list. If the given column is already
   * sorted, then the direction will be reversed.
   * @param {String} list_name The list to set the sort column for
   * @param {String} column The column to sort by
   */
  setSortColumn: function(list_name, column) {
    // Grab the updated data using Ajax, stuff it in the container,
    // stuff it in historyStorage, then add dhtmlHistory.new()
    
    var list = this.getList(list_name);

    if(list.column == column || list.column == null)
      list.direction = (list.direction=="DESC")?"ASC":"DESC";
    list.column = column;
    this.parameterChanged(list_name); // Updates the list
  },
  
  /**
   * Sets the page for a given list. Will update the list afterwards.
   * @param {String} list_name The list whos page will be set.
   * @param {Integer} page The page to switch to.
   */
  setPage: function(list_name, page) {
    this.getList(list_name).page = page;
    this.parameterChanged(list_name);
  },
  
  /**
   * Sets all filter parameters for a given list. Will update the list afterwards.
   * @param {String} list_name The list whos parameters will be set.
   * @param {Hash} params Hash object of parameters
   */
  setListFilterParams: function(list_name, params) {
  	this.getList(list_name).filters = params;
  	this.parameterChanged(list_name);
  }
};
