/* Copyright 2008 Google Inc. All Rights Reserved.
 * Author: ssoneff@google.com (Steven Soneff)
 * 
 * Interactive JavaScript Tutorial for the Google Finance GData API
 * 
 * The methods in this file generate the tutorial content from a template
 * structure for a specified set of examples in the accompanying index.html and
 * set up event handlers for login/logout and run operations.  The content 
 * (code and description) for each example is fetched with AJAX after the page 
 * has loaded.
 * 
 * These methods make use of the latest version of the GData client JavaScript
 * library including AuthSub for JavaScript.  jQuery and a modified version of
 * Codepress engine (provided by the GData team) as well as a the "New Wave"
 * tabs UI library are also required.
 * 
 */

// Protected namespace.
var jsguide = {};

// The Google Data Finance service object, used for AuthSub.
jsguide.financeService = null;

// The current action use case that the user is executing.
jsguide.currentActionId = null;

// CSS IDs.
jsguide.MAIN = 'main';
jsguide.DISPLAY = 'display';
jsguide.LOGIN_BUTTON = 'login';
jsguide.ACTION_SECTION = 'action';
jsguide.TAB_SECTION = 'tabsection';
jsguide.TAB_TEMPLATE = 'tabtemplate';
jsguide.ANCHOR = 'anchor';
jsguide.ACTION_TEXT = 'actiontext';
jsguide.CODELINK = 'codelink';
jsguide.RUNCODE_BUTTON = 'runcode';
jsguide.RERUNCODE_BUTTON = 'reruncode';
jsguide.EDITCODE_BUTTON = 'editcode';
jsguide.RESETCODE_BUTTON = 'resetcode';
jsguide.SOURCECODE = 'sourcecode';
jsguide.OVERVIEW = 'overview';
jsguide.TAB = 'tab';

// Constant estimate of line height in Codepress, about 18px per line.
jsguide.PX_PER_LINE = 18;

// Constant URLs.
jsguide.SCOPE_URL = 'http://finance.google.com/finance/feeds/';
jsguide.PARENT_URL = 'http://code.google.com/apis/finance/' + 
    'developers_guide_js.html#Interactive_Samples';

// Constant Labels.
jsguide.LOGIN_LABEL = 'Login';
jsguide.LOGOUT_LABEL = 'Logout';
jsguide.RUNCODE_LABEL = 'Run';
jsguide.RUNNING_LABEL = 'running ...';
jsguide.LOADING_LABEL = 'loading ...';
jsguide.CODELINK_LABEL = 'download source code';
jsguide.SAMPLE_DIR = '/static/finance/samples/';
jsguide.SAMPLE_OVERVIEW_EXTENSION = '.html';
jsguide.SAMPLE_SOURCECODE_EXTENSION = '.js';

/**
 * Global initalization.  
 * - Hides page content if this is a token redirection.
 * - Redirect the main URL if this page isn't accessed through an iframe.
 * - Initiate UI generation process.
 */   
jsguide.main = function() {  

  if (jsguide.isTokenRedirect()){   
    jQuery('body').css({visibility: 'hidden'});
  } else {
    if (!jsguide.isIframe()) {
      window.location.href = jsguide.PARENT_URL; 
    }
    // Begin UI initialization.
    setTimeout(jsguide.initUI, 0);
  }
};

/**
 * UI initialization.
 * - Start the DOM generation process.
 * - Attach event handlers to UI components for AuthSub login/logout.
 */
jsguide.initUI = function() {

  // Begin DOM generate.
  jsguide.generateCode();

  // set up event handlers for login/logout buttons.
  jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).click(function() {
    if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
      google.accounts.user.login();
    } else {
      jsguide.setRunEnabled(false);
      google.accounts.user.logout();
      jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGIN_LABEL});

      var re = new RegExp(['_', jsguide.RUNCODE_BUTTON].join(''));

      jQuery(jsguide.getId(jsguide.TAB_SECTION)).
          find('input').each(function() {
              if (re.test(jQuery(this).attr('id'))) {
                jQuery(this).get(0).disabled = false;
                jQuery(this).attr({'value': jsguide.LOGIN_LABEL});
              }
          });
    }
  });  

  // Initialize display label and runnable status based on login status.
  if (jsguide.hasToken(jsguide.SCOPE_URL)) {
    jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGOUT_LABEL});
    jsguide.setRunEnabled(true);
  } else {
    jQuery(jsguide.LOGIN_BUTTON).attr({value: jsguide.LOGIN_LABEL});
    jsguide.setRunEnabled(false);
  }
};

/**
 * Check if this script is being executed inside an iframe.
 * @return {boolean} True/false to indicate if inside an iframe. 
 */
jsguide.isIframe = function() {
  
  var status = false; 

  try {
    // Attempt to access parent parent (if there is one).
    status = !(parent.location.href == window.location.href);
  } catch (exception) {
    // A cross domain parent access cause an exception, so now 
    // we know this page is being loaded as an iframe.
    status = true;
  }
  //status = true;
  return status;
};

/**
 * Check if there is a token valid for the input scope URL.
 * @param {string} scope The Scope URL.
 * @return {boolean} True/false to indicate if there is a valid token for this
 *     scope URL.
 */  
jsguide.hasToken = function(scope) {
  var success = true;
  if (google.accounts.user.checkLogin(scope) === '') {
    success = false;
  }
  return success;
};

/**
 * Detect whether the current session is a token redirect
 * @return {Boolean} True/false to whether this is a redirect session
 */  
jsguide.isTokenRedirect = function() {

  var status = false;
  
  var url = location.href;

  var matchArr = url.match(/#2/);
  
  if (matchArr != null) {
    status = true;
  }

  return status;
};

/*
 * Render all buttons within each tab section to be enabled or disabled.
 * @param {boolean} enabled The boolean flag to indicate enabled or disabled.
 */
jsguide.setRunEnabled = function(enabled) {

  var re = new RegExp(['_', jsguide.RUNCODE_BUTTON].join(''));

  jQuery(jsguide.getId(jsguide.TAB_SECTION)).find('input').each(function() {
    if (!re.test(jQuery(this).attr('id'))) {
      jQuery(this).get(0).disabled = !enabled;
    }
  });
};

/*
 * This method takes the tabtemplate DOM structure and automatically 
 * generate copies of it to represent each action links listed within  
 * the action div.  Each newly generated tab section is subsequently
 * initialized with event handlers for all its feature buttons.
 */
jsguide.generateCode = function() {     

  // Grab all links within the action div section.
  jQuery([jsguide.getId(jsguide.ACTION_SECTION),' a'].
      join('')).each(function() {
    
    // Each actionLink represents a use case which will generate a 
    // whole new tab section based on the tabtemplate tree
    var actionLink = jQuery(this);

    // Store its ID as its actionId.
    var actionId = actionLink.attr('id');

    // Store the action link text.
    var actionText = actionLink.html();

    // Grab the tabtemplate div and clone everything in it.
    var clonedTemplate = jQuery(jsguide.getId(jsguide.TAB_TEMPLATE)).clone();

    // Rename the ID of this clone to be prepended with the actionId.
    clonedTemplate.attr({'id': [actionId, '_', jsguide.TAB].join('')});

    // Rename all IDs of every descendent element inside the 
    // cloned template to be prepended with the actionId.
    clonedTemplate.find('*[id]').each(function() { 
      var descendent = jQuery(this);
      var newId = [actionId, '_', descendent.attr('id')].join('');
      descendent.attr({'id': newId});  
    });  
   
    // Point the action link to this newly cloned template section.
    actionLink.attr({href: jsguide.getId(jsguide.ANCHOR, actionId)}); 

    // Locate the source code link element to place the source code link.
    codeLinkId = jsguide.getId(jsguide.CODELINK, actionId);

    // Form the URL to the source code.
    sourceUrl = [jsguide.SAMPLE_DIR, actionId,
        jsguide.SAMPLE_SOURCECODE_EXTENSION].join('');

    // Form the URL to the souce code in HTML format.
    sourceUrlHtml = ['<a target="_blank" href="', sourceUrl, '">', 
        jsguide.CODELINK_LABEL, '</a>'].join('');

    // Add the source code link to the code link element
    clonedTemplate.find(codeLinkId).html(sourceUrlHtml);

    // Grab the place holder (tabsection) where all these generated 
    // cloned templates will go into
    var tabsection = jQuery(jsguide.getId(jsguide.TAB_SECTION));   
    
    // Create an anchor link at the beginning of each cloned template
    var anchor = jQuery('<a/>');
    anchor.attr({name: [actionId, '_', jsguide.ANCHOR].join('')});
    tabsection.append(anchor);
    tabsection.append(clonedTemplate);
    
    // Set the output tab of this cloned template with the action text.
    jQuery(jsguide.getId(jsguide.ACTION_TEXT, actionId)).html(actionText);

    // if there is no auth token, replace the "Run" label with "Login" 
    if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
      jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).attr(
      {'value': jsguide.LOGIN_LABEL});
    }

    // Set up overview 
    var overviewFileName = actionId + jsguide.SAMPLE_OVERVIEW_EXTENSION;
    jsguide.setOverviewFile(overviewFileName, 
        jsguide.getId(jsguide.OVERVIEW, actionId));

    // Set up CodeMirror here
    var textarea = document.getElementById(actionId + '_' + jsguide.SOURCECODE);
    var codeMirrorName = [actionId, '_', jsguide.SOURCECODE].join('');

    window[codeMirrorName] = CodeMirror.fromTextArea(codeMirrorName, {
      height: "600px",
      content: textarea.value,
      parserfile: ["tokenizejavascript.js", "parsejavascript.js"],
      stylesheet: "/static/codemirror/css/jscolors.css",
      path: "/static/codemirror/js/",
      autoMatchParens: true,
      initCallback: function() {
        var fileName = actionId + jsguide.SAMPLE_SOURCECODE_EXTENSION;
        jsguide.setSourceFile(fileName, codeMirrorName);      
      }
    });

    // Set up the click handler of the runcode button of this cloned template
    jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).click(function() {
      
      // When this run button is really a login (no token is found)
      if (!jsguide.hasToken(jsguide.SCOPE_URL)) {
        jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).trigger('click');
        return;
      }

      // When this button, this action case is the current action
      jsguide.currentActionId = actionId;

      // Between each run session of the code, the display should be cleared
      jsguide.clearDisplay();  

      PRINT(jsguide.RUNNING_LABEL);

      // Dynamically retrieve the current CodeMirror object
      var currentCodeMirror = 
          window[[actionId, '_', jsguide.SOURCECODE].join('')];
      
      // Retrieve the current CodeMirror object source content, and execute it
      try {
        eval(currentCodeMirror.getCode());
      } catch(e) {
        PRINT(['JavaScript error(s): ', e.message].join(''));
      }
      // Go to the output tab for this cloned template to wait for output
      jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
          tabsClick(2);
    });
    
    // Set up the resetcode button.
    jQuery(jsguide.getId(jsguide.RESETCODE_BUTTON, actionId)).
        click(function() {
      var fileName = actionId + jsguide.SAMPLE_SOURCECODE_EXTENSION;
      jsguide.setSourceFile(fileName, 
          [actionId, '_', jsguide.SOURCECODE].join(''));
    });
    
    // Set up the reruncode button.
    jQuery(jsguide.getId(jsguide.RERUNCODE_BUTTON, actionId)).
        click(function() {
      jQuery(jsguide.getId(jsguide.RUNCODE_BUTTON, actionId)).
          trigger('click');
    });
    
    // Set up the editcode button.
    jQuery(jsguide.getId(jsguide.EDITCODE_BUTTON, actionId)).
        click(function() {
      jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
          tabsClick(1);
    });

    // Set up the viewcalendar button.
    jQuery(jsguide.getId(jsguide.VIEWCALENDAR, actionId)).click(function() {
      jsguide.showCalendar(actionId);      
    });

    // This is specific to the jQuery UI Tabs plugin. Basically, all the href 
    // of within the targeted UL will be mapped to a div section.  So This is 
    // rewriting all the links inside UI to point to the right anchor link 
    // (start with #).
    jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).
        find('*[href]').each(function() {
      var href = jQuery(this).attr('href');
      var re = /#([a-zA-Z0-9]+)$/;
      href.match(re);
      var href = RegExp.$1;
      jQuery(this).attr({'href': ['#', actionId, '_', href].join('')});      
    });

    // Now this cloned template is finished rewriting all its IDs and links,
    // ready for the Tabs plugin initialization.  No specific actions are
    // associated with tab behaviors, so use empty functions.
    jQuery([jsguide.getId(jsguide.TAB, actionId), ' > ul'].join('')).tabs({
      click: function() {},
      hide: function() {},
      show: function() {}
    });
    
  });  
};

/*
 * AJAX fetch the content specified by the file name and set the content of
 * the DIV ID with this content.
 * @param {string} fileName The name of the file to be fetched with AJAX.
 * @param {string} divId The DIV ID where its HTML content will be set with the
 *     file's content.
 */
jsguide.setOverviewFile = function(fileName, divId) {
  
  // Indicate the file is currently being loaded.
  jQuery(divId).html(jsguide.LOADING_LABEL);
  
  // The file is residing in the samples directory of the same host of 
  // this script.
  var fileUrl = jsguide.SAMPLE_DIR + fileName;

  jQuery.ajax({      
      url: fileUrl, 
      success: 
          function(text){
            // On success put the content of the file in the DIV
            jQuery(divId).html(text);
          },
      cache: false
      });
};

/*
 * AJAX fetch the source code specified by the file name and set the CodeMirror 
 * object with the source code.
 * @param {string} fileName The name of the file to be fetched with AJAX.
 * @param {string} objectName The name of the CodeMirror object (mapped to an
 *     action case.
 */
jsguide.setSourceFile = function(fileName, objectName) {

  var object = window[objectName];
  
  if (object) {
    object.setCode(jsguide.LOADING_LABEL);
  }
 
  var sourceFileUrl = jsguide.SAMPLE_DIR + fileName;

  jQuery.ajax({
    url: sourceFileUrl,
    cache: false,
    success: function(text){
      // Just before loading the source code to the CodeMirror, try to estimate 
      // and set the height of the sourcecode textarea based on the fetched 
      // source code.  This way, the height of the textarea will be just fitted
      // for the length of the source code.
      var newLineCount = 0;
      for (var i = 0; i < text.length; i++) {
        var c = text.charAt(i);
        if (c == '\n') {
          newLineCount++;
        }
      }         
      object.setCode(text);
      //object.win.parent.frames[0].style.height = newLineCount * jsguide.PX_PER_LINE;        
    }
  });
};

/*
 * Handles error conditions: displays error, disables run
 * @param {Object} e The error.
 */  
jsguide.handleError = function(e) {
  var error = e.cause ? e.cause.statusText : e.message;
  
  jQuery(jsguide.getId(jsguide.getId(jsguide.DISPLAY), 
      jsguide.currentActionId)).html(error);
  
  if (jsguide.hasToken(jsguide.SCOPE_URL)) {
    jsguide.setRunEnabled(false);
    google.accounts.user.logout();
    jQuery(jsguide.getId(jsguide.LOGIN_BUTTON)).
        attr({value: jsguide.LOGIN_LABEL});
  }
};

/*
 * This method return the actual CSS ID of an element. Since a lot of 
 * the DOM elements are auto-generated, prepended the actionId to form the 
 * actual CSS ID, if one is available.
 * @param {string} elementId The CSS ID of an element.
 * @param {string} actionId The action case this element live within.
 * @return {string} The actual CSS ID that points to an element within 
 *     this page.
 */
jsguide.getId = function(elementId, actionId) {
  var actualId = ['#', elementId].join('');
  if (actionId) {
    actualId = ['#', actionId, '_', elementId].join('');
  }
  return actualId;
};

/**
 * Clear the display area of the current action section
 */  
jsguide.clearDisplay = function() {
  var display = jQuery(jsguide.getId(jsguide.DISPLAY, 
      jsguide.currentActionId));
  display.empty();
};

/*
 * Convenient method for the user of the dev guide to use within the
 * sample code to print out the given text in the display area of the output
 * tab.
 * @param {string} Some text.
 */
var PRINT = function(text) {

  var display = jQuery(jsguide.getId(jsguide.DISPLAY, 
      jsguide.currentActionId));
  
  var re = /running \.\.\./;

  if (re.test(display.html())) {
    display.empty();
  }
  
  display.append(text.toString());
  display.append('<br/>');
};

// Load the Google data JavaScript client library
google.load('gdata', '1.x');

// Set the callback function when the library is ready
google.setOnLoadCallback(jsguide.main);
