/*! drupalgap 2015-06-27 */
// Create the drupalgap object.
var drupalgap = {
  blocks: [],
  content_types_list: {}, /* holds info about each content type */
  date_formats: { }, /* @see system_get_date_formats() in Drupal core */
  date_types: { }, /* @see system_get_date_types() in Drupal core */
  entity_info: {},
  field_info_fields: {},
  field_info_instances: {},
  field_info_extra_fields: {},
  ng: { }, /* holds onto angular stuff */
  remote_addr: null, /* php's $_SERVER['REMOTE_ADDR'] via system connect */
  site_settings: {}, /* holds variable settings from the Drupal site */
};

// Create the drupalgap module for Angular.
angular.module('drupalgap', [])
  .value('drupalgapSettings', null);

// Create the app.
var dgApp = angular.module('dgApp', dg_ng_dependencies());

// Run the app.
dgApp.run([
    '$rootScope', '$routeParams', '$location', 'drupalSettings',
    function($rootScope, $routeParams, $location, drupalSettings) {

      dpm('dgApp.run()');
      console.log(arguments);

      dg_ng_set('routeParams', $routeParams);
      dg_ng_set('location', $location);
      
      dg_ng_set('drupalSettings', drupalSettings);

      // Watch for changes in the Angular route (this is fired twice per route change)...
      /*$rootScope.$on("$locationChangeStart", function(event, next, current) {

          // Extract the current menu path from the Angular route, and warn
          // about any uncrecognized routes.
          // @TODO this doesn't do anything, but it a good placeholder for
          // future needs/hooks while pages are changing. Revisit these two
          // function's implementations now that we have a better understanding
          // of Angular's routing.
          var path_current = drupalgap_angular_get_route_path(current);
          var path_next = drupalgap_angular_get_route_path(next);
          if (!path_next) {
            if (!drupalgap_path_get()) { return; } // Don't warn about the first page load.
            console.log('locationChangeStart - unsupported path: ' + path_next);
          }

      });*/
  }
]);

/**
 *
 */
function dg_ng_dependencies() {
  try {
    return [
        'ngRoute',
        'ngSanitize',
        'angular-drupal',
        'drupalgap',
        'dgEntity',
        'dgSystem',
        'dgText',
        'dgUser'
    ];
  }
  catch (error) { console.log('dg_ng_dependencies - ' + error); }
}


/**
 *
 */
function drupalgap_load_blocks(drupalgapSettings) {
  try {
    dpm('drupalgap_load_blocks');
    //console.log(drupalgapSettings);
    
    // For each module type specified in drupalgapSettings (core, contrib,
    // custom)...
    var modules = drupalgapSettings.modules;
    for (var type in modules) {
      if (!modules.hasOwnProperty(type)) { continue; }
      
      // For each module within the type...
      var _modules = modules[type];
      for (var name in _modules) {
        if (!_modules.hasOwnProperty(name)) { continue; }
        
        var function_name = name + '_block_info';
        
        // Skip any modules that don't implement hook_block_info().
        if (!dg_function_exists(function_name)) { continue; }
        
        // Call the implementation of hook_block_info() for the current module,
        // then iterate over each of its blocks, placing them onto
        // drupalgap.blocks one by one.
        var module = _modules[name];
        var blocks = window[function_name]();
        console.log(name);
        console.log(blocks);
        for (var delta in blocks) {
          if (!blocks.hasOwnProperty(delta)) { continue; }
          var block = blocks[delta];
          if (!block.delta) { block.delta = delta; }
          if (!block.module) { block.module = name; }
          
          // Merge in any block settings.
          //angular.merge(block);
          
          // Add the block to drupalgap.blocks.
          drupalgap.blocks.push(block);
        }

      }

    }
    dpm('BLOCKS!');
    console.log(drupalgap.blocks);
  }
  catch (error) { console.log('drupalgap_load_blocks - ' + error); }
}

/**
 *
 */
function drupalgap_block_load(delta) {
  try {
    for (var index in drupalgap.blocks) {
      if (!drupalgap.blocks.hasOwnProperty(index)) { continue; }
      var block = drupalgap.blocks[index];
      if (block.delta == delta) { return block; }
    }
    return null;
  }
  catch (error) { console.log('drupalgap_block_load - ' + error); }
}

/**
 *
 */
function drupalgap_render_block(delta, block) {
  try {
    angular.merge(block, drupalgap_block_load(delta));
    console.log('loaded block');
    console.log(block);
    var function_name = block.module + '_block_view';
    if (!dg_function_exists(function_name)) {
      console.log('WARNING: ' + function_name + '() does not exist, so we are skipping this block: ' + delta);
      return '';
    }
    return window[function_name](block.delta);
  }
  catch (error) { console.log('drupalgap_render_block - ' + error); }
}


/**
 * Implementation of arg(index = null, path = null).
 * @return {*}
 */
function arg() {
  try {
    var result = null;
    // If there were zero or one arguments provided.
    if (arguments.length == 0 || arguments.length == 1) {
      // Split the path into parts.
      var drupalgap_path = dg_path_get();
      var args = drupalgap_path.split('/');
      // If no arguments were provided just return the split array, otherwise
      // return whichever argument was requested.
      if (arguments.length == 0) { result = args; }
      else if (args[arguments[0]]) { result = args[arguments[0]]; }
    }
    else {
      // A path was provided, split it into parts, then return the split array
      // if they didn't request a specific index, otherwise return the value of
      // the specific index inside the split array.
      var path = arguments[1];
      var args = path.split('/');
      if (arguments[0] && args[arguments[0]]) { result = args[arguments[0]]; }
      else { result = args; }
    }
    return result;
  }
  catch (error) { console.log('arg - ' + error); }
}

/**
 * Converts a JSON object to an XML/HTML tag attribute string and returns the
 * string.
 * @param {Object} attributes
 * @return {String{
 */
function dg_attributes(attributes) {
  try {
    //dpm('dg_attributes');
    //console.log(attributes);
    var attribute_string = '';
    if (attributes) {
      for (var name in attributes) {
          if (!attributes.hasOwnProperty(name)) { continue; }
          var value = attributes[name];
          if (value != '') {
            // @todo - if someone passes in a value with double quotes, this
            // will break. e.g.
            // 'onclick':'_drupalgap_form_submit("' + form.id + "');'
            // will break, but
            // 'onclick':'_drupalgap_form_submit(\'' + form.id + '\');'
            // will work.
            attribute_string += name + '="' + value + '" ';
          }
          else {
            // The value was empty, just place the attribute name on the
            // element.
            attribute_string += name + ' ';
          }
      }
    }
    return attribute_string;
  }
  catch (error) { console.log('dg_attributes - ' + error); }
}

/**
 * Given a JS function name, this returns true if the function exists in the
 * scope, false otherwise.
 * @param {String} name
 * @return {Boolean}
 */
function dg_function_exists(name) {
  try {
    return (eval('typeof ' + name) == 'function');
  }
  catch (error) {
    alert('dg_function_exists - ' + error);
  }
}

/**
 * Given a string separated by underscores or hyphens, this will return the
 * camel case version of a string. For example, given "foo_bar" or "foo-bar",
 * this will return "fooBar".
 * @see http://stackoverflow.com/a/2970667/763010
 */
function dg_get_camel_case(str) {
  try {
    return str.replace(/[-_]([a-z])/g, function (g) { return g[1].toUpperCase(); });
  }
  catch (error) { console.log('dg_get_camel_case - ' + error); }
}

/**
 *
 */
function dg_kill_camel_case(str, separator) {
  try {
    return str.replace(/([A-Z])/g, separator + '$1');
  }
  catch (error) { console.log('dg_kill_camel_case - ' + error); }
}

/**
 *
 */
function dg_language_default() {
  try {
    var drupalSettings = dg_ng_get('drupalSettings');
    return typeof drupalSettings.language !== 'undefined' ?
      drupalSettings.language : 'und';
  }
  catch (error) { console.log('dg_language_default - ' + error); }
}

/**
 *
 */
function dg_ng_compile($compile, $scope, html) {
  try {
    var linkFn = $compile(html);
    return linkFn($scope);
  }
  catch (error) { console.log('dg_ng_compile - ' + error); }
}

/**
 *
 */
function dg_ng_get(key) {
  try {
    return drupalgap.ng[key];
  }
  catch (error) { console.log('dg_ng_get - ' + error); }
}

/**
 *
 */
function dg_ng_set(key, value) {
  try {
    drupalgap.ng[key] = value;
  }
  catch (error) { console.log('dg_ng_set - ' + error); }
}

/**
 * Get the current DrupalGap path.
 * @return {String}
 */
function dg_path_get() {
  try {
    var $location = dg_ng_get('location');
    return $location['$$path'].slice('1');
  }
  catch (error) { console.log('dg_path_get - ' + error); }
}

/**
 * Get the current Angular "route" object.
 * @see https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
 * @return {Object}
 */
function dg_route_get() {
  try {
    var $route = dg_ng_get('route');
    return $route.current;
  }
  catch (error) { console.log('dg_route_get - ' + error); }
}

/**
 * Returns translated text.
 * @param {String} str The string to translate
 * @return {String}
 */
function t(str) {
  return str;
}


/**
 * @deprecated
 */
function drupalgap_attributes(attributes) {
  try {
    console.log(
      'DEPRECATED - drupalgap_attributes(): use dg_attributes() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_attributes(attributes);
  }
  catch (error) { console.log('drupalgap_attributes - ' + error); }
}

/**
 * @deprecated
 */
function drupalgap_field_info_field(field_name) {
  try {
    console.log(
      'DEPRECATED - drupalgap_field_info_field(): use dg_field_info_field() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_field_info_field(field_name);
  }
  catch (error) { console.log('drupalgap_field_info_field - ' + error); }
}

/**
 * @deprecated
 */
function drupalgap_function_exists(name) {
  try {
    console.log(
      'DEPRECATED - drupalgap_function_exists(): use dg_function_exists() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_function_exists(name);
  }
  catch (error) { console.log('drupalgap_function_exists - ' + error); }
}

/**
 * Given a form_id, this will return a form with a directive attribute for it.
 * @param {String} form_id
 * @return {String}
 * @deprecated
 */
/*function drupalgap_get_form(form_id) {
  try {
    console.log(
      'DEPRECATED - drupalgap_get_form() - instead, use a directive with this name: ' +
      dg_get_camel_case(form_id)
    );
    return;
  }
  catch (error) { console.log('drupalgap_get_form - ' + error); }
}*/

/**
 * @deprecated
 */
function drupalgap_form_defaults(form_id) {
  try {
    console.log(
      'DEPRECATED - drupalgap_form_defaults(): use dg_form_defaults() instead in ' +
      arguments.callee.caller.name + '(), and pass $scope to it'
    );
    return dg_form_defaults(form_id);
  }
  catch (error) { console.log('drupalgap_form_defaults - ' + error); }
}

/**
 * @deprecated
 */
function drupalgap_file_get_contents(path, options) {
  try {
    console.log(
      'DEPRECATED - drupalgap_file_get_contents(): use dg_file_get_contents() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_file_get_contents(path, options);
  }
  catch (error) { console.log('drupalgap_file_get_contents - ' + error); }
}

/**
 * @deprecated
 */
function drupalgap_form_get_element_id(name, form_id) {
  console.log(
    'DEPRECATED - drupalgap_form_get_element_id(): use dg_form_get_element_id() instead in ' +
    arguments.callee.caller.name + '()'
  );
  return dg_form_get_element_id(name, form_id);
}

/**
 * @deprecated
 */
function drupalgap_form_render(form) {
  console.log(
    'DEPRECATED - drupalgap_form_render(): use dg_form_render() instead in ' +
    arguments.callee.caller.name + '()'
  );
  return dg_form_render(form);
}

/**
 * @deprecated
 */
function drupalgap_get_camel_case(str) {
  try {
    console.log('DEPRECATED - drupalgap_get_camel_case(), use dg_get_camel_case() instead');
    return dg_get_camel_case(str);
  }
  catch (error) { console.log('drupalgap_get_camel_case - ' + error); }
}

/**
 * @deprecated
 */
function drupalgap_kill_camel_case(str, separator) {
  try {
    console.log('DEPRECATED - drupalgap_kill_camel_case(), use dg_kill_camel_case() instead');
    return dg_kill_camel_case(str, separator);
  }
  catch (error) { console.log('drupalgap_kill_camel_case - ' + error); }
}

/**
 *
 */
function drupalgap_ng_get(key) {
  try {
    console.log(
      'DEPRECATED - drupalgap_ng_get(): use dg_ng_get() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_ng_get(key);
  }
  catch (error) { console.log('drupalgap_ng_get - ' + error); }
}

/**
 *
 */
function drupalgap_ng_set(key, value) {
  try {
    console.log(
      'DEPRECATED - drupalgap_ng_set(): use dg_ng_set() instead in ' +
      arguments.callee.caller.name + '()'
    );
    dg_ng_set(key, value);
  }
  catch (error) { console.log('drupalgap_ng_set - ' + error); }
}

/**
 * Get the current DrupalGap path.
 * @return {String}
 */
function drupalgap_path_get() {
  try {
    console.log(
      'DEPRECATED - drupalgap_path_get(): use dg_path_get() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_path_get();
  }
  catch (error) { console.log('drupalgap_path_get - ' + error); }
}

/**
 * Set the current DrupalGap path.
 * @param {String} path
 * @deprecated
 */
function drupalgap_path_set(path) {
  console.log(
    'DEPRECATED - drupalgap_path_set(): is now handled by Angular, the caller is being ignored:' +
    arguments.callee.caller.name + '()'
  );
  return;
}

/**
 * @deprecated
 */
function empty(value) {
  console.log(
    'DEPRECATED - empty(): use dg_empty() instead in ' +
    arguments.callee.caller.name + '()'
  );
  return dg_empty(value);
}

/**
 * Set the current DrupalGap path.
 * @param {String} path
 * @deprecated
 */
function is_int(n) {
  console.log(
    'DEPRECATED - is_int(): use dg_is_int() instead in ' +
    arguments.callee.caller.name + '()'
  );
  return dg_is_int(n);
}

/**
 *
 */
function language_default() {
  try {
    console.log(
      'DEPRECATED - language_default(): use dg_language_default() instead in ' +
      arguments.callee.caller.name + '()'
    );
    return dg_language_default();
  }
  catch (error) { console.log('language_default - ' + error); }
}

/**
 * Reads entire file into a string and returns the string. Returns false if
 * it fails.
 * @param {String} path
 * @param {Object} options
 * @return {String}
 */
function dg_file_get_contents(path, options) {
  try {
    var file = false;
    var default_options = {
      type: 'GET',
      url: path,
      dataType: 'html',
      data: null,
      async: false,
      success: function(data) { file = data; },
      error: function(xhr, textStatus, errorThrown) {
        console.log(
          'dg_file_get_contents - failed to load file (' + path + ')'
        );
      }
    };
    $.extend(default_options, options);
    jQuery.ajax(default_options);
    return file;
  }
  catch (error) { console.log('dg_file_get_contents - ' + error); }
}

/**
 *
 */
function drupalgap_form_render_elements(form) {
  try {
    var html = '';
    if (!form.elements) { return html; }
    for (var name in form.elements) {
      if (!form.elements.hasOwnProperty(name)) { continue; }
      html += drupalgap_form_render_element(form, form.elements[name]);
    }
    return html;
  }
  catch (error) { console.log('drupalgap_form_render - ' + error); }
}

/**
 *
 */
function drupalgap_form_render_element(form, element) {
  try {
    // Preprocess element if necessary...
    // @TODO great spot for a hook.
    
    // Submit button.
    if (element.type == 'submit') {
      element.attributes['class'] += ' dg_form_submit_button ';
      if (typeof element.attributes['data-ng-click'] === 'undefined') {
        element.attributes['data-ng-click'] = 'drupalgap_form_submit(\'' + form.id + '\', form_state);';
      }
      if (typeof element.attributes['value'] === 'undefined' && element.value) {
        element.attributes['value'] = element.value;
      }
    }
    else {
      // All other elements should have an ng-model attached, which allows the
      // form state values to be properly assembled and ready for validation and
      // submission handlers.
      if (typeof element.attributes['ng-model'] === 'undefined') {
        element.attributes['ng-model'] = "form_state['values']['" + element.name + "']";
      }
    }
    return theme('form_element', { element: element });
  }
  catch (error) { console.log('drupalgap_form_render_element - ' + error); }
}

/**
 *
 */
function theme_form_element(variables) {
  try {
    return '<div>' +
      theme('form_element_label', { element: variables.element } ) +
      theme(variables.element.type, {
          attributes: variables.element.attributes,
          element: variables.element
      }) +
    '</div>';
  }
  catch (error) { console.log('theme_form_element - ' + error); }
}

/**
 *
 */
function theme_form_element_label(variables) {
  try {
    return typeof variables.element.title !== 'undefined' ?
      variables.element.title : '';
  }
  catch (error) { console.log('theme_form_element_label - ' + error); }
}

/**
 *
 */
function dg_form_element_set_empty_options_and_attributes(form, language) {
  try {
    // Set empty options and attributes properties on each form element if the
    // element does not yet have any. This allows others to more easily modify
    // options and attributes on an element without having to worry about
    // testing for nulls and creating empty properties first.
    for (var name in form.elements) {
      if (!form.elements.hasOwnProperty(name)) { continue; }
      var element = form.elements[name];
      // If this element is a field, load its field_info_field and
      // field_info_instance onto the element.
      var element_is_field = false;
      var field_info_field = dg_field_info_field(name);
      if (field_info_field) {
        element_is_field = true;
        form.elements[name].field_info_field = field_info_field;
        form.elements[name].field_info_instance =
          drupalgap_field_info_instance(
            form.entity_type,
            name,
            form.bundle
          );
      }
      form.elements[name].is_field = element_is_field;
      // Set the name property on the element if it isn't already set.
      if (!form.elements[name].name) { form.elements[name].name = name; }
      // If the element is a field, we'll append a language code and delta
      // value to the element id, along with the field items appended
      // onto the element using the language code and delta values.
      var id = null;
      if (element_is_field) {
        // What's the number of allowed values (cardinality) on this field?
        // A cardinality of -1 means the field has unlimited values.
        var cardinality = parseInt(element.field_info_field.cardinality);
        if (cardinality == -1) {
          cardinality = 1; // we'll just add one element for now, until we
                           // figure out how to handle the 'add another
                           // item' feature.
        }
        // Initialize the item collections language code if it hasn't been.
        if (!form.elements[name][language]) {
          form.elements[name][language] = {};
        }
        // Prepare the item(s) for this element.
        for (var delta = 0; delta < cardinality; delta++) {
          // Prepare some item defaults.
          var item = drupalgap_form_element_item_create(
            name,
            form,
            language,
            delta
          );
          // If the delta for this item hasn't been created on the element,
          // create it using the default item values. Otherwise, merge the
          // default values into the pre existing item on the element.
          if (!form.elements[name][language][delta]) {
            form.elements[name][language][delta] = item;
          }
          else {
            $.extend(true, form.elements[name][language][delta], item);
          }
        }
      }
      else {
        // This element is not a field, setup default options if none
        // have been provided. Then set the element id.
        if (!element.attributes) {
          form.elements[name].attributes = { };
        }
        id = dg_form_get_element_id(name, form.id);
        form.elements[name].id = id;
        form.elements[name].attributes.id = id;
      }
    }
  }
  catch (error) { console.log('dg_form_element_set_empty_options_and_attributes - ' + error); }
}

/**
 * Given a form element name and the form_id, this generates an html id
 * attribute value to be used in the DOM. An optional third argument is a
 * string language code to use. An optional fourth argument is an integer delta
 * value to use on field elements.
 * @param {String} name
 * @param {String} form_id
 * @return {String}
 */
function dg_form_get_element_id(name, form_id) {
  try {
    if (name == null || name == '') { return ''; }
    var id =
      'edit-' +
      form_id.toLowerCase().replace(/_/g, '-') + '-' +
      name.toLowerCase().replace(/_/g, '-');
    // Any language code to append to the id?
    if (arguments[2]) { id += '-' + arguments[2]; }
    // Any delta value to append to the id?
    if (typeof arguments[3] !== 'undefined') {
      id += '-' + arguments[3] + '-value';
    }
    return id;
  }
  catch (error) { console.log('dg_form_get_element_id - ' + error); }
}


/**
 *
 */
function drupalgap_get_form(form_id) {
  try {
    return theme('form', { form_id: form_id });
    // Set up form defaults.
    /*var form = {
      attributes: {
        id: form_id,
        'class': []
      }
    };*/
    // Set up a directive attribute to handle this form, then theme and return.
    //form.attributes[] = '';
  }
  catch (error) { console.log('drupalgap_get_form - ' + error); }
}

/**
 *
 */
function theme_form(variables) {
  try {
    // Theme the form as an Angular directive based on the form's id.
    var directive = variables.form_id.replace(/_/g, '-')
    return '<' + directive + '></' + directive + '>';
    //return '<form ' + dg_attributes(variables.form.attributes) + '></form>';
  }
  catch (error) { console.log('theme_form - ' + error); }
}

/**
 *
 */
function dg_form_render(form) {
  try {
    // Render the prefix and suffix and wrap them in their own div.
    var prefix = form.prefix;
    if (!dg_empty(prefix)) {
      prefix = '<div class="form_prefix">' + prefix + '</div>';
    }
    var suffix = form.suffix;
    if (!dg_empty(suffix)) {
      suffix = '<div class="form_suffix">' + suffix + '</div>';
    }
    return '<form ' + dg_attributes(form.attributes) + '>' +
      prefix +
      drupalgap_form_render_elements(form) +
      suffix +
    '</form>';
  }
  catch (error) { console.log('drupalgap_form_render - ' + error); }
}

/**
 * Given a form id, this will assemble and return the default form JSON object.
 * @param {String} form_id
 * @return {Object}
 */
function dg_form_defaults(form_id, $scope) {
  try {
    var form = {};
    
    // Set the form id, elements, buttons, options and attributes.
    form.id = form_id;
    form.elements = {};
    form.buttons = {};
    form.attributes = {
      id: form_id,
      'class': ''
    };
    
    // Create a prefix and suffix.
    form.prefix = '';
    form.suffix = '';
    
    // Create empty arrays for the form's validation and submission handlers.
    form.validate = [];
    form.submit = [];
    
    if (!$scope.form_state) { $scope.form_state = { values: { } } };
    
    // FORM SUBMIT HANDLER
    // @TODO - who is passing the form_id and form_state to this function? we
    // probably don't need to do that anymore, because that data should already
    // be available in the scope... I think.
    $scope.drupalgap_form_submit = function() {
      
      dpm('drupalgap_form_submit');
      console.log(arguments);
      
      // Extract the form and its id along with the form state, from the scope.
      // Remember the form state values are automatically assembled by Angular,
      // unlike DrupalGap 1.x where we assembled them manually.
      var form = $scope.form;
      var form_state = $scope.form_state;
      var form_id = form.id;
      //console.log(form);
      //console.log(form_state);
      //console.log(form_id);
  
      // Clear out previous form errors.
      drupalgap.form_errors = {};
  
      // Build the form validation wrapper function.
      var form_validation = function() {
        try {
  
          // Call the form's validate function(s), if any.
          /*for (var index in form.validate) {
              if (!form.validate.hasOwnProperty(index)) { continue; }
              var fn = form.validate[index];
              fn.apply(null, Array.prototype.slice.call([form, form_state]));
          }*/
  
          // Call drupalgap form's api validate.
          // @TODO this should be replaced with Angular's way of doing things.
          //_drupalgap_form_validate(form, form_state);
  
          // If there were validation errors, show the form errors and stop the
          // form submission. Otherwise submit the form.
          /*if (!jQuery.isEmptyObject(drupalgap.form_errors)) {
            var html = '';
            for (var name in drupalgap.form_errors) {
                if (!drupalgap.form_errors.hasOwnProperty(name)) { continue; }
                var message = drupalgap.form_errors[name];
                html += message + '\n\n';
            }
            drupalgap_alert(html);
          }*/
          //else {
            form_submission();
          //}

        }
        catch (error) {
          console.log('drupalgap_form_submit - form_validation - ' + error);
        }
      };
  
      // Build the form submission wrapper function.
      var form_submission = function() {
        try {
          // Call the form's submit function(s), if any.
          for (var index in form.submit) {
              if (!form.submit.hasOwnProperty(index)) { continue; }
              var fn = form.submit[index];
              fn.apply(null, Array.prototype.slice.call([form, form_state]));
          }
        }
        catch (error) {
          console.log('drupalgap_form_submit - form_submission - ' + error);
        }
      };
  
      // Get ready to validate and submit the form, but first...
  
      // If this is an entity form, and there is an image field on the form, we
      // need to asynchronously process the image field, then continue onward
      // with normal form validation and submission.
      /*if (form.entity_type &&
        image_fields_present_on_entity_type(form.entity_type, form.bundle)
      ) {
        _image_field_form_process(form, form_state, {
            success: form_validation
        });
      }
      else {*/
        // There were no image fields on the form, proceed normally with form
        // validation, which will in turn process the submission if there are no
        // validation errors.
        form_validation();
      /*}*/
      
      
      
    }

    // Finally, return the form.
    return form;
  }
  catch (error) { console.log('dg_form_defaults - ' + error); }
}

/**
 * @param {Object} $compile
 * @param {Object} $scope
 * @param {Object|Null} hookFieldWidgetForm
 */
function dg_ng_compile_form($compile, $scope) {
  try {
    //dpm('dg_ng_compile_form');
    //console.log(arguments);
    
    var drupalSettings = dg_ng_get('drupalSettings');
    
    // Pull out any optional arguments.
    var hookFieldWidgetForm = arguments[2] ? arguments[2] : null;
    
    // @TODO sometime between dg_ng_compile_form() and dgFormElement.link, we
    // are losing the form state.

    dg_form_element_set_empty_options_and_attributes($scope.form, drupalSettings.language);

    // Give modules an opportunity to alter the form.
    //module_invoke_all('form_alter', $scope.form, null, $scope.form.id);

    // Place the assembled form into local storage so _drupalgap_form_submit
    // will have access to the assembled form.
    //drupalgap_form_local_storage_save($scope.form);

    // For each form element...
    for (var name in $scope.form.elements) {
      if (!$scope.form.elements.hasOwnProperty(name)) { continue; }
      var element = $scope.form.elements[name];
      //dpm(name);
      //console.log(element);
      
      if (!element.is_field) {
        if (typeof element.attributes.name === 'undefined') {
          $scope.form.elements[name].attributes.name = element.name;
        }
        if (typeof element.default_value !== 'undefined') {
          // @TODO we shouldn't be dropping all these directly in scope, let's
          // put them in their own object instead. Although this may be possible
          // to deprecate now because of our ng-model usage, needs testing...
          $scope[element.name] = element.default_value;
        }
      }
      else {
        hookFieldWidgetForm.setField(element.name, element.field_info_field);
      }

      // Place any hidden input's value (if any) into the scope so it can be
      // properly bound to the form state values.
      if (element.type == 'hidden') {
        if (typeof element.default_value !== 'undefined') {
          // @TODO we should probably stick these values in their own object, so
          // they aren't polluting the scope with nonsense.
          $scope[element.name] = element.default_value;
        }
        var ng_model = typeof element.attributes['ng-model'] !== 'undefined' ?
          element.attributes['ng-model'] : dg_form_element_ng_model(element);
        $scope.form.elements[name].attributes['ng-init'] = ng_model + " = " + element.attributes.name;
      }

    }

    // Finally compile the rendered form.
    return dg_ng_compile($compile, $scope, dg_form_render($scope.form));
  }
  catch (error) { console.log('dg_ng_compile_form - ' + error); }
}




/**
 * Themes a password input.
 * @param {Object} variables
 * @return {String}
 */
function theme_password(variables) {
  try {
    variables.attributes.type = 'password';
    var output = '<input ' + dg_attributes(variables.attributes) + ' />';
    return output;
  }
  catch (error) { console.log('theme_password - ' + error); }
}

/**
 * Themes a password input.
 * @param {Object} variables
 * @return {String}
 */
function theme_submit(variables) {
  try {
    dpm('theme_submit');
    console.log(variables);
    variables.attributes.type = 'submit';
    var output = '<input ' + dg_attributes(variables.attributes) + ' />';
    return output;
  }
  catch (error) { console.log('theme_submit - ' + error); }
}

/**
 * Themes a text input.
 * @param {Object} variables
 * @return {String}
 */
function theme_textfield(variables) {
  try {
    variables.attributes.type = 'text';
    var output = '<input ' + dg_attributes(variables.attributes) + ' />';
    return output;
  }
  catch (error) { console.log('theme_textfield - ' + error); }
}


/**
 * Given an argument, this will return true if it is an int, false otherwise.
 * @param {Number} n
 * @return {Boolean}
 */
function dg_is_int(n) {
  // Credit: http://stackoverflow.com/a/3886106/763010
  if (typeof n === 'string') { n = parseInt(n); }
  return typeof n === 'number' && n % 1 == 0;
}

/**
 * Returns true if given value is empty. A generic way to test for emptiness.
 * @param {*} value
 * @return {Boolean}
 */
function dg_empty(value) {
  try {
    if (value === null) { return true; }
    if (typeof value === 'object') { return Object.keys(value).length === 0; }
    return (typeof value === 'undefined' || value == '');
  }
  catch (error) { console.log('dg_empty - ' + error); }
}


dgApp.config(function(drupalgapSettings) {

  dpm('config() - initializing...');
  console.log(arguments);

  // @WARNING only certain providers like constants are available here, no scope
  // or value available here...

  //console.log($provide.value('drupalgapSettings').$get());
  
  drupalgap_onload(drupalgapSettings);

  // @TODO this should be included via index.html as a script.
  // @WARNING to get this file navigate to ?q=drupalgap/connect in your
  // browser then save it at the path mentioned below.
  // @WARNING Synchronous XMLHttpRequest on the main thread is deprecated because of its
  // detrimental effects to the end user's experience.
  drupalgap_service_resource_extract_results({
      service: 'system',
      resource: 'connect',
      data: JSON.parse(dg_file_get_contents('app/js/drupalgap_connect.json'))
  });

});

/**
 *
 */
function drupalgap_onload(drupalgapSettings) {
  try {
    drupalgap_load_blocks(drupalgapSettings);
  }
  catch (error) { console.log('drupalgap_onload - ' + error); }
}

dgApp.config(function() {
    
    return;
  
  dpm('config() - building entity views...');
  
  // For each entity type...
  for (var entity_type in drupalgap.field_info_instances) {
    if (!drupalgap.field_info_instances.hasOwnProperty(entity_type)) { continue; }
    var entity_bundles = drupalgap.field_info_instances[entity_type];
    
    // Initialize a place for this entity type's view(s) templates.
    drupalgap.views.templates[entity_type] = {};
    
    // For each bundle on the entity type...
    for (var bundle in entity_bundles) {
      if (!entity_bundles.hasOwnProperty(bundle)) { continue; }
      
      // Grab the field instances for this entity type and bundle.
      var instances = entity_bundles[bundle];
      //console.log(instances);

      // If there are no fields on the bundle, skip it.
      if (typeof instances !== 'object') {
        dpm('skipping ' + entity_type + '/' + bundle + ', no fields on it...');
        continue;
      }
      
      // Make a view template for each entity mode.
      drupalgap.views.templates[entity_type][bundle] = { };
      var modes = ['view', 'edit'];
      for (var i = 0; i < modes.length; i++) {
        var mode = modes[i];
        var template = '';
        switch (mode) {
        case 'view':
          template = '{{' + entity_type + '.' + entity_primary_key_title(entity_type) + '}}';
          break;
        case 'edit':
          template = '{{' + entity_type + '.' + entity_primary_key_title(entity_type) + '}}';
          break;
        }
        drupalgap.views.templates[entity_type][bundle][mode] = {
          template: '<div ng-model="' + entity_type + '">' + template + '</div>'
        };
      }

      var field_weights = {};
      var field_displays = {};
      
      // For each field on the instance...
      for (var field_name in instances) {
        if (!instances.hasOwnProperty(field_name)) { continue; }
        var field = instances[field_name];
        //console.log(field);
        
        // Determine which display mode to use. The default mode will be used
        // if the drupalgap display mode is not present.
        var display = drupalgap_field_display(field);
        
        // Skip hidden fields.
        if (display.type == 'hidden') { continue; }
        
        // Save the field display and weight.
        field_displays[field_name] = display;
        field_weights[field_name] = display.weight;
        
      }
      
      //dpm('field_weights');
      //console.log(field_weights);
      //dpm('field_displays');
      //console.log(field_displays);
      
      // Extract the field weights and sort them.
      var extracted_weights = [];
      for (var field_name in field_weights) {
          if (!field_weights.hasOwnProperty(field_name)) { continue; }
          var weight = field_weights[field_name];
          extracted_weights.push(weight);
      }
      extracted_weights.sort(function(a, b) { return a - b; });

      // For each sorted weight, locate the field with the corresponding weight,
      // then render it's field content.
      var completed_fields = [];
      for (var weight_index in extracted_weights) {
          if (!extracted_weights.hasOwnProperty(weight_index)) { continue; }
          var target_weight = extracted_weights[weight_index];
          for (var field_name in field_weights) {
              if (!field_weights.hasOwnProperty(field_name)) { continue; }
              var weight = field_weights[field_name];
              if (target_weight == weight) {
                if (completed_fields.indexOf(field_name) == -1) {
                  completed_fields.push(field_name);
                  
                  var modes = ['view', 'edit'];
                  for (var i = 0; i < modes.length; i++) {
                    var mode = modes[i];
                    var hook_name = mode == 'view' ?
                      'hook_field_formatter_view' : 'hook_field_widget_form'
                    var attrs = drupalgap_attributes({
                      'ng-controller': hook_name,
                      field_name: field_name
                    });
                    drupalgap.views.templates[entity_type][bundle][mode].template +=
                      '<div ' + attrs + '>{{' + field_name + '}}</div>';
                  }
                  
                  /*entity.content += drupalgap_entity_render_field(
                    entity_type,
                    entity,
                    field_name,
                    field_info[field_name],
                    field_displays[field_name]
                  );*/
                  break;
                }
              }
          }
      }
      
      //console.log('views templates');
      //console.log(drupalgap.views.templates);

    }

  }
  
});

// CONFIGURE HOOK_MENU() ITEMS AS ANGULAR ROUTES
dgApp.config(['$routeProvider',
  function($routeProvider) {
    
    return;

    dpm('config() - building route provider...');
    //console.log(arguments);

    // Attach hook_menu() paths to Angular's routeProvider.
    for (var path in drupalgap.menu_links) {
      if (!drupalgap.menu_links.hasOwnProperty(path)) { continue; }

      // Extract the menu link.
      var menu_link = drupalgap.menu_links[path];

      // Skip 'MENU_DEFAULT_LOCAL_TASK' items.
      if (menu_link.type == 'MENU_DEFAULT_LOCAL_TASK') {
        console.log('WARNING: dgApp - deprecated | MENU_DEFAULT_LOCAL_TASK on path: ' + path);
        continue;
      }

      // Use the active theme's page template as the templateUrl, if one wasn't
      // provided.
      // @TODO hard coded theme? bad bad bad
      if (!menu_link.templateUrl) {
        menu_link.templateUrl = 'themes/spi/page.tpl.html';
      }

      // Determine the menu link's controller, by first falling back to the
      // page_callback property which we'll warn the developer about, otherwise
      // we'll use menu_link object for the routeProvider.
      if (!menu_link.controller && menu_link.page_callback) {
        menu_link.controller = 'drupalgap_goto_controller';
      }
      else if (!menu_link.controller) {
        console.log('WARNING: dgApp - routeProvider | no controller provided for path: ' + path);
      }

      // Add the menu link to Angular's routeProvider.
      // @TODO apparently attaching all the controllers at once, instead of on
      // demand is expensive, seek alternative routes in utilizing controllers.
      //console.log(menu_link);
      // @TODO by attaching the complete hook_menu item link here, DG stuff ends
      // up in the same object as an Angular route, and we may or may not be
      // colliding with how Angular does things.
      $routeProvider.when('/' + path, menu_link);

    }

    // Set the otherwise ruling to load the front page.
    $routeProvider.otherwise({
        redirectTo: '/' + drupalgap.settings.front
    });

  }]);


/**
 * Execute the page callback associated with the current path and return its
 * content.
 * @param {Object} $compile
 * @param {Object} $injector
 * @return {Object}
 */
function menu_execute_active_handler($compile, $injector) {
  try {
    //dpm('menu_execute_active_handler');
    
    var path = dg_path_get();
    //dpm('path: ' + path);
    
    var route = dg_route_get();
    //dpm('route');
    //console.log(route);
    
    // Determine the page_callback function.
    var page_callback = typeof route['$$route'].page_callback !== 'undefined' ?
      route['$$route'].page_callback : null;
    if (!page_callback || !dg_function_exists(page_callback)) { return '404'; }
    
    // Determine the page_arguments, if any.
    var page_arguments = typeof route['$$route'].page_arguments !== 'undefined' ?
      route['$$route'].page_arguments : null;
    
    // Call the page_callback, with or without arguments. For each page
    // argument, if the argument is an integer, grab the corresponding arg(#),
    // otherwise just push the arg onto the page arguments. Then try to prepare
    // any entity that may be present in the url so the entity is sent via the
    // page arguments to the page callback, instead of just sending the integer.
    if (!page_arguments) { return window[page_callback](); }
    var _page_args = [];
    var args = arg(null, path);
    for (var index in page_arguments) {
      if (!page_arguments.hasOwnProperty(index)) { continue; }
      var object = page_arguments[index];
      if (dg_is_int(object) && args[object]) { _page_args.push(args[object]); }
      else { _page_args.push(object); }
    }
    return window[page_callback].apply(null, Array.prototype.slice.call(_page_args));
  }
  catch (error) {
    console.log('menu_execute_active_handler - ' + error);
  }
}

// Used to render the "dg-page" directive attribute from the theme's
// page.tpl.html file.
dgApp.directive("dgPage", function($compile, drupalgapSettings) {
    dpm('dgPage');
    return {
      link: function(scope, element, attrs) {
        
        dpm('dgPage - link...');
        var theme = drupalgapSettings.theme;
        console.log(theme);

        var template = '';
        for (var name in theme.regions) {
          if (!theme.regions.hasOwnProperty(name)) { continue; }
          var region = theme.regions[name];
          template += drupalgap_render_region(region);
        }

        // Compile the template for Angular and append it to the directive's
        // html element.
        var linkFn = $compile(template);
        var content = linkFn(scope);
        element.append(content);

      }
    };
});

dgApp.controller('dg_page_controller', [
    '$scope', '$sce', '$route', '$location', '$routeParams',
    function($scope, $sce, $route, $location, $routeParams) {
      try {

        dpm('dg_page_controller');
        //console.log(arguments);
  
        // Place the route into the global dg ng, we don't do this in run()
        // because the route isn't fully initalized until this controller is
        // invoked.
        dg_ng_set('route', $route);
  
      }
      catch (error) { console.log('dg_page_controller - ' + error); }
  }
]);


/**
 *
 */
function drupalgap_render_region(region) {
  try {
    return theme('region', {
        region: region,
        blocks: drupalgap_render_region_blocks(region)
    });
  }
  catch (error) { console.log('drupalgap_render_region - ' + error); }
}

/**
 *
 */
function drupalgap_render_region_blocks(region) {
  try {
    if (!region.blocks) { return ''; }
    var html = '';
    for (var delta in region.blocks) {
      if (!region.blocks.hasOwnProperty(delta)) { continue; }
      var block = region.blocks[delta];
      console.log(block);
      html += drupalgap_render_block(delta, block);
    }
    return html;
  }
  catch (error) { console.log('drupalgap_render_region_blocks - ' + error); }
}

/**
 *
 */
function theme_region(variables) {
  try {
    var region = variables.region;
    var format = typeof region.format === 'undefined' ?
      'div' : region.format;
    return '<' + format + ' ' + dg_attributes(region.attributes) + '>' +
      variables.blocks +
    '</' + format + '>';
  }
  catch (error) { console.log('theme_region - ' + error); }
}


/**
 * Given a html string, a render object or render array, this return the html
 * representing the content's output.
 * @param {String|Object|Array} output The html string or render array to render.
 * @return {String}
 */
function drupalgap_render(content) {
  try {
    //dpm('drupalgap_render');
    //console.log(content);
    var type = $.type(content);
    if (type === 'string') { return content; }
    var html = '';
    if (type === 'object') {
      if (content.markup) { return content.markup; }
      if (content.theme) { return theme(content.theme, content); }
      for (var index in content) {
        if (!content.hasOwnProperty(index)) { continue; }
        var piece = content[index];
        var _type = $.type(piece);
        if (_type === 'object') { drupalgap_render(piece); }
        else if (_type === 'array') {
          for (var i = 0; i < piece.length; i++) {
            html += drupalgap_render(piece[i]);
          }
        }
        
      }
    }
    return html;
    
    
    
    
    
    // Since the output has already been assembled, render the content
    // based on the output type. The output type will either be an html string
    // or a drupalgap render object.
    var output_type = $.type(output);
    var content = '';

    // If the output came back as a string, we can render it as is. If the
    // output came back as on object, render each element in it through the
    // theme system.
    if (output_type === 'string') {
      // The page came back as an html string.
      content = output;
    }
    else if (output_type === 'object') {
      // The page came back as a render object. Let's define the names of
      // variables that are reserved for theme processing.
      var render_variables = ['theme', 'view_mode', 'language'];

      // Is there a theme value specified in the output and the registry?
      if (output.theme && drupalgap.theme_registry[output.theme]) {

        // Extract the theme object template and determine the template file
        // name and path.
        var template = drupalgap.theme_registry[output.theme];
        var template_file_name = output.theme.replace(/_/g, '-') + '.tpl.html';
        var template_file_path = template.path + '/' + template_file_name;

        // Make sure the template file exists.
        if (drupalgap_file_exists(template_file_path)) {

          // Loads the template file's content into a string.
          var template_file_html = drupalgap_file_get_contents(
            template_file_path
          );
          if (template_file_html) {

            // What variable placeholders are present in the template file?
            var placeholders = drupalgap_get_placeholders_from_html(
              template_file_html
            );
            if (placeholders) {

              // Replace each placeholder with html.
              // @todo - each placeholder should have its own container div and
              // unique id.
              for (var index in placeholders) {
                  if (!placeholders.hasOwnProperty(index)) { continue; }
                  var placeholder = placeholders[index];
                  var html = '';
                  if (output[placeholder]) {
                    // Grab the element variable from the output.
                    var element = output[placeholder];
                    // If it is markup, render it as is, if it is themeable,
                    // then theme it.
                    if (output[placeholder].markup) {
                      html = output[placeholder].markup;
                    }
                    else if (output[placeholder].theme) {
                      html = theme(output[placeholder].theme, element);
                    }
                    // Now remove the variable from the output.
                    delete output[placeholder];
                  }
                  // Now replace the placeholder with the html, even if it was
                  // empty.
                  template_file_html = template_file_html.replace(
                    '{:' + placeholder + ':}',
                    html
                  );
              }
            }
            else {
              // There were no place holders found, do nothing, ok.
            }

            // Finally add the rendered template file to the content.
            content += template_file_html;
          }
          else {
            console.log(
              'drupalgap_render - failed to get file contents (' +
                template_file_path +
              ')'
            );
          }
        }
        else {
          console.log(
            'drupalgap_render - template file does not exist (' +
              template_file_path +
              ')'
            );
        }
      }

      // Iterate over any remaining variables and theme them.
      // @todo - each remaining variables should have its own container div and
      // unique id, similar to the placeholder div containers mentioned above.
      for (var element in output) {
          if (!output.hasOwnProperty(element)) { continue; }
          var variables = output[element];
          if ($.inArray(element, render_variables) == -1) {
            content += theme(variables.theme, variables);
          }
      }
    }

    // Now that we are done assembling the content into an html string, we can
    // return it.
    return content;
  }
  catch (error) { console.log('drupalgap_render - ' + error); }
}


/**
 * Implementation of theme().
 * @param {String} hook
 * @param {Object} variables
 * @return {String}
 */
function theme(hook, variables) {
  try {
    
    //dpm('theme(' + hook + ')');
    //console.log(variables);
    
    // If there is HTML markup present, just return it as is. Otherwise, run
    // the theme hook and send along the variables.
    if (!variables) { variables = {}; }
    if (variables.markup) { return variables.markup; }
    var content = '';
    
    // Determine the theme function and verify its existence.
    var theme_function = 'theme_' + hook;
    if (!dg_function_exists(theme_function)) {
      console.log(theme_function + '() missing!');
      return '';
    }
    
    // @TODO check for modules implementing the theme hook.
    
    // @TODO check for the current theme implementing the theme hook.
    
    // If no attributes are coming in, look to variables.options.attributes
    // as a secondary option, and to variables.element.attributes as a third
    // option, otherwise setup an empty JSON object for them.
    if (
      typeof variables.attributes === 'undefined' ||
      !variables.attributes
    ) {
      if (variables.options && variables.options.attributes) {
        variables.attributes = variables.options.attributes;
      }
      if (variables.element && variables.element.attributes) {
        variables.attributes = variables.element.attributes;
      }
      else {
        variables.attributes = {};
      }
    }
    // If there is no class name, set an empty one.
    if (!variables.attributes['class']) { variables.attributes['class'] = ''; }
    
    if (!window[theme_function]) { return ''; }
    var fn = window[theme_function];
    content = fn.call(null, variables);
    return content;
    
  }
  catch (error) { console.log('theme - ' + error); }
}


angular.module('dgEntity', ['drupalgap'])

// ~ hook_menu()
.config(['$routeProvider', function($routeProvider) {

      var entity_types = drupal_entity_types();
      
      // Add routes to view entities.
      for (var i = 0; i < entity_types.length; i++) {
        var entity_type = entity_types[i];
        var view_route = '/' + entity_type + '/:' + drupal_entity_primary_key(entity_type);
        $routeProvider.when(view_route, {
            templateUrl: 'themes/spi/page.tpl.html',
            controller: 'dg_page_controller',
            page_callback: 'dg_entity_page_view',
            page_arguments: [0, 1]
        });
      }
      
}])

.directive("entityView", function($compile, drupal) {
    return {
      controller: function($scope) {
        var entity_type = arg(0);
        var entity_id = arg(1);
        $scope.entity_type = entity_type;
        $scope.entity_id = entity_id;
        $scope.entity_load = {
          entity: drupal[entity_type + '_load'](entity_id)
        };
      },
      //replace: true,
      link: function(scope, element, attrs) {
        scope.entity_load.entity.then(function (entity) {
            //console.log(scope);
            //console.log(entity);
            
            var content = { };
            
            // Grab this entity's field info instances.
            var instances = drupalgap_field_info_instances(
              scope.entity_type,
              entity.type // @TODO support all entity types, not just nodes
            );
            //console.log(instances);
            
            // Render each field instance...
            for (var field_name in instances) {
              if (!instances.hasOwnProperty(field_name)) { continue; }
              var instance = instances[field_name];
              
              // Extract the drupalgap display mode and the module name in
              // charge of the field's formatter view hook.
              var display = instance.display.drupalgap;
              var module = display.module;
              var hook = module + '_field_formatter_view';
              
              // Invoke the hook_field_formmater_view(), if it exists.
              if (!dg_function_exists(hook)) { console.log(hook + '() missing!'); continue; }
              content[field_name] = window[hook](
                scope.entity_type,
                entity,
                dg_field_info_field(field_name),
                instance,
                entity.language,
                entity[field_name][entity.language],
                display
              );
              
            }
            
            element.replaceWith($compile(drupalgap_render(content))(scope));
        });
      }
    };
});

/**
 *
 */
function dg_entity_page_view(entity_type, entity_id) {
  try {
    return '<entity-view></entity';
    //var directive_name = entity_type.replace(/_/g, '-') + '-view';
    //return '<' + directive_name + '></' + directive_name + '>';
  }
  catch (error) { console.log('dg_entity_page_view - ' + error); }
}

/**
 * Given a field name, this will return its field info.
 * @param {String} field_name
 * @return {Object}
 */
function dg_field_info_field(field_name) {
  try {
    return drupalgap.field_info_fields[field_name];
  }
  catch (error) { console.log('dg_field_info_field - ' + error); }
}

/**
 * Given an entity type and/or a bundle name, this returns the field info
 * instances for the entity or the bundle.
 * @param {String} entity_type
 * @param {String} bundle_name
 * @return {Object}
 */
function drupalgap_field_info_instances(entity_type, bundle_name) {
  try {
    var field_info_instances;
    // If there is no bundle, pull the fields out of the wrapper.
    // @TODO there appears to be a special case with commerce_products, in that
    // they aren't wrapped like normal entities (see the else statement when a
    // bundle name isn't present). Or do we have a bug here, and we shouldn't
    // be expecting the wrapper in the first place?
    if (!bundle_name) {
      if (entity_type == 'commerce_product') {
        field_info_instances =
          drupalgap.field_info_instances[entity_type];
      }
      else if (
        typeof drupalgap.field_info_instances[entity_type] !== 'undefined' &&
        typeof drupalgap.field_info_instances[entity_type][entity_type] !== 'undefined'
      ) {
        field_info_instances =
          drupalgap.field_info_instances[entity_type][entity_type];
      }
    }
    else {
      if (typeof drupalgap.field_info_instances[entity_type] !== 'undefined') {
        field_info_instances =
          drupalgap.field_info_instances[entity_type][bundle_name];
      }
    }
    return field_info_instances;
  }
  catch (error) { console.log('drupalgap_field_info_instances - ' + error); }
}


/**
 * Implements hook_field_formatter_view().
 * @param {String} entity_type
 * @param {Object} entity
 * @param {Object} field
 * @param {Object} instance
 * @param {String} langcode
 * @param {Object} items
 * @param {*} display
 * @return {Object}
 */
function image_field_formatter_view(entity_type, entity, field, instance,
  langcode, items, display) {
  try {
    var element = [];
    for (var delta in items) {
      if (!items.hasOwnProperty(delta)) { continue; }
      var item = items[delta];
      var theme = dg_empty(display.settings.image_style) ?
        'image' : 'image_style';
      var image = {
        theme: theme,
        alt: item.alt,
        title: item.title
      };
      if (!dg_empty(theme)) {
        image.style_name = display.settings.image_style;
        image.path = item.uri;
      }
      else { image.path = drupalgap_image_path(item.uri); }
      element.push(image);
    }
    return element;
  }
  catch (error) { console.log('image_field_formatter_view - ' + error); }
}

/**
 * Implementation of theme_image().
 * @param {Object} variables
 * @return {String}
 */
function theme_image(variables) {
  try {
    // Turn the path, alt and title into attributes if they are present.
    if (variables.path) { variables.attributes['ng-src'] = variables.path; }
    if (variables.alt) { variables.attributes.alt = variables.alt; }
    if (variables.title) { variables.attributes.title = variables.title; }
    // Render the image.
    return '<img ' + dg_attributes(variables.attributes) + ' />';
  }
  catch (error) { console.log('theme_image - ' + error); }
}

/**
 * Implementation of theme_image_style().
 * @param {Object} variables
 * @return {String}
 */
function theme_image_style(variables) {
  try {
    variables.path = image_style_url(variables.style_name, variables.path);
    return theme_image(variables);
  }
  catch (error) { console.log('theme_image - ' + error); }
}

/**
 * Given an image style name and image uri, this will return the absolute URL
 * that can be used as a src value for an img element.
 * @param {String} style_name
 * @param {String} path
 * @return {String}
 */
function image_style_url(style_name, path) {
  try {
    var drupalSettings = dg_ng_get('drupalSettings');
    var src =
      drupalSettings.site_path + drupalSettings.base_path + path;
    if (src.indexOf('public://') != -1) {
      src = src.replace(
        'public://',
        drupalSettings.file_public_path +
          '/styles/' +
          style_name +
          '/public'
      );
    }
    else if (src.indexOf('private://') != -1) {
      src = src.replace(
        'private://',
        drupalSettings.file_private_path +
          '/styles/' +
          style_name +
          '/private'
      );
    }
    return src;
  }
  catch (error) { console.log('image_style_url - ' + error); }
}


/**
 * Given a json drupalgap options array from a service resource results call,
 * this extracts data based on the resource and populates necessary global vars.
 * @param {Object} options
 */
function drupalgap_service_resource_extract_results(options) {
  try {
    if (options.service == 'system' && options.resource == 'connect') {
      drupalgap.remote_addr = options.data.remote_addr;
      drupalgap.entity_info = options.data.entity_info;
      drupalgap.field_info_instances = options.data.field_info_instances;
      drupalgap.field_info_fields = options.data.field_info_fields;
      drupalgap.field_info_extra_fields = options.data.field_info_extra_fields;
      
      // @TODO uncomment once 7.x-2.x is more stable
      /*drupalgap.taxonomy_vocabularies =
        drupalgap_taxonomy_vocabularies_extract(
          result.taxonomy_vocabularies
        );*/

      // The system connect resource's success function places what is in
      // options.data.user to overwrite Drupal.user, so anything we want in
      // the Drupal.user object must be added to options.data.user isntead.
      // Extract and build the user's permissions.
      /*options.data.user.permissions = [];
      var permissions = options.data.user_permissions;
      for (var permission in permissions) {
        options.data.user.permissions.push(permissions[permission]);
      }*/
      // Pull out the content types, and set them by their type.
      var content_types_list = options.data.content_types_list;
      for (var index in content_types_list) {
          if (!content_types_list.hasOwnProperty(index)) { continue; }
          var object = content_types_list[index];
          drupalgap.content_types_list[object.type] = object;
      }
      // Pull out the content types user permissions.
      /*options.data.user.content_types_user_permissions =
        options.data.content_types_user_permissions;*/
      // Pull out the site settings.
      drupalgap.site_settings = options.data.site_settings;
      // Pull out the date formats and types.
      if (typeof options.data.date_formats !== 'undefined') {
        drupalgap.date_formats = options.data.date_formats;
      }
      if (typeof options.data.date_types !== 'undefined') {
        drupalgap.date_types = options.data.date_types;
      }
    }
  }
  catch (error) {
    console.log('drupalgap_service_resource_extract_results - ' + error);
  }
}


angular.module('dgSystem', ['drupalgap']).config(function() {
    dpm('dgSystem - config');
});

dgApp.directive("dgMain", function($compile, $injector) {
    dpm('dgMain');
    return {
      link: function(scope, element) {
        
        dpm('dgMain - link...');
        
        // Compile the template for Angular and append it to the directive's
        // html element.
        var linkFn = $compile(
          drupalgap_render(
            menu_execute_active_handler($compile, $injector)
          )
        );
        var content = linkFn(scope);
        element.append(content);

      }
    };
});

/**
 * Implements hook_block_info().
 * @return {Object}
 */
function system_block_info() {
    var blocks = {
      main: { },
      messages: { },
      logo: { },
      logout: { },
      title: { },
      powered_by: { },
      help: { }
    };
    // Make additional blocks for each system menu.
    /*var system_menus = menu_list_system_menus();
    for (var menu_name in system_menus) {
        if (!system_menus.hasOwnProperty(menu_name)) { continue; }
        var menu = system_menus[menu_name];
        var block_delta = menu.menu_name;
        blocks[block_delta] = {
          name: block_delta,
          delta: block_delta,
          module: 'menu'
        };
    }*/
    return blocks;
}

/**
 * Implements hook_block_view().
 * @param {String} delta
 * @return {String}
 */
function system_block_view(delta) {
  try {
    switch (delta) {
      case 'main':
        // This is the main content block, it is required to be in a theme's
        // region for the content of a page to show up (nodes, users, taxonomy,
        // comments, etc). Here we use it as an Angular directive, dgMain.
        return '<div dg-main></div>';
        break;
      default:
        return '';
        break;
    }
  }
  catch (error) { console.log('system_block_info - ' + error); }
}

angular.module('dgText', ['drupalgap'])
.directive("textFieldFormatterView", function($compile) {
    return {

      controller: function($scope, drupal) {

      },

      link: function(scope, element, attrs) {

        // Add the form to the element.
        //element.append(dg_ng_compile_form($compile, scope));
        element.append($compile(drupalgap_render('<p>TEXT FIELDING STILL!</p>'))(scope));
      }

    };
});

/**
 * Implements hook_field_formatter_view().
 * @param {String} entity_type
 * @param {Object} entity
 * @param {Object} field
 * @param {Object} instance
 * @param {String} langcode
 * @param {Object} items
 * @param {*} display
 * @return {Object}
 */
function text_field_formatter_view(entity_type, entity, field, instance,
  langcode, items, display) {
  try {
    var element = [];
    for (var delta in items) {
      if (!items.hasOwnProperty(delta)) { continue; }
      var item = items[delta];
      // Grab the field value, but use the safe_value if we have it.
      var value = item.value;
      if (typeof item.safe_value !== 'undefined') {
        value = item.safe_value;
      }
      element.push({ markup: value });
    }
    return element;
  }
  catch (error) { console.log('text_field_formatter_view - ' + error); }
}


angular.module('dgUser', ['drupalgap'])

// hook_menu()
.config(['$routeProvider', function($routeProvider) {
      //dpm('dgUser - routeProvider');
      $routeProvider.when('/user/login', {
          templateUrl: 'themes/spi/page.tpl.html',
          //template: drupalgap_get_form('user_login_form'),
          //template: '<form user-login-form></form>',
          controller: 'dg_page_controller',
          page_callback: 'drupalgap_get_form',
          page_arguments: ['user_login_form']
      });
}]);

// @TODO this should be attached to the module, not the app.
dgApp.directive("userLoginForm", function($compile) {
    return {

      controller: function($scope, drupal) {
        
        dpm('userLoginForm - controller');

        // Set up form defaults.
        var form = dg_form_defaults("user_login_form", $scope);

        // Build form elements.
        form.entity_type = 'user';
        form.bundle = null;
        form.elements.name = {
          type: 'textfield',
          title: t('Username'),
          title_placeholder: true,
          required: true
        };
        form.elements.pass = {
          type: 'password',
          title: t('Password'),
          title_placeholder: true,
          required: true,
          attributes: {
            //onkeypress: "drupalgap_form_onkeypress('" + form.id + "')"
          }
        };
        form.elements.submit = {
          type: 'submit',
          value: t('Login')
        };
        /*if (user_register_access()) {
          form.buttons['create_new_account'] = {
            title: t('Create new account'),
            attributes: {
              onclick: "drupalgap_goto('user/register')"
            }
          };
        }
        form.buttons['forgot_password'] = {
          title: t('Request new password'),
            attributes: {
              onclick: "drupalgap_goto('user/password')"
            }
        };*/

        // Form submit handler.
        form.submit.push(function(form, form_state) {
            drupal.user_login(
              form_state.values.name,
              form_state.values.pass
            ).then(function(data) {
                console.log(data);
                //drupalgap_goto(drupalgap.settings.front);
            });
        });

        // Place the form into the scope.
        $scope.form = form;

      },

      link: function(scope, element) {
        
        dpm('userLoginForm - link');

        // Add the form to the element.
        element.append(dg_ng_compile_form($compile, scope));

      }

    };
});


/**
 *
 */
function user_login_form(form) {
  try {
    console.log(arguments);
    form.elements.name = {
      type: 'textfield',
      title: t('Username'),
      title_placeholder: true,
      required: true
    };
    form.elements.pass = {
      type: 'password',
      title: t('Password'),
      title_placeholder: true,
      required: true,
      attributes: {
        onkeypress: "drupalgap_form_onkeypress('" + form.id + "')"
      }
    };
    form.elements.submit = {
      type: 'submit',
      value: t('Login')
    };
    return form;
  }
  catch (error) { console.log('user_login_form - ' + error); }
}

