/**
 * Backend wireframe specifics
 */

/**
 * Render button control
 * 
 * @param String caption
 * @param String type
 */
App.Wireframe.Utils['label'] = function(caption, for_field, required, after_text) {
  if(typeof(required) == 'undefined') {
    required = false;
  } // if
  
  if(typeof(after_text) == 'undefined') {
    after_text = ':';
  } // if
  
  var label_text = required ? caption.clean() + after_text + ' <span class="label_required">*</span>' : caption.clean() + after_text;
  var for_attribute = for_field ? 'for="' + for_field + '"' : '';
  
  return '<label ' + for_field + ' class="main_label">' + label_text + '</label>';
};

/**
 * Render button control
 * 
 * @param String caption
 * @param String type
 */
App.Wireframe.Utils['button'] = function(caption, type) {
  if(typeof(type) == 'undefined') {
    type = 'button';
  } // if
  
  if(type == 'submit') {
    return '<button type="submit" class="default"><span><span>' + caption.clean() + '</span></span></button>';
  } else {
    return '<button type="' + type + '"><span><span>' + caption.clean() + '</span></span></button>';
  } // if
};

/**
 * Backedn layout implementation
 */
App.Wireframe.BackendLayout = function() {
  
  /**
   * Indicator whether login layout is built
   * 
   * @var Boolean
   */
  var login_built = false;
  
  /**
   * Indicator whether backend layout is built
   * 
   * @var Boolean
   */
  var backend_built = false;
  
  // Public interface
  return {
    
    /**
     * Initialize layout with given settings
     * 
     * @param Object s
     */
    'init' : function(s) {
      var logged_user = typeof(s) == 'object' && s && typeof(s['logged_user']) == 'object';
      
      // If we have logged in user, build backend
      if(logged_user) {
        App.Wireframe.BackendLayout.buildBackend(s, s['content_url'] ? s['content_url'] : null);    
        
      // No logged in user? Start with login page
      } else {
        App.Wireframe.BackendLayout.buildLogin(s);
      } // if
      
      // monitor background layout changes
      App.Wireframe.Updates.subscribe('backend_layout', null, function(response) {
        if(typeof(response['refresh_backend_wireframe']) == 'object') {
          if(typeof(response['refresh_backend_wireframe']['main_menu'])) {
            App.Wireframe.MainMenu.batchSet(response['refresh_backend_wireframe']['main_menu'], App.Wireframe.MainMenu.getCurrent());
          } // if
          
          if(response['refresh_backend_wireframe']['status_bar']) {
            App.Wireframe.Statusbar.batchSet(response['refresh_backend_wireframe']['status_bar']);
          } // if
        } // if
      });

      // we have to monitor all ajax requests to check if session has expired
      jQuery.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
        var success = options.success;
        options.success = function(data, textStatus, jqXHR) {
          if (typeof(data) == 'string' && (data.indexOf('<!-- *%NOT LOGGED IN%* -->') != -1 || data.indexOf('--Printing login form--') != -1)) {
            if (App.Wireframe.BackendLayout.isLoginBuilt()) {
              return true;
            } // if

            $.ajax({
              'url'       : App.extendUrl(App.Config.get('logout_url'), {'async' : 1, 'force' : 1}),
              'type'      : 'POST',
              'data'      : {'submitted' : 'submitted'},
              'success'   : function (response) {
                App.Wireframe.BackendLayout.buildLogin(response);
                App.Wireframe.Flash.error(App.lang('Session expired. Please log in'));
              },
              'error'     : function (response) {
                window.location = App.Config.get('homepage_url');
              }
            });

            return false;
          } // if

          if (typeof(success) === "function") return success(data, textStatus, jqXHR);
        };
      });
    },
    
    /**
     * Build login screen based on given settings
     * 
     * @param s
     */
    'buildLogin' : function(s) {
      var settings = jQuery.extend({
        'request' : null, 
        'login_url' : '', 
        'forgot_password_url' : '', 
        'reset_password_url' : '', 
        'login_logo_url' : ''
      }, s);
      
      App.langs = s['clientside_lang'];
      
      if(backend_built) {
        App.Wireframe.BackendLayout.tearDownBackend();
      } // if
      
      var body = $('body').addClass('backend_login');
      
      // Login splash
      if(settings['login_logo_url']) {
        body.append('<div id="login_splash"><img src="' + settings['login_logo_url'].clean() + '" alt="Logo"></div>');
      } // if
      
      // basic dom
      body.append('<div id="content">' + 
        '<div id="auth_dialog_container"><div id="auth_dialog"><h3><span></span></h3></div></div>' +
      '</div>');

      // find wrappers
      var auth_dialog_content = body.find('#auth_dialog_container');
      var auth_dialog = auth_dialog_content.find('#auth_dialog');
      var auth_dialog_title = auth_dialog_content.find('#auth_dialog h3 span');
      
      // login_footer
      var login_footer = $('<div id="login_footer"></div>').insertAfter(auth_dialog.parent());
      var forgot_password_link = $('<a href="' + settings['forgot_password_url'].clean() + '" class="forgot_password_link">' + App.lang('Forgot password?') + '</a>').appendTo(login_footer).hide();
      var show_login_form_link = $('<a href="#" class="show_loging_form">' + App.lang('Return to the login screen') + '</a>').appendTo(login_footer).hide();
      
      var show_login_form = function () {
        App.Wireframe.PageTitle.set(App.lang('Sign In'));
        auth_dialog_content.attr('class', 'login_form');
        auth_dialog_title.html(App.lang('Sign in with your credentials'));
        forgot_password_link.show();
        show_login_form_link.hide();
        
        login_form.show();
        forgot_password_form.hide();
        reset_password_form.hide();
        return false;
      };
      
      var show_forgot_password_form = function () {
        App.Wireframe.PageTitle.set(App.lang('Recover Password'));
        auth_dialog_content.attr('class', 'recover_password_form');
        auth_dialog_title.html(App.lang('Recover your password'));
        forgot_password_link.hide();
        show_login_form_link.show();
        
        login_form.hide();
        forgot_password_form.show();
        reset_password_form.hide();
        return false;
      };
      
      var show_reset_password_form = function () {
        App.Wireframe.PageTitle.set(App.lang('Reset Password'));
        auth_dialog_content.attr('class', 'reset_password_form');
        auth_dialog_title.html(App.lang('Enter new password for your account'));
        forgot_password_link.hide();
        show_login_form_link.show();
        
        login_form.hide();
        forgot_password_form.hide();
        reset_password_form.show();
        return false;
      };
      
      // render forms
      var login_form = this.renderLoginForm(settings, auth_dialog_content).appendTo(auth_dialog).hide();
      var forgot_password_form = this.renderForgotPasswordForm(settings, auth_dialog_content, show_login_form).appendTo(auth_dialog).hide();
      var reset_password_form = this.resetPasswordForm(settings, auth_dialog_content, show_login_form).appendTo(auth_dialog).hide();
      
      // forms toggler handling
      forgot_password_link.click(show_forgot_password_form);
      show_login_form_link.click(show_login_form);
      
      var href = window.location.href;
      if (href.indexOf('reset-password')!=-1 && href.indexOf('user_id') !=-1 && href.indexOf('code') != -1) {
        show_reset_password_form();
      } else {
        show_login_form();
      } // ifs      
      
      login_built = true;
    },

    /**
     * Check if login is built
     *
     * @return {Boolean}
     */
    'isLoginBuilt' : function () {
      return login_built;
    },
    
    /**
     * Renders the login form
     * 
     * @param Object settings
     * @param jQuery auth_dialog_content
     * @return jQuery
     */
    'renderLoginForm' : function (settings, auth_dialog_content) {
      var login_form = $('<form action="' + settings['login_url'].clean() + '" method="post" novalidate>' +
        '<div class="big_input control_holder"><label class="main_label" for="login_form_email">' + App.lang('Email Address') +' </label><input name="login[email]" id="login_form_email" tabindex="1" class="text_field" type="email" placeholder="' + App.lang('Email Address') + '"></div>' + 
        '<div class="big_input control_holder"><label class="main_label" for="login_form_password">' + App.lang('Password') + '</label><input name="login[password]" id="login_form_password" tabindex="2" type="password" class="password_field" placeholder="' + App.lang('Password') + '"></div>' + 
        '<div class="control_holder interface_picker_holder"><div class="select_interface"><ul>' +
          '<li><a href="#" title="Default" value="default"><img src="' + App.Wireframe.Utils.imageUrl('icons/interface_default.png', 'environment', 'default') + '" alt="' + App.lang('Default') + '" /></a></li>' +
          '<li><a href="#" title="Phone" value="phone"><img src="' + App.Wireframe.Utils.imageUrl('icons/interface_phone.png', 'environment', 'default') + '" alt="' + App.lang('Phone') + '" /></a></li>' +
        '</ul><input type="hidden" name="interface" class="selected_value" /></div></div>' +
        '<div class="login_action">' + 
        '<div class="control_holder login_form_remember_holder"><label for="login_form_remember" class="auto"><input id="login_form_remember" tabindex="4" class="auto checkbox_field" type="checkbox" name="login[remember]" value="checked"> <span class="checkbox_field_label">' + App.lang('Remember me') + '</span></label></div>' + 
        '<div class="button_holder"><button tabindex="5" class="login" type="submit" accesskey="s">' + App.lang('Login') + '</button></div>' +
        '</div>' + 
        '<input type="hidden" name="submitted" value="submitted" style="display: none">' + 
      '</form>');
        
        
      // maybe detect smart defaults depending of device used
      var initial_interface = 'default';      
      var interface_picker_wrapper = login_form.find('div.select_interface');
      var interface_picker_field = interface_picker_wrapper.find('input.selected_value');
      var interface_picker_interfaces = interface_picker_wrapper.find('a');

      // interface picker handling
      interface_picker_interfaces.click(function () {
        var selected_interface = $(this);
        interface_picker_interfaces.removeClass('selected');
        selected_interface.addClass('selected');
        interface_picker_field.val(selected_interface.attr('value'))
      });
      interface_picker_wrapper.find('a[value=' + initial_interface + ']').trigger('click');

      // Validate email address
      var email_field = login_form.find('input#login_form_email');
      // put the entered email into lowercase
      email_field.change(function () {
        email_field.val(email_field.val().toLowerCase());
      });
        
      login_form.submit(function() {
        email_field.val(email_field.val().toLowerCase());

        var email_address = jQuery.trim(email_field.val());
        
        if(email_address) {
          if(!App.isValidEmail(email_address)) {
            App.Wireframe.Flash.error('Please enter a valid email address');
            email_field.focus();
            return false;
          } // if
        } else {
          App.Wireframe.Flash.error('Please enter your email address');
          email_field.focus();
          
          return false;
        } // if
        
        // Validate password
        var password_field = login_form.find('input#login_form_password');
        var password = password_field.val();
        
        if(password == '') {
          App.Wireframe.Flash.error('Please enter your password');
          password_field.focus();
          
          return false;
        } // if
        
        // Show loader
        auth_dialog_content.hide();
        var loader = $('<div id="loader"></div>').insertBefore(auth_dialog_content);
        
        $.ajax({
          'url' : App.extendUrl(login_form.attr('action'), {'async' : 1}), 
          'type' : 'post', 
          'data' : {
            'login[interface]' : login_form.find('div.select_interface input.selected_value').val(),
            'login[redirect_to_url]' : window.location.href,
            'login[email]' : email_address, 
            'login[password]' : password, 
            'login[remember]' : login_form.find('input#login_form_remember')[0].checked ? 1 : 0, 
            'submitted' : 'submitted'
          }, 
          'success' : function(response) {
          	if ((typeof(response) != 'object') || (response['interface'] == 'undefined')) {
          	  App.Wireframe.Flash.error(response);
          	  loader.remove();
              auth_dialog_content.show();
              email_field.focus();
          	} else if (response['interface'] == 'default') {
              App.Wireframe.BackendLayout.buildBackend(response, response['redirect_to_url']);
            } else {
              window.location = response['redirect_to_url'];
            } // if
          }, 
          'error' : function(response) {
            if(typeof(response) == 'object') {
              var reason = App.Wireframe.Utils.responseToErrorMessage(response);
            } else {
              var reason = App.lang('Unknown error. Please contact support for assistance');
            } // if

            App.Wireframe.Flash.error(App.lang('Authentication Failed. Reason: :reason', {
              'reason' : reason
            }));

            loader.remove();
            auth_dialog_content.show();
            email_field.focus();
          }
        });
        
        return false;
      });
      return login_form;
    },
    
    /**
     * Renders the forgot password form
     * 
     * @param Object settings
     * @param jQuery auth_dialog_content
     * @return jQuery
     */
    'renderForgotPasswordForm' : function (settings, auth_dialog_content, success_function) {      
      var forgot_password_form = $('<form action="' + settings['forgot_password_url'].clean() + '" method="post" novalidate>' +
          '<div class="big_input control_holder"><label class="main_label" for="recover_form_email">' + App.lang('Email Address') +' </label><input name="forgot_password[email]" id="recover_form_email" tabindex="1" class="text_field" type="email" placeholder="' + App.lang('Email Address') + '"></div>' + 
          '<div class="button_holder"><button tabindex="5" class="login" type="submit" accesskey="s">Recover Password</button></div>' +
          '<input type="hidden" name="submitted" value="submitted" style="display: none">' + 
      '</form>');

      var email_field = forgot_password_form.find('input#recover_form_email');
      email_field.change(function () {
        email_field.val(email_field.val().toLowerCase());
      });
      
      forgot_password_form.submit(function () {
        // Validate email address
        email_field.val(email_field.val().toLowerCase());
        var email_address = jQuery.trim(email_field.val());
        
        if(email_address) {
          if(!App.isValidEmail(email_address)) {
            App.Wireframe.Flash.error('Please enter a valid email address');
            email_field.focus();
            return false;
          } // if
        } else {
          App.Wireframe.Flash.error('Please enter your email address');
          email_field.focus();
          
          return false;
        } // if
        
        // Show loader
        auth_dialog_content.hide();
        var loader = $('<div id="loader"></div>').insertBefore(auth_dialog_content);

        $.ajax({
          'url' : App.extendUrl(forgot_password_form.attr('action'), {'async' : 1}), 
          'type' : 'post', 
          'data' : {
            'forgot_password[email]' : email_address, 
            'submitted' : 'submitted'
          }, 
          'success' : function(response) {
            App.Wireframe.Flash.success('Email with instructions on how to reset your password has been sent to email address you have specified');
            
            loader.remove();
            auth_dialog_content.show();
            
            if (success_function && typeof (success_function) == 'function') {
              success_function();
            } // if

            // @todo return to the login screen
          }, 
          'error' : function(response) {
            App.Wireframe.Flash.error('Failed to recover password. Check if your email address is valid, and try again');
            
            loader.remove();
            auth_dialog_content.show();
            
            email_field.focus();
          }
        });
          
        return false;
      });
      
      return forgot_password_form;
    },
    
    /**
     * Renders the forgot password form
     * 
     * @param Object settings
     * @param jQuery auth_dialog_content
     * @return jQuery
     */
    'resetPasswordForm' : function (settings, auth_dialog_content, success_function) {
      var reset_password_form = $('<form action="' + window.location.href + '" method="post" novalidate>' +
          '<div class="big_input control_holder"><label class="main_label" for="reset_form_password">' + App.lang('New Password') + '</label><input name="reset[password]" id="reset_form_password" tabindex="1" type="password" class="password_field" placeholder="' + App.lang('New Password') + '"></div>' + 
          '<div class="big_input control_holder"><label class="main_label" for="reset_form_password_a">' + App.lang('Repeat New Password') + '</label><input name="reset[password_a]" id="reset_form_password_a" tabindex="1" type="password" class="password_field" placeholder="' + App.lang('Repeat New Password') + '"></div>' + 
          '<div class="button_holder"><button tabindex="5" class="default" type="login" accesskey="s">Set New Password</button></div>' +
          '<input type="hidden" name="submitted" value="submitted" style="display: none">' + 
      '</form>');
      
      reset_password_form.submit(function () {
        var password_field = reset_password_form.find('#reset_form_password');
        var password_repeat_field = reset_password_form.find('#reset_form_password_a');
        
        var new_password = $.trim(password_field.val());
        var new_password_repeat = $.trim(password_repeat_field.val());
        
        // validate passwords
        if (!new_password || new_password.length < 3) {
          App.Wireframe.Flash.error('Minimum password length is 3 characters');
          password_field.focus();
          return false;
        }; // if
        
        if (new_password_repeat != new_password) {
          App.Wireframe.Flash.error('Passwords do not match');
          password_repeat_field.focus();
          return false;
        } // if
        
        // Show loader
        auth_dialog_content.hide();
        var loader = $('<div id="loader"></div>').insertBefore(auth_dialog_content);

        $.ajax({
          'url' : App.extendUrl(reset_password_form.attr('action'), {'async' : 1}), 
          'type' : 'post', 
          'data' : {
            'reset[password]' : new_password, 
            'reset[password_a]' : new_password_repeat, 
            'submitted' : 'submitted'
          }, 
          'success' : function(response) {
            App.Wireframe.Flash.success('Password has been reset successfully. You may now login with your new login credentials');
            
            loader.remove();
            auth_dialog_content.show();
            
            if (success_function && typeof (success_function) == 'function') {
              success_function();
            } // if
          }, 
          'error' : function(response) {
            App.Wireframe.Flash.error('Failed to reset password. Please try again');
            
            loader.remove();
            auth_dialog_content.show();
            
            email_field.focus();
          }
        });
          
        return false;
      });
        
      return reset_password_form;
    },
    
    /**
     * Tear down login screen
     */
    'tearDownLogin' : function() {
      $('body').empty().removeClass('backend_login');
      login_built = false;
    }, 
    
    /**
     * Build backend interface based on given settings
     * 
     * @param Object s
     * @param String content_url
     */
    'buildBackend' : function(s, content_url) {
      
      var settings = jQuery.extend({
        'request' : null, 
        'logged_user' : null, 
        'menu_items' : null, 
        'breadcrumbs' : null, 
        'page_tabs' : {}, // Should be object, not array 
        'statusbar_items' : null, 
        'search_url' : '', 
        'quick_search_url' : ''
      }, s);
            
      if(login_built) {
        App.Wireframe.BackendLayout.tearDownLogin();
      } // if
      
      var body = $('body').addClass('backend');
      
      // set the langs
      App.langs = s['clientside_lang'];
      
      // Main menu
      body.append('<div id="wireframe_menu" popups_position="right" popups_offset="-18,0">' + 
        '<div id="menu"></div>' + 
        '<div id="wireframe_footer"><div id="footer">' + 
          '<p id="powered_by"><a href="http://www.activecollab.com/" target="_blank"><img src="' + App.Wireframe.Utils.imageUrl('layout/branding/acpowered-white.png', 'system', 'default') + '" alt="Powered by activeCollab" /></a></p>' + 
        '</div></div>' + 
      '</div>');
      
      App.Wireframe.MainMenu.init("menu");
      
      if(typeof(settings['menu_items']) == 'object' && settings['menu_items']) {
        App.Wireframe.MainMenu.batchSet(settings['menu_items']);
      } // if
      
      if (settings['menu_items_current']) {
        App.Wireframe.MainMenu.setCurrent(settings['menu_items_current']);
      } // if
      
      // Breadcrumbs
      body.append('<div id="wireframe_breadcrumbs">' + 
        '<ul id="breadcrumbs"></ul>' + 
      '</div>');
      
      App.Wireframe.BreadCrumbs.init('breadcrumbs');
      
      if(typeof(settings['breadcrumbs']) == 'object' && settings['breadcrumbs']) {
        App.Wireframe.BreadCrumbs.batchSet(settings['breadcrumbs']);
      } // if
      
      // Page tabs
      body.append('<div id="wireframe_page_tabs">' + 
        '<ul id="page_tabs"></ul>' + 
        '<ul id="page_tabs_right">' + 
          '<li id="global_search"></li>' + 
          '<li id="logout_button"><a href="' + settings['logout_url'].clean() + '" target="_self">Logout</a></li>' + 
        '</ul>' + 
      '</div>');
    
      App.Wireframe.PageTabs.init('page_tabs');
      
      if(typeof(settings['page_tabs']) == 'object' && settings['page_tabs']) {
        App.Wireframe.PageTabs.batchSet(settings['page_tabs'], settings['page_tabs_current']);
      } // if
            
      // Initialize quick search
      $('#global_search').backendQuickSearch({
        'search_url' : settings['search_url'], 
        'quick_search_url' : settings['quick_search_url']
      });
      
      body.find('#logout_button a').click(function() {
        var link = $(this);
        
        if(link.data('block_clicks')) {
          return false;
        } // if
        
        if(confirm(App.lang('Are you sure that you want to log out?'))) {
          History.pushState({
            'skip' : true
          }, null, App.Config.get('homepage_url'));
          
          link.data('block_clicks', true);
          
          $.ajax({
            'url' : App.extendUrl(link.attr('href'), {'async' : 1}), 
            'type' : 'post', 
            'data' : { 
              'submitted' : 'submitted'
            }, 
            'success' : function(response) {
              // if logout utl exists
              if (response.on_logout_url) {
                $('body').block();
                App.Wireframe.Flash.success('Logout successful. Redirecting, please wait.');
                window.location = response.on_logout_url;
                return false;
              } else {
                App.Wireframe.BackendLayout.buildLogin(response);                
              } // if
            }, 
            'error' : function(response) {
              App.Wireframe.Flash.error('Failed to log you in. Check your credentials and try again');
              link.data('block_clicks', false);
            }
          });
        } // if
        
        return false;
      });
      
      // Content
      body.append('<div id="wireframe_main_container">' + 
        '<div id="wireframe_page_title">' + 
          '<ul id="page_title_actions" style="display: none"></ul>' + 
          '<h1 id="page_title"></h1>' + 
        '</div>' + 
        '<div id="wireframe_content"></div>' + 
      '</div>');
      
      App.Wireframe.PageTitle.init('wireframe_page_title');
      App.Wireframe.PageTitle.set(App.lang('Loading...'));
      
      if (settings['page_title']) {
        App.Wireframe.PageTitle.set(settings['page_title']);
      } // if
      
      if (settings['page_title_actions']) {
        App.Wireframe.PageTitle.batchSetActions(settings['page_title_actions']);
      } // if
      
      if (settings['print_url']) {
        App.Wireframe.PageTitle.setPrintUrl(settings['print_url']);
      } // if
      
      if (settings['benchmark']) {
        App.Wireframe.Benchmark.set(settings['benchmark']);
      } // if  
      
      if (settings['list_mode_enabled']) {
        App.Wireframe.Content.enableListMode();
      } else {
        App.Wireframe.Content.disableListMode();
      } // if
      
      App.Wireframe.Content.init('wireframe_main_container', 'wireframe_content');
      
      // Status bar
      body.append('<div id="wireframe_statusbar" popups_position="top" popups_offset="0,10">' + 
        '<div id="bottom_bar"></div>' + 
      '</div>');
      
      App.Wireframe.Statusbar.init("bottom_bar");
      
      if(typeof(settings['statusbar_items']) == 'object' && settings['statusbar_items']) {
        App.Wireframe.Statusbar.batchSet(settings['statusbar_items']);
      } // if

      // try to see if there is a hash in request, and if it is, we need to get rid of it
      if (content_url && content_url.indexOf('#') != -1) {
        var new_route = content_url.substring(content_url.indexOf('#') + 1);
        
        if (new_route[0] == '?' || new_route[0] == '/') {
          new_route = new_route.substring(1);
        } // if
        
        if(App.Config.get('path_info_through_query_string')) {
          if (new_route.indexOf('?') != -1) {
            new_route = new_route.replace('?', '');
          } // if
          content_url = App.Config.get('url_base') + '?' + new_route;
        } else {
          content_url = App.Config.get('url_base') + '/' + new_route;
        } // if
      } // if

      
      // if we're trying to load homepage, and there is no following slash, we need to add it, otherwise we'll get server redirect
      if (content_url && content_url == App.Config.get('homepage_url')) {
        content_url = App.Config.get('homepage_url');
        if (content_url[(content_url.length - 1)] != '/') {
          content_url += '/';
        } // if
      } // if
            
      // And now redirect user to the originally requested page if there is any
      if (content_url) {
        App.Wireframe.Content.setFromUrl(content_url, true);
      } // if
      
      // And done!
      backend_built = true;
    }, 
    
    /**
     * Tear down backend
     */
    'tearDownBackend' : function() {
      $('body').empty().removeClass('backend');
      backend_built = false;
    }
    
  };
  
}();

/**
 * Wireframe content handling
 */
App.Wireframe.Content = function() {
  
  /**
   * Main container instance
   *
   * @var jQuery
   */
  var main_container;
  
  /**
   * Content wrapper
   *
   * @var jQuery
   */
  var content_wrapper;
  
  /**
   * Loading queue
   *
   * @var Array
   */
  var loading = [];
  
  /**
   * Current content URL
   * 
   * @param String
   */
  var current_url = false;
  
  /**
   * Load URL
   * 
   * @param String url
   * @param Function success
   * @param Function error
   */
  var load_from_url = function(url, success, error) {
    var inline_loader = $('<div class="inline_loader"><div class="inline_loader_inner"></div><div class="inline_loader_bottom">' + App.lang('Loading') + ' ...</div></div>').appendTo(main_container);
    window.soft_refreshed = false;
    
    $.ajax({
      'url' : App.extendUrl(url, {'inline' : 1}), 
      'type' : 'get', 
      'success' : function(response) {        
        inline_loader.remove();
        App.Wireframe.Content.set(response, url);
        
        // download pending file
        if (pending_download) {
          setTimeout(function () {
            window.location.href = pending_download;
            pending_download = false;
          }, 500);
        } // if
        
        if(typeof(success) == 'function') {
          success.apply(this, [ response ]);
        } // if
      }, 
      'error' : function(response) {
        inline_loader.remove();
        App.Wireframe.Flash.error('Failed to load page. Please try again later');
        
        if(typeof(error) == 'function') {
          error.apply(this, [ response ]);
        } // if
      }
    });
  }; // load_from_url
  
  /**
   * Load next page in the queue
   */
  var load_next_page = function() {
    if(typeof(loading[0]) == 'string') {
      load_from_url(loading[0], function(response) {
        loading.splice(0, 1); // Remove frist element
        if(loading.length > 0) {
          load_next_page();
        } // if
      }, function(response) {
        loading.splice(0, 1); // Remove frist element
        
        if(loading.length > 0) {
          load_next_page();
        } // if
      });
    } // if
  };
  
  var pending_download = false;
  
  // Public interface
  return {
    
    /**
     * Initialize content handler
     *
     * @param String wrapper_id
     */
    'init' : function(main_container_id, wrapper_id) {
      main_container = $('#' + main_container_id);
      content_wrapper = $('#' + wrapper_id);
      
      // if this element is reinitialized first unbind history api handling and then bind it again      
      $(window).unbind('statechange.backend_layout').bind('statechange.backend_layout', function(e) {
        if (History.getState().data['skip']) {
          return null;
        } // if

        // set the current url
        current_url = History.getState()['url'];

        // set indicator to false
        History.handled = false;

        // trigger event so other plugins can handle state_changed
        App.Wireframe.Events.trigger('history_state_changed');

        // if some plugin handled this event, we don't need to handle it globally
        if (History.handled) {
          return true;
        } // if

        // do the default action (load content)
        loading.push(History.getState()['url']);
        load_next_page();
      });

      // catch the refresh key combo
      $(window).bind('keydown', function (event) {
        // if shift or alt key is pressed
        if (event.shiftKey || event.altKey) {
          return true;
        } // if

        var refresh = false

        // only F5 pressed
        if (event.keyCode == 116) {
          if (!event.metaKey && !event.ctrlKey) {
            refresh = true;
          } // if
        } else {
          if ($.platform.mac) {
            if (event.metaKey && event.keyCode == 82 && !event.ctrlKey) {
              refresh = true;
            } // if
          } else {
            if (!event.metaKey && event.keyCode == 82 && event.ctrlKey) {
              refresh = true;
            } // if
          } // if
        } // if

        // if key is not refresh, forward it
        if (!refresh) {
          return true;
        } // if

        // if it's already soft refreshed do the real refresh
        if (window.soft_refreshed) {
          return true;
        } // if

        // put soft refreshed flag to true
        window.soft_refreshed = true;

        // close the flyout
        App.widgets.FlyoutDialog.front().close();

        // if object list exists, refreshes it, and if not the content
        var objects_list = $('.objects_list_wrapper:first');
        if (!objects_list.length || (objects_list.length && !objects_list.objectsList('reload_current'))) {
          App.Wireframe.Content.reload();
        } // if

        // stop propagation
        return false;
      });
    },
    
    /**
     * Set wireframe content
     *
     * @param String content
     * @param string URL
     */
    'set' : function(content, url) {
      App.Wireframe.Content.unbind(); // unbind all content bound events
      App.Wireframe.Events.unbind('.single'); // unbind all single bound events
      App.Wireframe.Events.unbind('.inline_tab'); // unbind inline tab
      
      if(typeof(content_wrapper) != 'undefined') {
        content_wrapper.empty().append(content);
        current_url = url;

        App.Wireframe.Events.trigger('content_updated', [ content_wrapper[0], url ]);
      } // if
    },
    
    /**
     * Reload page content
     */
    'reload' : function() {
      if(current_url) {
        load_from_url(current_url);
        window.soft_refreshed = true;
      } // if
    },
    
    /**
     * Set page content from URL
     *
     * @param String url
     * @param Boolean force
     */
    'setFromUrl' : function(url, force) {
      if(typeof(url) == 'string' && url) {
        App.widgets.FlyoutDialog.front().close(); // close dialog if it's open
        
        if (url.toLowerCase().indexOf('disposition=attachment') != -1) {
          pending_download = url;
          url = App.Config.get('url_base');
        } // if
        
        if(force) {
          load_from_url(url);
        } else {
          if(App.Wireframe.Content.isCurrentUrl(url)) {
            App.Wireframe.Content.reload(); // Refresh page
          } else {
            var skip_loading = false;
            
            // check if link is already first element to be loaded in queue
            if (loading && typeof(loading) == 'object' && loading.length) {
              if (loading[loading.length - 1] == url) {
                skip_loading = true;
              } // if
            } // if
            
            // if it's not, load it
            if (!skip_loading) {
              var page_prefix = App.Config.get('identity_name') ? App.Config.get('identity_name') + ' — ' : false;
              var loading_title = page_prefix + App.lang('Loading...');

              History.pushState({'handler' : 'content'}, loading_title, url);
            } // if            
          } // if
        } // if
      }
    },
    
    /**
     * Enable list mode
     */
    'enableListMode' : function() {
      if(!$('body').is('body.list_mode')) {
        $('body').addClass('list_mode');
      } // if
    },
    
    /**
     * Disable list mode
     */
    'disableListMode' : function() {
      if($('body').is('body.list_mode')) {
        $('body').removeClass('list_mode');
      } // if
    },
    
    /**
     * Set event listener for currently loaded content
     *
     * @param String event
     * @param Function callback
     */
    'bind' : function(event, callback) {
      App.Wireframe.Events.bind(event+'.content', callback);
    }, 
    
    /**
     * Unbind content event
     *
     * @param String event
     */
    'unbind' : function(event) {
      if (event == undefined) {
        event = '';
      } // if
      App.Wireframe.Events.unbind(event+'.content');
    }, 
    
    /**
     * Trigger content event
     *
     * @param String event
     * @param Array params
     */
    'trigger' : function(event, params) {
      if (event == undefined) {
        event = '';
      } // if
      App.Wireframe.Events.trigger(event+'.content', params);
    },
    
    /**
     * Returns true if url is current URL
     * 
     * @param String url
     * @return boolean
     */
    'isCurrentUrl' : function(url) {
      if(typeof(url) == 'string' && typeof(current_url) == 'string') {
        return unescape(url) == unescape(current_url);
      } else {
        return false;
      } // if
    } 
    
  };
  
}();

/**
 * Main menu implementation
 */
App.Wireframe.MainMenu = function() {
  
  /**
   * Menu wrapper instance
   *
   * @var jQuery
   */
  var menu_wrapper;
  
  // Public interface
  return {
    
    /**
     * Initialize main menu
     *
     * @param String wrapper_id
     */
    'init' : function(wrapper_id) {
      menu_wrapper = $('#' + wrapper_id).append('<ul class="group" id="menu_group_main"></ul><ul class="group" id="menu_group_profile"></ul>');
    },
    
    /**
     * Add menu item
     *
     * @param String item_name
     * @param Object item
     * @param Boolean is_current
     */
    'add' : function(item_name, item, is_current) {
      var item = jQuery.extend({ 
        'title' : null, 
        'url' : '', 
        'icon' : null, 
        'onclick' : null,  
        'group' : 'main',
        'add_before' : null
      }, item);
      
      var group = menu_wrapper.find('#menu_group_' + item['group']);
      
      if(group.length && item.title) {
        var list_item = $('<li id="menu_item_' + item_name + '" class="item">' + 
          '<a href="' + item['url'].clean() + '">' + 
            '<span class="icon"><img src="' + item['icon'].clean() + '" /></span>' + 
            '<span class="label">' + item['title'].clean() + '</span>' + 
          '</a>' + 
        '</li>');
        
        if(item['add_before'] != null) {
        	var before_item = menu_wrapper.find('#menu_item_' + item['add_before']);
        	before_item.before(list_item);
        } else {
        	group.append(list_item);
        }//if
        
        if(is_current) {
          group.find('li.item').removeClass('current');
          list_item.addClass('current');
        } // if
        
        if(typeof(item['onclick']) == 'string' && item['onclick']) {
          var onclick;
          eval('onclick = ' + item['onclick']);
          
          if(typeof(onclick) == 'function') {
            onclick.apply(list_item[0]);
          } // if
        } // if
      } // if
    },
    
    /**
     * Remove item by name
     * 
     */
    'remove' : function(item_name) {
    	var item = menu_wrapper.find('#menu_item_' + item_name).remove();
    },
    
    /**
     * Return name of the current item
     * 
     * @return String
     */
    'getCurrent' : function() {
      var current_item = menu_wrapper.find('li.item.current');
      
      if(current_item.length) {
        return current_item.attr('id').substr(10);
      } else {
        return false;
      } // if
    }, 
    
    /**
     * Set current menu item
     */
    'setCurrent' : function(name) {
      var item = menu_wrapper.find('#menu_item_' + name);
      
      if(item.length > 0) {
        menu_wrapper.find('li.item').removeClass('current');
        item.addClass('current');
      } // if
    },
    
    /**
     * Add multiple items to the menu
     *
     * @param Object items
     * @param String current_item
     */
    'batchSet' : function(items, current_item) {
      App.Wireframe.MainMenu.clear();
      
      for(var item_name in items) {
        App.Wireframe.MainMenu.add(item_name, items[item_name], (current_item == item_name));
      } // for
    },
    
    /**
     * Clear main menu group
     */
    'clear' : function() {
      App.Wireframe.Events.unbind('.menubar');
      menu_wrapper.find('ul.group').empty();
    }
    
  };
  
}();

/**
 * Statusbar implementation
 */
App.Wireframe.Statusbar = function () {
  
  /**
   * Statusbar wrapper
   *
   * @var jQuery
   */
  var statusbar_wrapper;
  
  /**
   * List of left aligned status bar items
   *
   * @var jQuery
   */
  var statusbar_left;
  
  /**
   * List of right aligned status bar items
   *
   * @var jQuery
   */
  var statusbar_right;
   
  // Public interface
  return {
    
    /**
     * Initialize status bar
     *
     * @param String wrapper_id
     */
    'init' : function(wrapper_id) {
      statusbar_wrapper = $('#' + wrapper_id);
      
      statusbar_left = $('<div id="statusbar_left_options" class="bottom_options"></div>').appendTo(statusbar_wrapper);
      statusbar_right = $('<div id="statusbar_right_options" class="bottom_options"></div>').appendTo(statusbar_wrapper);
    },
    
    /**
     * Add item to statusbar list
     *
     * @param String item_name
     * @param Object item
     */
    'add' : function(item_name, item) {
      var item = jQuery.extend({ 
        'title' : null, 
        'url' : '', 
        'icon' : null, 
        'badge' : null,
        'onclick' : null,  
        'group' : 'left',
        'target' : null
      }, item);
      
      var badge_value = typeof(item['badge']) == 'number' && item['badge'] > 0 ? item['badge'] : '';
      
      var list_item = $('<li class="icon_button first_level_item_container" id="statusbar_item_' + item_name + '">' +
        '<a href="' + item['url'].clean() + '" style="background-image: url(\'' + item['icon'].clean() + '\')" class="first_level_item with_caption" ' + (item.target ? 'target="' + item.target + '"' : null) + '>' + item['title'].clean() + '<span class="badge">' + badge_value + '</span></a>' +
      '</li>');
      
      if(!badge_value) {
        list_item.find('span.badge').hide();
      } // if
      
      if(item['group'] == 'right') {
        statusbar_right.append(list_item);
      } else {
        statusbar_left.append(list_item);
      } // if
      
      var custom_onclick = false;
      if(typeof(item['onclick']) == 'string' && item['onclick']) {
        var onclick;
        eval('onclick =' + item['onclick']);
        
        if(typeof(onclick) == 'function') {
          onclick.apply(list_item[0]);
          custom_onclick = true;
        } // if
      } // if
      
      if(!custom_onclick && !item.target) {
        list_item.contextPopup({
          'title' : item['title'],
          'data' : App.extendUrl(item['url'], {'async' : 1}),
          'dataType' : 'url',
          'triggerEvent' : 'click'
        });
      } // if
    },
    
    /**
     * Set item badge value
     *
     * @param String name
     * @param mixed value
     */
    'setItemBadge' : function(name, value) {
      if (!statusbar_wrapper || !statusbar_wrapper.length) {
        return false;
      } // if

      var badge = statusbar_wrapper.find('#' + name + ' span.badge');
      
      if(badge.length) {
        badge.text(value);
        
        if(value) {
          badge.show();
        } else {
          badge.hide();
        } // if
      } // if
    }, 
    
    /**
     * Add complete set of statusbar items at once
     *
     * @param Object items
     */
    'batchSet' : function(items) {
      App.Wireframe.Statusbar.clear();
      
      for(var item_name in items) {
        App.Wireframe.Statusbar.add(item_name, items[item_name]);
      } // for
    },
    
    /**
     * Clear all statusbar items
     */
    'clear' : function() {
      App.Wireframe.Events.unbind('.statusbar');
      statusbar_left.empty();
      statusbar_right.empty();
    }
    
  };
  
}();

/**
 * Page tabs implementation
 */
App.Wireframe.PageTabs = function() {
  
  /**
   * Page tabs wrapper instance
   *
   * @var jQuery
   */
  var page_tabs_wrapper;
  
  // Public interface
  return {
    
    /**
     * Page tabs initialization method
     *
     * @param String wrapper_id
     */
    'init' : function(wrapper_id) {
      page_tabs_wrapper = $('#' + wrapper_id);
    },
    
    /**
     * Add tab to page tabs list
     *
     * @param String name
     * @param String text
     * @param String url
     * @param Boolean is_current
     */
    'addTab' : function(name, text, url, is_current) {
      var page_tab = $('<li id="page_tab_' + name + '" class="page_tab"><a href="' + url.clean() + '">' + text.clean() + '</a></li>');
      
      if(is_current) {
        page_tab.find('a').addClass('current');
      } // if
      
      page_tabs_wrapper.append(page_tab);
    },
    
    /**
     * Add icon to the tabs list
     * 
     * @param String name
     * @param String text
     * @param String url
     * @param String icon
     */
    'addIcon' : function(name, text, url, icon) {
      page_tabs_wrapper.append('<li id="page_tab_' + name + '" class="page_tab page_tab_icon"><a href="' + url.clean() + '"><img src="' + icon.clean() + '"> <span>' + text.clean() + '</span></a></li>');
    },
    
    /**
     * Add separator
     */
    'addSeparator' : function() {
      page_tabs_wrapper.append('<li class="separator"></li>');
    },
    
    /**
     * Set specific tab as current tab
     *
     * @param jQuery
     */
    'setAsCurrent' : function(tab_name) {
      var tab = page_tabs_wrapper.find('#page_tab_' + tab_name);
      if(tab.length > 0) {
        page_tabs_wrapper.find('li.page_tab a').removeClass('current');
        tab.find('a').addClass('current');
      } // if
    },
    
    /**
     * Batch set tabs
     *
     * @param Object tabs
     * @param String current_tab_name
     */
    'batchSet' : function(tabs, current_tab_name) {
      var tabs_wrapper = $('#page_tabs');
      
      App.Wireframe.PageTabs.clear();

      $.each(tabs, function (tab_name, tab) {
        if(tab['text'] == '-') {
          App.Wireframe.PageTabs.addSeparator();
        } else if(tab_name.substr(0, 5) == 'icon-' && typeof(tab['icon']) == 'string' && tab['icon']) {
          App.Wireframe.PageTabs.addIcon(tab_name, tab['text'], tab['url'], tab['icon']);
        } else {
          App.Wireframe.PageTabs.addTab(tab_name, tab['text'], tab['url'], tab_name == current_tab_name);
        } // if
      });
      
      page_tabs_wrapper.find('li.page_tab:first').addClass('first');
    }, 
    
    /**
     * Clear list of page tabs
     */
    'clear' : function() {
      page_tabs_wrapper.empty();
    }
    
  };
  
}();

/**
 * Page bread crumbs implementation
 */
App.Wireframe.BreadCrumbs = function() {
  
  /**
   * Breadcrumbs wtapper instance
   *
   * @var jQuery
   */
  var breadcrumbs_wrapper;
  
  // Public interface
  return {
    
    /**
     * Initialize bread crumbs
     *
     * @param String wrapper_id
     */
    'init' : function(wrapper_id) {
      breadcrumbs_wrapper = $('#' + wrapper_id);
    },
    
    /**
     * Add single breadcrumb to the list of breadcrumbs
     *
     * url parameter is optional
     *
     * @param String text
     * @param String url
     */
    'add' : function(text, url) {
      if(url) {
        breadcrumbs_wrapper.append('<li><a href="' + url.clean() + '" title="' + text.clean() + '">' + text.clean() + '</a><span class="raquo">&raquo;</span></li>');
      } else {
        breadcrumbs_wrapper.append('<li class="last"><span class="current">' + text.clean() + '</span></li>');
      } // if
    }, 
    
    /**
     * Mass set bread crumbs
     *
     * @param Object breadkcrumbs
     */
    'batchSet' : function(breadcrumbs) {
      App.Wireframe.BreadCrumbs.clear();
      
      for(var i in breadcrumbs) {
        App.Wireframe.BreadCrumbs.add(breadcrumbs[i]['text'], typeof(breadcrumbs[i]['url']) == 'undefined' ? '' : breadcrumbs[i]['url']);
      } // for
    },
    
    /**
     * Clear bread crumbs
     */
    'clear' : function() {
      breadcrumbs_wrapper.empty();
    }
    
  };
  
}();

/**
 * Page title behavior
 */
App.Wireframe.PageTitle = function() {
  
  /**
   * Page title wrapper instance
   *
   * @var jQuery
   */
  var page_title_wrapper;
  
  /**
   * Page title (h1)
   *
   * @var jQuery
   */
  var page_title;
  
  /**
   * Page title actions (ul)
   *
   * @var jQuery
   */
  var page_actions;
  
  /**
   * Browser title
   * 
   * @var JQuery
   */
  var browser_title;
  
  /**
   * Show dropdown timer
   * 
   * @var DropDown
   */
  var show_dropdown_timer;
  
  // Public interface
  return {
    
    /**
     * Initialize page title
     *
     * @param String wrapper_id
     */
    'init' : function(wrapper_id) {
      page_title_wrapper = $('#' + wrapper_id);
      
      page_title = page_title_wrapper.find('h1');
      page_actions = page_title_wrapper.find('ul');
    }, 
    
    /**
     * Return current page title
     *
     * @return String
     */
    'get' : function() {
      return page_title.text();
    },
    
    /**
     * Set page title to a new value
     *
     * @param String title
     */
    'set' : function(value) {
      var page_sufix = App.Config.get('identity_name') ? ' — ' + App.Config.get('identity_name') : false;

      if ($.browser.msie) {
        document.title = page_sufix ? value + page_sufix : value; // workaround for internet explorer
      } else {
        $('html head title:last').text(page_sufix ? value + page_sufix : value);
      } // if

      if (page_title && page_title.length) {
        page_title.text(value);
      } // if
    },
    
    /**
     * Add widget to the page title befor the title itself
     * 
     * @param string widget_id
     * @param jquery widget
     */
    'addWidgetBefore' : function (widget_id, widget) {
      // remove widget if widget exists
      this.removeWidget(widget_id);

      var widgets_wrapper = page_title.find('span.page_title_left_widgets');
      if (!widgets_wrapper.length) {
        widgets_wrapper = $('<span class="page_title_left_widgets"></span>').prependTo(page_title);
      } // if

      var full_id = 'page_title_widget_' + widget_id;
      var widget_wrapper = $('<span id="' + full_id + '" class="left_title_widget widget"></span>').append(widget).prependTo(widgets_wrapper);
    },
    
    /**
     * Add widget to the page title after the title
     * 
     * @param string widget_id
     * @param jquery widget
     */
    'addWidgetAfter' : function (widget_id, widget) {
      // remove widget if widget exists
      this.removeWidget(widget_id);

      var widgets_wrapper = page_title.find('span.page_title_right_widgets');
      if (!widgets_wrapper.length) {
        widgets_wrapper = $('<span class="page_title_right_widgets"></span>').appendTo(page_title);
      } // if

      var full_id = 'page_title_widget_' + widget_id;      
      var widget_wrapper = $('<span id="' + full_id + '" class="left_title_widget widget"></span>').append(widget).appendTo(widgets_wrapper);
    },
    
    /**
     * Remove page title widget with widget_id
     * 
     * @param string widget_id
     */
    'removeWidget' : function (widget_id) {
      var full_id = 'page_title_widget_' + widget_id;
      page_title.find('#' + full_id);
    },
    
    /**
     * Set clean title (remove all actions)
     */
    'setClean' : function(value) {
      App.Wireframe.PageTitle.clearActions();
      App.Wireframe.PageTitle.set(value);
    },
    
    /**
     * Create a page title spanshot
     *
     * @return Object
     */
    'snapshot' : function(snap) {
      if(typeof(snap) == 'undefined') {
        var page_title_clone = page_title.clone();
        page_title_clone.find('span.page_title_right_widgets').remove();
        page_title_clone.find('span.page_title_left_widgets').remove();
        page_title_clone.find('ul.page_title_actions').remove();

        return {
          'left_widgets' : page_title.find('span.page_title_left_widgets').clone(true, true),
          'right_widgets' : page_title.find('span.page_title_right_widgets').clone(true, true),
          'actions' : page_actions.clone(true, true),
          'title' : page_title_clone.text()
        };
      } else {
        App.Wireframe.PageTitle.set(snap.title);
        App.Wireframe.PageTitle.clearActions();
        
        var old_page_actions = page_actions;
        page_actions = snap.actions.clone(true, true).insertAfter(page_actions);
        old_page_actions.remove();

        if (snap.left_widgets && snap.left_widgets.length) {
          page_title.prepend(snap.left_widgets);
        } // if

        if (snap.right_widgets && snap.right_widgets.length) {
          page_title.append(snap.right_widgets);
        } // if
      } // if
    },

    /**
     * Add single page action
     *
     * @param String name
     * @param Object action
     * @param String class_name
     */
    'addAction' : function(name, action, class_name) {
      var item = $('<li class="title_action" id="page_action_' + name + '"><a href="' + action['url'].clean() + '" class="title_action_button" title="' + action['text'].clean() + '">' + action['text'].clean() + '</a></li>').disableSelection().appendTo(page_actions);
      var link = item.find('a');
      
      if(class_name) {
        link.addClass(class_name);
      } // if
      
      if(action['icon']) {
        link.css({
          'background-image' : 'url(' + action['icon'] + ')', 
          'padding-left' : '24px'
        });
      } // if
      
      // Set attributes
      if(typeof(action['attributes']) == 'object' && action['attributes']) {
        link.attr(action['attributes']);
      } // if
      
      if(typeof(action['onclick']) == 'string' && action['onclick']) {
        var onclick;
        
        eval('onclick = ' + action['onclick']);
        
        if(typeof(onclick) == 'function') {
          onclick.apply(link[0]);
        } // if
      } else if (typeof(action['onclick']) == 'function') {
        item.click(function () {
          return action['onclick'].apply(link[0]);
        });
      } else {
        link.openInline();
      } // if
      
      page_actions.show();
    },
    
    /**
     * Add drop-down
     *
     * @param String name
     * @param String text
     * @param Object subitems
     * @param String class_name
     */
    'addDropDown' : function(name, text, subitems, class_name) {
      if(typeof(class_name) == 'undefined') {
        class_name = '';
      } // if
      
      var dropdown = $('<li class="title_action object_actions drop_down" id="page_action_' + name + '">'+
        '<span class="title_action_button ' + class_name  + '">' + text.clean() + '</span>' +
        '<div class="title_actions_dropdown menu_dropdown"><ul class="object_options"></ul></div>'+
      '</li>').appendTo(page_actions).disableSelection();
      
      // Show / hide drop-down
      dropdown.hover(function() {
        show_dropdown_timer = window.setTimeout('App.Wireframe.PageTitle.showDropDown("' + name + '")', 100);
      }, function() {
        App.Wireframe.PageTitle.hideDropDown(name);
        clearTimeout(show_dropdown_timer);
      });
      
      var options_list = dropdown.find('ul');
      
      this.renderDropdownSubitems(subitems, options_list);

      dropdown.find('div.drop_down *').click(function () {
        dropdown.removeClass('opened');
      });
      
      page_actions.show();
    },
    
    /**
     * Render all subitems to some dropdown
     * @param Object subitems
     * @param jQuery options_list
     */
    'renderDropdownSubitems' : function (subitems, options_list) {
      for(var i in subitems) {
        if(subitems[i]['text'] && subitems[i]['url']) {
          var item = $('<li id="page_action_' + i + '" class="subaction"><a href="' + subitems[i].url.clean() + '" title="' + subitems[i]['text'].clean() + '">' + subitems[i]['text'].clean() + '</a></li>').appendTo(options_list);
          var link = item.find('a');
          
          if(subitems[i]['class']) {
            link.addClass(subitems[i]['class']);
          } // if
          
          if(subitems[i]['icon']) {
            link.css({
              'background-image' : 'url(' + subitems[i]['icon'] + ')'
            });
          } // if
          
          // Set custom attributes
          for(var j in subitems[i]) {
            if(j == 'url' || j == 'text' || j == 'class' || j == 'onclick') {
              continue;
            } // if
            
            link.attr(j, subitems[i][j]);
          } // for
          
          // Set attributes
          if(typeof(subitems[i]['attributes']) == 'object' && subitems[i]['attributes']) {
            link.attr(subitems[i]['attributes']);
          } // if
          
          // Set click handler
          if(typeof(subitems[i]['onclick']) == 'string' && subitems[i]['onclick']) {
            var onclick;
            eval('onclick = ' + subitems[i]['onclick']);

            if(typeof(onclick) == 'function') {
              onclick.apply(link[0]);
            } // if
          } else if (typeof(subitems[i]['onclick']) == 'function') {
            link.click(function () {
              return subitems[i]['onclick'].apply(link[0]);
            });
          } else {
            link.openInline();
          } // if
        } else {
          $('<li id="page_action_' + i + '" class="separator"></li>').appendTo(options_list);
        } // if
      } // if
    },

    /**
     * Update dropdown with new options
     * 
     * @param string dropdown_id
     * @param Object subitems
     */
    'updateDropDown' : function (dropdown_id, subitems) {
      var dropdown = dropdown_id;
      if (typeof(dropdown) == 'string') {
        dropdown = $('#' + dropdown_id);
      } // if
      
      var options_list = dropdown.find('ul').empty();
      
      this.renderDropdownSubitems(subitems, options_list);
    },

    /**
     * Add single page action to dropdown
     *
     * @param String name
     * @param Object action
     */
    'addActionToDropdown' : function(name, action, class_name, dropdown_id, dropdown_title) {
      var dropdown_target = page_actions.find('li#page_action_'+dropdown_id+' div.menu_dropdown ul');
      if (dropdown_target.length) {
        var dropdown = dropdown_target.parents('li.drop_down:first').show();
      } else {
        var dropdown = $('<li class="title_action object_actions drop_down" id="page_action_' + dropdown_id + '">'+
          '<span class="title_action_button ' + dropdown_id  + '">' + dropdown_title.clean() + '</span>' +
          '<div class="title_actions_dropdown menu_dropdown"><ul class="object_options"</ul></div>'+
        '</li>').appendTo(page_actions).disableSelection();
        
        dropdown.click(function () {
          dropdown.toggleClass('opened');
        }).delayedHover(function () {}, function () {
          dropdown.removeClass('opened');
        }, 300);
        
        dropdown.find('div.drop_down *').click(function () {
          dropdown.removeClass('opened');
        });
          
        var dropdown_target = dropdown.find('.menu_dropdown ul:first');
      } // if
      
      var item = $('<li class="subaction" id="page_action_' + name + '"><a href="' + action['url'].clean() + '" class="' + class_name  + '">' + action['text'].clean() + '</a></li>').disableSelection().appendTo(dropdown_target);
      
      if(typeof(action['onclick']) == 'function') {
        item.find('a').click(action['onclick']);
      } // if
      
      page_actions.show();
    },
    
    /**
     * Show drop-down for a given option
     * 
     * @param String name
     */
    'showDropDown' : function(name) {
      $('#page_action_' + name).addClass('opened');
    },
    
    /**
     * Hide drop-down for a given action
     * 
     * @param String name
     */
    'hideDropDown' : function(name) {
      $('#page_action_' + name).removeClass('opened');
    }, 
    
    /**
     * Add multiple actions at once
     *
     * @param Object actions
     */
    'batchSetActions' : function(actions) {
      App.Wireframe.PageTitle.clearActions();
      
      for(var action_name in actions) {
        var class_name = typeof(actions[action_name]) != 'undefined' ? actions[action_name] : '';
        
        if(typeof(actions[action_name]['subitems']) == 'object' && actions[action_name]['subitems'] !== null) {
          this.addDropDown(action_name, actions[action_name]['text'], actions[action_name]['subitems'], class_name);
        } else {
          this.addAction(action_name, actions[action_name], class_name);
        } // if
      };
    },
    
    /**
     * Initialize bahavior of page action
     *
     * @param String name
     * @param Function init_function
     */
    'initAction' : function(name, init_func) {
      var to_init = typeof(name) == 'string' ? [ name ] : name;
      
      for(var i in to_init) {
        var page_action = page_actions.find('#page_action_' + to_init[i]);
        
        if(page_action.length) {
          init_func.apply(page_action[0]);
        } // if
      } // for
    },
    
    /**
     * Remove action if exists
     * 
     * @param String name
     * @returns boolean
     */
    'removeAction' : function (name) {
      var page_action = page_actions.find('#page_action_'+name);
      if (!page_action.length) {
        return false;
      } // if
      
      var parent_dropdown = page_action.parents('li.drop_down:first');
      page_action.remove();
      
      // dropdown doesnt contain any options, so it needs to be removed (in future, if we enable manual addition to the dropdown, we will need to hide it instead remove it
      if (parent_dropdown.length && !parent_dropdown.find('li').length) {
        parent_dropdown.remove();
      } // if
      
      return true;
    },
    
    /**
     * Remove all existing page actions
     */
    'clearActions' : function() {
      page_actions.empty().hide();
    },
    
    /**
     * Handles print page functionality - if url is false, remove button if exists, otherwise adds the print button with given url
     * 
     * @param String url
     */
    'setPrintUrl' : function (url) {
      if (url) {
        App.Wireframe.PageTitle.addAction('print', {
          'url' : url,
          'text' : App.lang('Print'),
          'onclick' : function () {
            App.Wireframe.Print.doPrint($(this).attr('href'));
            return false;
          }
        }, 'page_print');
      } else {
        App.Wireframe.PageTitle.removeAction('print');
      } // if
    }
   
  };
  
}();


/**
 * Wireframe printing
 */
App.Wireframe.Print = function () {

  /**
   * Is active indicator
   */
  var is_active = false;
  
  // Public interface
  return {
    
    /**
     * Do print
     * 
     * @param String url
     */
    'doPrint' : function (url) {
      if (is_active) {
        App.Wireframe.Print.closePrint();
      } // if
      
      var print_overlay = $('<div class="print_overlay"></div>').appendTo($('body'));
      var print_iframe = $('<iframe class="print_iframe" src="' + url + '"></iframe>').appendTo(print_overlay);
      var print_dialog = $('<div class="print_dialog"></div>').insertAfter(print_overlay).append(App.lang('<strong>Preparing page for printing.</strong><br />Please wait.'));
      var close_print_dialog = $('<a href="#" class="close_print_dialog">' + App.lang('Close Dialog') + '</a>').appendTo(print_dialog);

      close_print_dialog.click(function () {
        App.Wireframe.Print.close();
        return false;
      });
    },
    
    /**
     * Close print overlay and cancel print preparation
     */
    'close' : function () {
      var print_overlay = $('div.print_overlay').remove();
      var print_dialog = $('div.print_dialog').remove();
    }
  };
  
}(); // App.Wireframe.Print

/**
 * Benchmark interface
 */
App.Wireframe.Benchmark = function() {
  
  return {
    
    /**
     * Set benchmark information
     */
    'set' : function(data) {
      var item = $('#statusbar_item_developer_tools');
      
      if(item.length) {
        var elements = [];
        
        if(typeof(data) == 'object' && data) {
          if(typeof(data['execution_time'])) {
            elements.push(data['execution_time']);
          } // if
          
          if(typeof(data['memory_usage'])) {
            elements.push(data['memory_usage']);
          } // if
          
          if(typeof(data['queries_count'])) {
            elements.push(data['queries_count']);
          } // if
        } // if
        
        if(elements.length) {
          item.find('a').text(elements.join(' | '));
        } else {
          item.find('a').text(App.lang('Developer'));
        } // if
      } // if
    }
    
  };
  
}();

/**
 *  Navigation engine
 * 
 *  HISTORY API scenarios
 *
 *  API Supported:
 *    1. http://someurl.com/#projects : rewrite the url to http://someurl.com/projects and load content from that page
 *    2. http://someurl.com/projects#people: rewrite the browser url to http://someurl.com/people and load content from that url
 *    3. http://someurl.com/projects : do nothing and load the url as usual
 *
 *  API NOT Supported
 *    1. http://someurl.com/#projects : do not rewrite the url, but load data from http://someurl.com/projects
 *    2. http://someurl.com/projects#people: redirect browser to the url http://someurl.com/#people
 *    3. http://someurl.com/projects : redirect browser to http://someurl.com/#projects
 *
 *  ----------------------------------------------------------------------------------------------------------------------------------------        
 *
 *  TEST URLS - common urls
 *    - http://trunk.activecollab.dev/public/
 *    - http://trunk.activecollab.dev/public/index.php
 *    - http://trunk.activecollab.dev/public/index.php?path_info=
 *    - http://trunk.activecollab.dev/public/#index.php?path_info=admin
 *    - http://trunk.activecollab.dev/public/index.php?path_info=admin
 *    - http://trunk.activecollab.dev/public/index.php?path_info=projects#index.php?path_info=admin
 *          
 *  TEST URLS - 'pretty' urls
 *    - http://trunk.activecollab.dev/public/
 *    - http://trunk.activecollab.dev/public/#/admin
 *    - http://trunk.activecollab.dev/public/admin
 *    - http://trunk.activecollab.dev/public/projects#admin 
 *    
 */
App.Wireframe.Navigation = function () {
  
  // initial params storage
  var initial_params = null;
  var loaded_content = null;
  var loaded_url = null;
  
  var homepage_url;
  var url_base;
  
  /**
   *  Remove trailing slash
   *
   * @param String url   
   * @return url   
   */
  var remove_trailing_slash = function (url) {
    url = url + '';
    if (url[url.length - 1] == '/') {
      url = url.substring(0, url.length - 1);
    } // if
    return url;
  }; // skip_trailing_hash
  
  /**
   * Resolve url in browsers who support history api
   */
  var resolve_api_supported = function () {
    var current_hash = App.Wireframe.Navigation.getHash();
    if (current_hash) {
      App.Wireframe.Navigation.constructBackend(homepage_url + '/' + current_hash);
    } else {
      App.Wireframe.Navigation.constructBackend();
    } // if        
  }; // resolve_api_supported

  /**
   * Resolve urls in browsers who do not support history, and urls are common type
   */
  var resolve_api_disabled_common_urls = function () {
    var current_hash = App.Wireframe.Navigation.getHash();
    var search_string = App.Wireframe.Navigation.getSearchString();
    
    if (current_hash) {            
      if (!search_string) {
        App.Wireframe.Navigation.constructBackend(homepage_url + '/' + current_hash);  
      } else {
        App.Wireframe.Navigation.redirect(homepage_url + '/#' + current_hash);
      } // if
    } else {
      if (!search_string) {
        App.Wireframe.Navigation.constructBackend();
      } else {
        App.Wireframe.Navigation.redirect(homepage_url + '/#' + search_string);
      } // if  
    } // if        
  }; // resolve_api_disabled_common_urls

  /**
   * Resolve urls in browsers who do not support history, and urls are pretty
   */
  var resolve_api_disabled_pretty_urls = function () {
    var current_hash = App.Wireframe.Navigation.getHash();
    var search_string = App.Wireframe.Navigation.getSearchString();
        
    if (current_hash) {            
      if (!search_string) {
        App.Wireframe.Navigation.constructBackend(homepage_url + '/' + current_hash);  
      } else {
        App.Wireframe.Navigation.redirect(homepage_url + '/#' + current_hash);
      } // if
    } else {
      if (!search_string) {
        App.Wireframe.Navigation.constructBackend();
      } else {
        App.Wireframe.Navigation.redirect(homepage_url + '/#' + search_string);
      } // if  
    } // if        
  }; // resolve_api_disabled_pretty_urls
  
  // public interface
  return {
    /**
     * Initialize backend navigation
     * 
     * @param Array params
     * @returns null
     */
    'init' : function (params) {
      initial_params = params['init_params'];
      loaded_content = params['loaded_content'];
      loaded_url = params['loaded_url']; 
      
      homepage_url = remove_trailing_slash(App.Config.get('homepage_url'));
      url_base = remove_trailing_slash(App.Config.get('url_base'));
      
      if (App.Wireframe.Navigation.isHistoryApiSupported()) {
        resolve_api_supported();
      } else if (!App.Wireframe.Navigation.usingPrettyUrls()) {
        resolve_api_disabled_common_urls();
      } else {
        resolve_api_disabled_pretty_urls();
      }; // if
      
      App.Wireframe.Navigation.handleLinks();
    },
    
    /**
     * Returns current hash
     * 
     * @returns String
     */
    'getHash' : function () {
      // clean hash
      var current_hash = window.location.hash ? window.location.hash.substring(1) : '';
      if (current_hash.indexOf('?&_suid') != -1) {
        current_hash = current_hash.substring(0, current_hash.indexOf('?&_suid'));
      } else if (current_hash.indexOf('&_suid') != -1) {
        current_hash = current_hash.substring(0, current_hash.indexOf('&_suid'));
      } // if
      
      return current_hash;
    },
    
    /**
     * Returns search string
     * 
     * @returns string
     */
    'getSearchString' : function () {
      if (!App.Wireframe.Navigation.usingPrettyUrls()) {
        var search_string = window.location.search;
      } else {
        var current_request_url = App.Wireframe.Navigation.getCurrentRequestUrl();
        if (current_request_url.indexOf('#') != -1) {
          var without_hash = current_request_url.substring(0, current_request_url.indexOf('#'));
        } else {
          var without_hash = current_request_url;
        } // if
        
        var search_string = without_hash.substr(homepage_url.length);
        if (search_string == '/') {
          search_string = '';
        } // if
      } // if
      
      return search_string;
    },
    
    /**
     * Get current request url
     * 
     * @returns string
     */
    'getCurrentRequestUrl' : function () {
      return window.location.href;
    },
    
    /**
     * Checks if history api is supported
     * 
     * @returns Boolean
     */
    'isHistoryApiSupported' : function () {
      return window.history && Boolean(window.history.pushState) && typeof(window.history.pushState) == 'function';
    },
    
    /**
     * Check if we are using pretty urls
     * 
     * @returns Boolean
     */
    'usingPrettyUrls' : function () {
      return !App.Config.get('path_info_through_query_string');
    },
    
    /**
     * Construct backend
     * 
     * @param String url - optional url to load instead of current loaded content
     * @return null
     */
    'constructBackend' : function (url) {
      // initialize backend layout
      App.Wireframe.BackendLayout.init($.extend(initial_params, (url ? {'content_url' : url} : {})));

      // if url has been rewriten, skip the content setting
      if (!url) {
        App.Wireframe.Content.set(loaded_content, loaded_url);
      }; // if
    },
    
    /**
     * Redirect to new url
     * 
     * @param String url
     * @returns null
     */
    'redirect' : function (url) {
      window.location = url;
    },
    
    /**
     * Handle links on page
     * 
     * Handle body clicks and look for <a> elements clicked then execute open inline
     * command on it (optimization, so we don't need to bind clicks to every <a>
     * element)
     * 
     * @returns null
     */
    'handleLinks' : function () {
      $('body').delegate('a', 'click', function (event) {
        var link = $(this), open_inline = true;
        
        // Links that have target set to blank don't open inline
        if((link.attr('target') == '_blank') || (link.attr('target') == '_self')) {
          open_inline = false;
        } // if
        
        if(link.is('.notinline')) {
          open_inline = false;
        } // if
        
        var href = link.attr('href');
        
        if(href == '#') {
          return false;
        } // if
        
        // do not intercept tinymce buttons
        if (link.is('.mceButton')) {
          return true;
        } // if
        
        // Ignore external links
        var url_base = App.Config.get('url_base');
        
        if(href && url_base && href.substr(0, url_base.length) != url_base) {
          window.open(href);
          return false;
        } // if
        
        // If we already have click event, don't open inline
        var events = link.data('events');
        if(events && typeof(events) == 'object' && typeof(events['click']) == 'object') {
          open_inline = false;
        } // if
        
        if (open_inline) {
          if (!event['metaKey'] && !event['ctrlKey'] && !event['altKey']) {
            App.Wireframe.Content.setFromUrl(link.attr('href'));
            return false;
          } // if
        } // if
        
        return true;
      });
    }
  };
  
}(); // App.Wireframe.Navigation

/**
 * Open selected link inline
 *
 * @param Object settings
 */
jQuery.fn.openInline = function() {
  return this.click(function() {
    App.Wireframe.Content.setFromUrl($(this).attr('href'));
    return false;
  });
};


// initialize wireframe updater
$(window).load(function () {
  var wireframe_updates_url = App.Config.get('wireframe_updates_url');
  if(wireframe_updates_url) {
    App.Wireframe.Updates.start(wireframe_updates_url);
  } // if
});