function klApplication() {

  var this_proxy = this;

  /* private */
  function __construct()
  {
    initSubmitionCallBackForm();
    initCartFunctions();

    initRoundCorners();
    initNM();
    initNavigation();
    initCSSFixes();
  }

  function getIndexFromId(str, separator)
  {
    if(!separator)
      var separator = '_';

    var tmp = str.split(separator);
    var num = tmp[tmp.length - 1];

    return num;
  }

  function showMessage(message)
  {
    alert(message);
  }

  function initNM()
  {
    jQuery('a.nyroModal').nyroModal({zIndexStart: 999});
  }

  function initCSSFixes()
  {
    var h1 = jQuery('#content_column1_container').height();
    var h3 = jQuery('#content_column3_container').height();

    if(h1 > h3)
    {
      jQuery('#content_column3_container').css('min-height', h1);
    }
    else
    {
      jQuery('#content_column1_container').css('min-height', h3 - 55);
    }
  }

  function initRoundCorners()
  {
    jQuery('#content_column1_container, #content_column3_container .pad3').corner("11px");
  }

  function initNavigation()
  {
    jQuery('#lnav_catalog ul').hide();
    jQuery('#lnav_catalog li a').not('#lnav_catalog ul li a').click(function(){
      jQuery(this).parents('li').find('ul').slideToggle();
      return false;
    });
  }

  function initSubmitionCallBackForm()
  {
    jQuery('#callback_form').submit(function(){
      var phone = jQuery('#phone').val();
      var time = jQuery('#time').val();
      return loadContent('#' + this.id + '_container', this.action, { phone: phone, time: time }, null, this.method);
    });
  }

  function initCartFunctions()
  {
    jQuery('.remove_from_cart').click(function(){
      var id = jQuery(this).attr('rel');
      jQuery.ajax({
        url: this.href,
        type: 'GET',
        data: { type: 'ajax', back_url: window.location.href },
        dataType: 'json',
        success: function(data)
        {
          if(data.status == 'success')
            jQuery('#'+id).remove();

          if(data.total_amount == 0)
            jQuery('#cart_product_list').html('<p>Ваша корзина пуста.</p>');

          if(data.message)
            showMessage(data.message);
        }
      });
      return false;
    });

    jQuery('.add_to_cart_form').submit(function(){
      var id = jQuery(this).find('[name=id]').val();
      var amount = jQuery(this).find('[name=amount]').val();
      jQuery.ajax({
        url: this.action,
        type: 'POST',
        data: { type: 'ajax', id: id, amount: amount, back_url: window.location.href },
        dataType: 'json',
        success: function(data)
        {
          if(data.message)
            showMessage(data.message);
        }
      });
      return false;
    });
  }

  function initDragAndDrop()
  {
    if( typeof DropTarget == 'undefined' )
      return;

    jQuery('#product-list .refItem').click(function(){ return false; });
    var dragObjects = jQuery('#product-list .refItem img');
    for(var i=0; i<dragObjects.length; i++) {
        new DragObject(dragObjects[i]);
    }

    new DropTarget(document.getElementById('product_block_1'));
    new DropTarget(document.getElementById('product_block_2'));
  }

  function initGallery()
  {
    var galleries = $('.ad-gallery').adGallery({
      loader_image: 'images/loader.gif',
      width: 540, // Width of the image, set to false and it will read the CSS width
      height: 400, // Height of the image, set to false and it will read the CSS height
      thumb_opacity: 0.7, // Opacity that the thumbs fades to/from, (1 removes fade effect)
                          // Note that this effect combined with other effects might be resource intensive
                          // and make animations lag
      start_at_index: 0, // Which image should be displayed at first? 0 is the first image
      description_wrapper: $('#descriptions'), // Either false or a jQuery object, if you want the image descriptions
                                               // to be placed somewhere else than on top of the image
      animate_first_image: false, // Should first image just be displayed, or animated in?
      animation_speed: 400, // Which ever effect is used to switch images, how long should it take?
      display_next_and_prev: true, // Can you navigate by clicking on the left/right on the image?
      display_back_and_forward: true, // Are you allowed to scroll the thumb list?
      scroll_jump: 0, // If 0, it jumps the width of the container
      effect: 'slide-hori', // or 'slide-vert', 'resize', 'fade', 'none' or false
      enable_keyboard_move: true, // Move to next/previous image with keyboard arrows?
      // All callbacks has the AdGallery objects as 'this' reference
      callbacks: {
        // Executes right after the internal init, can be used to choose which images
        // you want to preload
        init: function() {
          // preloadAll uses recursion to preload each image right after one another
          this.preloadAll();
          // Or, just preload the first three
          this.preloadImage(0);
          this.preloadImage(1);
          this.preloadImage(2);
        }
      }
    });
  }

  /* public */
  klApplication.prototype.loadContent = function(elId, url, data, success, method, verbose)
  {
    if(window.submition_lock == true)
      return false;

    var options = {
      type: method || "POST",
      url: url,
      data: data || '',
      dataType: 'html',
      error: function(XMLHttpRequest, textStatus, errorThrown)
      {
        window.submition_lock = false;

        //unlockPage();

        if( XMLHttpRequest.status == 404 )
        {
          if(elId && data)
            jQuery(elId).html(XMLHttpRequest.responseText);
        }
        else
        {
          if(verbose)
            showMessage('Запрос не может быть вополнен. Попробуйте позже или обратитесь к администратору сайта.');
        }
      },
      success: function(data, textStatus)
      {
        window.submition_lock = false;

        //unlockPage();

        if(elId && data)
          jQuery(elId).html(data);

        if(success)
          success(data, textStatus);
      }
    };

    window.submition_lock = true;

    //lockPage();

    jQuery.ajax(options);

    return false;
  }

  klApplication.prototype.loadProductInfo = function(drop_target, drop_object)
  {
    var panel_id = getIndexFromId( drop_target.toString() );
    var product_id = getIndexFromId( drop_object.toString() );
    this_proxy.loadContent( '#' + drop_target.toString(),
                            '/catalog/product_info_ajax',
                            { id: product_id, panel_id: panel_id },
                            function() {
                              initNM();
                              initCartFunctions();
                            }
                          );
  }

  klApplication.prototype.buildUrl = function(url, rand, parameters)
  {
    if(rand)
    {
      var indexOfSign = url.indexOf("?");
      if( indexOfSign > 0 ) { url += '&'; } else { url += '?'; }

      url += Math.random();
    }

    if(parameters)
      url = appendParamsToUrl(url, parameters);
    else if(api)
      url = appendParamsToUrl(url, api.params);

    return url;
  }

  klApplication.prototype.setURIAnchor = function(url)
  {
    var parsed_url = this_proxy.parseUrl(url);
    var hash_url = '#' + parsed_url.path.substring(1);
    if(parsed_url.query)
      hash_url += '?' + parsed_url.query;

    window.location.hash = hash_url;
  }

  klApplication.prototype.parseUrl = function(str, component)
  {
    var  o   = {
        strictMode: false,
        key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
        q:   {
            name:   "queryKey",
            parser: /(?:^|&)([^&=]*)=?([^&]*)/g
        },
        parser: {
            strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
            loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // Added one optional slash to post-protocol to catch file:/// (should restrict this)
        }
    };

    var m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
    uri = {},
    i   = 14;
    while (i--) {uri[o.key[i]] = m[i] || "";}
    // Uncomment the following to use the original more detailed (non-PHP) script
    /*
        uri[o.q.name] = {};
        uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
        if ($1) uri[o.q.name][$1] = $2;
        });
        return uri;
    */

    switch (component) {
        case 'PHP_URL_SCHEME':
            return uri.protocol;
        case 'PHP_URL_HOST':
            return uri.host;
        case 'PHP_URL_PORT':
            return uri.port;
        case 'PHP_URL_USER':
            return uri.user;
        case 'PHP_URL_PASS':
            return uri.password;
        case 'PHP_URL_PATH':
            return uri.path;
        case 'PHP_URL_QUERY':
            return uri.query;
        case 'PHP_URL_FRAGMENT':
            return uri.anchor;
        default:
            var retArr = {};
            if (uri.protocol !== '') {retArr.scheme=uri.protocol;}
            if (uri.host !== '') {retArr.host=uri.host;}
            if (uri.port !== '') {retArr.port=uri.port;}
            if (uri.user !== '') {retArr.user=uri.user;}
            if (uri.password !== '') {retArr.pass=uri.password;}
            if (uri.path !== '') {retArr.path=uri.path;}
            if (uri.query !== '') {retArr.query=uri.query;}
            if (uri.anchor !== '') {retArr.fragment=uri.anchor;}
            return retArr;
    }
  }

  /* */
  __construct();
};


/* */
function initApp()
{
  klApp = new klApplication();
}

