var __screenCapturePageContext__ = {
  clone: function(object) {
    function StubObj() { }
    StubObj.prototype = object;
    var newObj = new StubObj();
    newObj.getInternalObject = function() {
      return this.__proto__;
    }
    newObj.toString = function() {
      try {
        return this.__proto__.toString();
      } catch (e) {
        return 'object Object';
      }
    }
    return newObj;
  },

  bind: function(newThis, func) {
    var args = [];
    for(var i = 2;i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    return function() {
      return func.apply(newThis, args);
    }
  },

  bodyWrapperDelegate_: null,
  currentHookStatus_: false,

  scrollValueHooker: function(oldValue, newValue, reason) {
    // When we hook the value of scrollLeft/Top of body, it always returns 0.
    return 0;
  },

  toggleBodyScrollValueHookStatus: function() {
    this.currentHookStatus_ = !this.currentHookStatus_;
    if (this.currentHookStatus_) {
      var This = this;
      try {
        document.__defineGetter__('body', function() {
          return This.bodyWrapperDelegate_.getWrapper();
        });
      } catch (e) {
        window.console.log('error' + e);
      }
      this.bodyWrapperDelegate_.watch('scrollLeft', this.scrollValueHooker);
      this.bodyWrapperDelegate_.watch('scrollTop', this.scrollValueHooker);
    } else {
      this.bodyWrapperDelegate_.unwatch('scrollLeft', this.scrollValueHooker);
      this.bodyWrapperDelegate_.unwatch('scrollTop', this.scrollValueHooker);
      var This = this;
      try {
        document.__defineGetter__('body', function() {
          return This.bodyWrapperDelegate_.getWrapper().getInternalObject();
        });
      } catch (e) {
        window.console.log('error' + e);
      }
    }
  },

  checkHookStatus: function() {
    var needHookScrollValue = document.documentElement.getAttributeNode(
        '__screen_capture_need_hook_scroll_value__');
    needHookScrollValue =
        !!(needHookScrollValue && needHookScrollValue.nodeValue == 'true');
    if (this.currentHookStatus_ != needHookScrollValue)
      this.toggleBodyScrollValueHookStatus();
  },

  init: function() {
    if (!this.bodyWrapperDelegate_) {
      this.bodyWrapperDelegate_ =
          new __screenCapturePageContext__.ObjectWrapDelegate(
              document.body, '^(DOCUMENT_[A-Z_]+|[A-Z_]+_NODE)$');
      document.documentElement.addEventListener(
          '__screen_capture_check_hook_status_event__',
          __screenCapturePageContext__.bind(this, this.checkHookStatus));
    }
  }
};

// ObjectWrapDelegate class will create a empty object(wrapper), map its
// prototype to the 'originalObject', then search all non-function properties
// (except those properties which match the propertyNameFilter) of the
// 'orginalObject' and set corresponding getter/setter to the wrapper.
// Then you can manipulate the wrapper as 'originalObject' because the wrapper
// use the corresponding getter/setter to access the corresponding properties in
// 'originalObject' and the all function calls can be call through the prototype
// inherit.
// After createing the wrapper object, you can use watch method to monitor any
// property which you want to know when it has been read(get) or change(set).
// Please see the detail comment on method watch.
// Remember the ObjectWrapDelegate returns the wrapDelegateObject instead of
// really wrapper object. You have to use ObjectWrapDelegate.getWrapper to get
// real wrapper object.
// parameter @originalObject, object which you want to wrap
// parameter @propertyNameFilter, string, regular expression pattern string for
//    those properties you don't put in the wrap object.
__screenCapturePageContext__.ObjectWrapDelegate = function(
    originalObject, propertyNameFilter) {
  this.window_ = window;

  // The wrapper is the object we use to wrap the 'originalObject'.
  this.wrapper_ = __screenCapturePageContext__.clone(originalObject);

  // This array saves all properties we set our getter/setter for them.
  this.properties_ = [];

  // This object to save all watch handlers. Each watch handler is bind to one
  // certain property which is in properties_.
  this.watcherTable_ = {};

  // Check the propertyNameFilter parameter.
  if (typeof propertyNameFilter == 'undefined') {
    propertyNameFilter = '';
  } else if (typeof propertyNameFilter != 'string') {
    try {
      propertyNameFilter = propertyNameFilter.toString();
    } catch (e) {
      propertyNameFilter = '';
    }
  }
  if (propertyNameFilter.length) {
    this.propertyNameFilter_ = new RegExp('');
    this.propertyNameFilter_.compile(propertyNameFilter);
  } else {
    this.propertyNameFilter_ = null;
  }

  // For closure to access the private data of class.
  var This = this;

  // Set the getter object.
  function setGetterAndSetter(wrapper, propertyName) {
    wrapper.__defineGetter__(propertyName, function() {
      var internalObj = this.getInternalObject();
      var originalReturnValue = internalObj[propertyName];
      var returnValue = originalReturnValue;

      // See whether this property has been watched.
      var watchers = This.watcherTable_[propertyName];
      if (watchers) {
        // copy the watcher to a cache in case someone call unwatch inside the
        // watchHandler.
        var watchersCache = watchers.concat();
        for (var i = 0, l = watchersCache.length; i < l; ++i) {
          var watcher = watchersCache[i];
          if (!watcher) {
            window.console.log('wrapper\'s watch for ' + propertyName +
                              ' is unavailable!');
            continue;  // should never happend
          }
          originalReturnValue = returnValue;
          try {
            returnValue = watcher(returnValue, returnValue, 'get');
          } catch (e) {
            returnValue = originalReturnValue;
          }
        }
      }
      return returnValue;
    });

    // Set the setter object.
    wrapper.__defineSetter__(propertyName, function(value) {
      var internalObj = this.getInternalObject();
      var originalValue = value;
      var userValue = originalValue;
      var oldValue;
      try {
        oldValue = internalObj[propertyName];
      } catch (e) {
        oldValue = null;
      }

      // See whether this property has been watched.
      var watchers = This.watcherTable_[propertyName];
      if (watchers) {
        // copy the watcher to a cache in case someone call unwatch inside the
        // watchHandler.
        var watchersCache = watchers.concat();
        for (var i = 0, l = watchersCache.length; i < l; ++i) {
          var watcher = watchersCache[i];
          if (!watcher) {
            window.console.log('wrapper\'s watch for ' + propertyName +
                              ' is unavailable!');
            continue;  // should never happend
          }
          originalValue = userValue;
          try {
            userValue = watcher(oldValue, userValue, 'set');
          } catch (e) {
            userValue = originalValue;
          }
        }
      }
      internalObj[propertyName] = userValue;
    });
  };

  this.cleanUp_ = function() {
    This.window_.removeEventListener('unload', This.cleanUp_, false);

    // Delete all properties
    for (var i = 0, l = This.properties_.length; i < l; ++i) {
      delete This.wrapper_[This.properties_[i]];
    }
    This.window_ = null;
    This.wrapper_ = null;
    This.properties_ = null;
    This.watcherTable_ = null;
    This.propertyNameFilter_ = null;
    This = null;
  }

  // We only bridge the non-function properties.
  for (var prop in originalObject) {
    if (this.propertyNameFilter_ && this.propertyNameFilter_.test(prop)) {
      this.propertyNameFilter_.test('');
      continue;
    }
    if (typeof originalObject[prop] != 'function') {
      this.properties_.push(prop);
      setGetterAndSetter(this.wrapper_, prop);
    }
  }

  // Listen the unload event.
  this.window_.addEventListener('unload', this.cleanUp_, false);
};

__screenCapturePageContext__.ObjectWrapDelegate.prototype.getWrapper =
    function() {
  return this.wrapper_;
}

// Check whether a property is in the wrapper or not. If yes, return true.
// Otherwise return false.
__screenCapturePageContext__.ObjectWrapDelegate.prototype.hasProperty =
    function(propertyName) {
  for (var i = 0, l = this.properties_.length; i < l; ++i) {
    if (propertyName == this.properties_[i])
      return true;
  }
  return false;
}

// Watches for a property to be accessed or be assigned a value and runs a
// function when that occurs.
// Watches for accessing a property or assignment to a property named prop in
// this object, calling handler(oldval, newval, reason) whenever prop is
// get/set and storing the return value in that property.
// A watchpoint can filter (or nullify) the value assignment, by returning a
// modified newval (or by returning oldval).
// When watchpoint is trigering by get opeartor, the oldval is equal with
// newval. The reason will be 'get'.
// When watchpoint is trigering by set opeartor, The reason will be 'set'.
// If you delete a property for which a watchpoint has been set,
// that watchpoint does not disappear. If you later recreate the property,
// the watchpoint is still in effect.
// To remove a watchpoint, use the unwatch method.
// If register the watchpoint successfully, return true. Otherwise return false.
__screenCapturePageContext__.ObjectWrapDelegate.prototype.watch = function(
    propertyName, watchHandler) {
  if (!this.hasProperty(propertyName))
    return false;
  var watchers = this.watcherTable_[propertyName];
  if (watchers) {
    for (var i = 0, l = watchers.length; i < l; ++i) {
      if (watchHandler == watchers[i])
        return true;
    }
  } else {
    watchers = new Array();
    this.watcherTable_[propertyName] = watchers;
  }
  watchers.push(watchHandler);
  return true;
}

// Removes a watchpoint set with the watch method.
__screenCapturePageContext__.ObjectWrapDelegate.prototype.unwatch = function(
    propertyName, watchHandler) {
  if (!this.hasProperty(propertyName))
    return false;
  var watchers = this.watcherTable_[propertyName];
  if (watchers) {
    for (var i = 0, l = watchers.length; i < l; ++i) {
      if (watchHandler == watchers[i]) {
        watchers.splice(i, 1);
        return true;
      }
    }
  }
  return false;
}
__screenCapturePageContext__.init();
