text: /*
      This file is part of ethereum.js.
  
      ethereum.js is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      ethereum.js is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.
  
      You should have received a copy of the GNU Lesser General Public License
      along with ethereum.js.  If not, see <http://www.gnu.org/licenses/>.
  */
  /** @file filter.js
   * @authors:
   *   Jeffrey Wilcke <jeff@ethdev.com>
   *   Marek Kotewicz <marek@ethdev.com>
   *   Marian Oancea <marian@ethdev.com>
   *   Fabian Vogelsteller <fabian@ethdev.com>
   *   Gav Wood <g@ethdev.com>
   * @date 2014
   */
  
  var RequestManager = require('./requestmanager');
  var formatters = require('./formatters');
  var utils = require('../utils/utils');
  
  /**
  * Converts a given topic to a hex string, but also allows null values.
  *
  * @param {Mixed} value
  * @return {String}
  */
  var toTopic = function(value){
  
      if(value === null || typeof value === 'undefined')
          return null;
  
      value = String(value);
  
      if(value.indexOf('0x') === 0)
          return value;
      else
          return utils.fromAscii(value);
  };
  
  /// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones
  /// @param should be string or object
  /// @returns options string or object
  var getOptions = function (options) {
  
      if (utils.isString(options)) {
          return options;
      } 
  
      options = options || {};
  
      // make sure topics, get converted to hex
      options.topics = options.topics || [];
      options.topics = options.topics.map(function(topic){
          return (utils.isArray(topic)) ? topic.map(toTopic) : toTopic(topic);
      });
  
      // lazy load
      return {
          topics: options.topics,
          to: options.to,
          address: options.address,
          fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock),
          toBlock: formatters.inputBlockNumberFormatter(options.toBlock) 
      }; 
  };
  
  /**
  Adds the callback and sets up the methods, to iterate over the results.
  
  @method getLogsAtStart
  @param {Object} self
  @param {funciton} 
  */
  var getLogsAtStart = function(self, callback){
      // call getFilterLogs for the first watch callback start
      if (!utils.isString(self.options)) {
          self.get(function (err, messages) {
              // don't send all the responses to all the watches again... just to self one
              if (err) {
                  callback(err);
              }
  
              messages.forEach(function (message) {
                  callback(null, message);
              });
          });
      }
  };
  
  /**
  Adds the callback and sets up the methods, to iterate over the results.
  
  @method pollFilter
  @param {Object} self
  */
  var pollFilter = function(self) {
  
      var onMessage = function (error, messages) {
          if (error) {
              return self.callbacks.forEach(function (callback) {
                  callback(error);
              });
          }
  
          messages.forEach(function (message) {
              message = self.formatter ? self.formatter(message) : message;
              self.callbacks.forEach(function (callback) {
                  callback(null, message);
              });
          });
      };
  
      RequestManager.getInstance().startPolling({
          method: self.implementation.poll.call,
          params: [self.filterId],
      }, self.filterId, onMessage, self.stopWatching.bind(self));
  
  };
  
  var Filter = function (options, methods, formatter, callback) {
      var self = this;
      var implementation = {};
      methods.forEach(function (method) {
          method.attachToObject(implementation);
      });
      this.options = getOptions(options);
      this.implementation = implementation;
      this.filterId = null;
      this.callbacks = [];
      this.pollFilters = [];
      this.formatter = formatter;
      this.implementation.newFilter(this.options, function(error, id){
          if(error) {
              self.callbacks.forEach(function(cb){
                  cb(error);
              });
          } else {
              self.filterId = id;
  
              // get filter logs for the already existing watch calls
              self.callbacks.forEach(function(cb){
                  getLogsAtStart(self, cb);
              });
              if(self.callbacks.length > 0)
                  pollFilter(self);
  
              // start to watch immediately
              if(callback) {
                  return self.watch(callback);
              }
          }
      });
  
  };
  
  Filter.prototype.watch = function (callback) {
      this.callbacks.push(callback);
  
      if(this.filterId) {
          getLogsAtStart(this, callback);
          pollFilter(this);
      }
  
      return this;
  };
  
  Filter.prototype.stopWatching = function () {
      RequestManager.getInstance().stopPolling(this.filterId);
      // remove filter async
      this.implementation.uninstallFilter(this.filterId, function(){});
      this.callbacks = [];
  };
  
  Filter.prototype.get = function (callback) {
      var self = this;
      if (utils.isFunction(callback)) {
          this.implementation.getLogs(this.filterId, function(err, res){
              if (err) {
                  callback(err);
              } else {
                  callback(null, res.map(function (log) {
                      return self.formatter ? self.formatter(log) : log;
                  }));
              }
          });
      } else {
          var logs = this.implementation.getLogs(this.filterId);
          return logs.map(function (log) {
              return self.formatter ? self.formatter(log) : log;
          });
      }
  
      return this;
  };
  
  module.exports = Filter;
  
  