// helper for manipulate hash-info

function WindowHashSettings(options){
  var options = options;
  if( ! jQuery ) {
    throw new Error( 'jQuery is required' );
  }
  if( ! jQuery.fn.hashchange || typeof( jQuery.fn.hashchange ) != "function" ) {
    throw new Error( 'jQuery.fn.hashchange is required' );
  }
  this.param_delimiter = options.param_delimiter || '/';
  this.value_delimiter = options.value_delimiter || ':';
  this.debug = options.debug || false;
  //
  var __log_counter = 0;
  this.log = function(){
    if (this.debug && window.console && typeof console.log === "function" ) {
      __log_counter = ++__log_counter % 1024;
      var args = [];
      for( i = 0, l = arguments.length; i < l; i++ ) {
        args.push( arguments[i] );
      }
      args.unshift( '[' + __log_counter + ']' );
      console.debug.apply( console, args);
    }
  };
  //
  this._load_callbacks = [];
  this._params = [];
  //
  this.add = function(param,values,value,callback){
    this.log( 'add', arguments );
    if( ! param ) {
      throw new Error( 'param name is required' );
    }
    if( ! ( values != null && values.length ) ) {
      throw new Error( 'values are required' );
    }
    this._params.push(
      {
        name: param,
        values: values,
        value: value != null ? value : values[0],
        'default': value != null ? value : values[0],
        callback: callback
      }
    );
  };
  this.remove = function(param){
    this.log( 'remove', arguments );
    if( ! param ) {
      throw new Error( 'param name is required' );
    }
    var params =[];
    for( var i = 0, l = this._params.length; i < l; i++ ) {
      if( this._params[i]['name'] != param ) {
        params.push( this._params[i] );
      }
    }
    this._params = params;
    return true;
  };
  this.get_param = function(param){
    if( param != null ) {
      for( var i = 0, l = this._params.length; i < l; i++ ) {
        if( this._params[i]['name'] == param ) {
          return this._params[i];
        }
      }
    }
    return null;
  };
  this.set = function(){
    this.log( 'set', arguments );
    if( arguments.length > 1 && ( ! ( arguments.length % 2 ) ) ) {
      for( var i = 0, l = arguments.length / 2; i < l; i++ ) {
        var param = arguments[i*2];
        var value = arguments[i*2+1];
        var param_info = this.get_param(param);
        if( param_info != null ) {
          if( value != null ) {
            for( var j = 0, k = param_info['values'].length; j < k; j++ ) {
              if( param_info['values'][j] == value ) {
                param_info['value'] = value;
                break;
              }
              else {
                // default value
                param_info['value'] = param_info['default'] != null ? param_info['default'] : param_info['values'][0];
              }
            }
          } else {
            // default value
            param_info['value'] = param_info['default'] != null ? param_info['default'] : param_info['values'][0];
          }
        }
      }
      return true;
    }
    return false;
  };
  this.get = function(param){
    this.log( 'get', arguments );
    var param_info = this.get_param(param);
    if( param_info != null ) {
      return param_info['value'];
    }
    return null;
  };
  this.callback = function(param){
    this.log( 'callback', arguments );
    var param_info = this.get_param(param);
    if( param_info != null ) {
      return param_info['callback'];
    }
    return null;
  };
  this.set_default = function(param,value){
    this.log( 'set_default', arguments );
    var param_info = this.get_param(param);
    var old_value = null;
    if( param_info != null ) {
      old_value = param_info['default'];
      param_info['default'] = value;
    }
    return old_value;
  };
  this.parse = function(hash_string){
    this.log( 'parse', arguments );
    var hash_string = hash_string;
    var params = {};
    if( hash_string != null && hash_string.length > 1 ) {
      if( /^#/.test( hash_string ) ) {
        hash_string = hash_string.substring(1);
      }
      if( hash_string.length > 1 ) {
        var params_pairs = hash_string.split( this.param_delimiter );
        if( params_pairs != null ) {
          for( var i = 0, l = params_pairs.length; i < l; i++ ) {
            var param_name_value = params_pairs[i].split( this.value_delimiter );
            params[ param_name_value[0] ] = param_name_value[1];
          }
        }
      }
    }
    return params;
  };
  this.add_load_callback = function(callback){
    this._load_callbacks.push( callback );
  };
  this.remove_load_callback = function(callback){
    for( var i = 0, l = this._load_callbacks.length; i < l; i++ ) {
      if( this._load_callbacks[i] == callback ) {
        this._load_callbacks[i] = null; // FIXME: currently removed callbacks are nullabled only
      }
    }
  };
  this.load = function(){
    this.log( 'load called' );
    var params = this.parse( window.location.hash );
    var all_params = {};
    var need_call_load_callbacks = false;
    for( var i = 0, l = this._params.length; i < l; i++ ) {
      var param_info = this._params[i];
      var param_name = param_info['name'];
      var old_value = param_info['value'];
      var new_value = params[param_name];
      this.log( 'param: ',param_name,', old_value: ',old_value,', new_value: ',new_value );
      if( old_value != new_value ) {
        need_call_load_callbacks = true;
        this.set( param_name, new_value );
        var callback = this.callback(param_name);
        if( callback ) {
          this.log( 'try call callback' );
          try{
            callback( old_value, this.get(param_name) ); // NB: don't optimize it, this.set and this.get is need both
          }catch(e){
            this.log( 'callback catched: ', e );
          }
        }
      }
      all_params[ param_name ] = {};
      all_params[ param_name ]['old_value'] = old_value;
      all_params[ param_name ]['new_value'] = this.get(param_name); // NB: don't optimize it, this.set and this.get is need both
    }
    if( need_call_load_callbacks ) {
      this.log( 'need call load_callbacks' );
      for( var i = 0, l = this._load_callbacks.length; i < l; i++ ) {
        var callback = this._load_callbacks[i];
        if( callback ) {
          this.log( 'try call load_callback' );
          try{
            callback( all_params );
          }catch(e){
            this.log( 'load callback catched: ', e );
          }
        }
      }
    }
  };
  this._make_hash = function(){
    this.log( '_make_hash', arguments );
    var hash = [];
    for( var i = 0, l = this._params.length; i < l; i++ ) {
      var param_info = this._params[i];
      var param_name = param_info['name'];
      var default_value = param_info['default'] != null ? param_info['default'] : param_info['values'][0];
      var value = param_info['value'];
      if( default_value != value ) {
        hash.push( param_name + this.value_delimiter + value );
      }
    }
    return hash;
  };
  this.hash = function(){
    this.log( 'hash', arguments );
    var hash = this._make_hash();
    var hash_string = '';
    if( hash != null && hash.length > 0 ) {
      hash_string = '#' + hash.join( this.param_delimiter )
    }
    this.log( 'hash is ' + hash_string );
    return hash_string;
  };
  this.commit = function(){
    this.log( 'commit', arguments );
    if( arguments.length ) {
      this.set.apply( this, arguments );
    }
    var new_hash = this.hash();
    if( String( window.location.hash ) != String( new_hash ) ) {
      window.location.hash = this.hash() || '#none';
    }
  };
};
