/*******************************************************************************
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use that file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
'use strict';
var Probe = require('../lib/probe.js');
var aspect = require('../lib/aspect.js');
var request = require('../lib/request.js');
var util = require('util');
var am = require('../');

function MongoProbe() {
  Probe.call(this, 'mongodb');
}
util.inherits(MongoProbe, Probe);

MongoProbe.prototype.aspectCollectionMethod = function(coll, method) {
  var that = this;
  aspect.around(
    coll,
    method,
    function(target, methodName, methodArgs, probeData) {
      var collectionName = target.collectionName;

      that.metricsProbeStart(probeData, target, method, methodArgs);
      that.requestProbeStart(probeData, target, method, methodArgs);
      if (aspect.findCallbackArg(methodArgs) != undefined) {
        aspect.aroundCallback(methodArgs, probeData, function(target, args, probeData) {

          var count;

          if (args && args.length > 1) {
            var res = args[1];
            if (res) {
              if (res.hasOwnProperty('matchedCount')) {
                count = res.matchedCount;
              } else if (res.hasOwnProperty('modifiedCount')) {
                count = res.modifiedCount;
              } else if (res.hasOwnProperty('insertedCount')) {
                count = res.insertedCount;
              } else if (res.hasOwnProperty('upsertedCount')) {
                count = res.upsertedCount;
              } else if (res.hasOwnProperty('deletedCount')) {
                count = res.deletedCount;
              } else if (res.hasOwnProperty('length')) {
                count = res.length;
              } else if (typeof res === 'number') {
                count = res;
              }
              if (methodName === 'bulkWrite') {
                count = res.modifiedCount + res.insertedCount + res.deletedCount + res.upsertedCount;
              }
            }
          }

          that.metricsProbeEnd(probeData, collectionName, method, methodArgs, count);
          that.requestProbeEnd(probeData, method, methodArgs);
        });
      }
    },
    function(target, methodName, methodArgs, probeData, rc) {
      var collectionName = target.collectionName;

      if (aspect.findCallbackArg(methodArgs) == undefined) {
        that.metricsProbeEnd(probeData, collectionName, method, methodArgs);
        that.requestProbeEnd(probeData, method, methodArgs);
      }
      return rc;
    }
  );
};

MongoProbe.prototype.attach = function(name, target) {
  var that = this;
  if (name != 'mongodb') return target;
  if (target.__ddProbeAttached__) return target;
  target.__ddProbeAttached__ = true;

  var coll = target['Collection'].prototype;
  var method = 'find';
  aspect.around(
    coll,
    method,
    function(target, methodName, methodArgs, probeData) {
      that.metricsProbeStart(probeData, target, method, methodArgs);
      that.requestProbeStart(probeData, target, method, methodArgs);
    },
    function(target, methodName, findArgs, probeData, rc) {
      var collectionName = target.collectionName;

      if (rc == undefined) {
        that.metricsProbeEnd(probeData, collectionName, method, findArgs);
        that.requestProbeEnd(probeData, method, findArgs);
      } else {
        aspect.before(rc, 'toArray', function(target, methodName, args, context) {
          aspect.aroundCallback(args, probeData, function(target, args, probeData) {
            var count;

            if (args && args.length > 1) {
              var res = args[1];
              if (res && res.hasOwnProperty('length')) {
                count = res.length;
              }
            }
            that.metricsProbeEnd(probeData, collectionName, method, findArgs, count);
            that.requestProbeEnd(probeData, method, findArgs);
          });
        });
      }
      return rc;
    }
  );

  that.aspectCollectionMethod(coll, 'aggregate');
  that.aspectCollectionMethod(coll, 'bulkWrite');
  that.aspectCollectionMethod(coll, 'count');
  that.aspectCollectionMethod(coll, 'createIndex');
  that.aspectCollectionMethod(coll, 'createIndexes');
  that.aspectCollectionMethod(coll, 'deleteMany');
  that.aspectCollectionMethod(coll, 'deleteOne');
  that.aspectCollectionMethod(coll, 'distinct');
  that.aspectCollectionMethod(coll, 'drop');
  that.aspectCollectionMethod(coll, 'dropIndex');
  that.aspectCollectionMethod(coll, 'dropIndexes');
  that.aspectCollectionMethod(coll, 'findOne');
  that.aspectCollectionMethod(coll, 'findOneAndDelete');
  that.aspectCollectionMethod(coll, 'findOneAndReplace');
  that.aspectCollectionMethod(coll, 'findOneAndUpdate');
  that.aspectCollectionMethod(coll, 'geoHaystackSearch');
  that.aspectCollectionMethod(coll, 'geoNear');
  that.aspectCollectionMethod(coll, 'group');
  that.aspectCollectionMethod(coll, 'indexes');
  that.aspectCollectionMethod(coll, 'indexExists');
  that.aspectCollectionMethod(coll, 'indexInformation');
  that.aspectCollectionMethod(coll, 'insertMany');
  that.aspectCollectionMethod(coll, 'insertOne');
  that.aspectCollectionMethod(coll, 'mapReduce');
  that.aspectCollectionMethod(coll, 'reIndex');
  that.aspectCollectionMethod(coll, 'rename');
  that.aspectCollectionMethod(coll, 'replaceOne');
  that.aspectCollectionMethod(coll, 'updateMany');
  that.aspectCollectionMethod(coll, 'updateOne');

  return target;
};

/*
 * Lightweight metrics probe for MongoDB queries
 *
 * These provide:
 *         time:        time event started
 *         query:        the query itself
 *         duration:    the time for the request to respond
 *         method:      the executed method for the query, such as find, update
 *         collection:  the mongo collection
 */
MongoProbe.prototype.metricsEnd = function(probeData, collectionName, method, methodArgs, count) {
  if (probeData && probeData.timer) {
    probeData.timer.stop();
    am.emit('mongo', {
      time: probeData.timer.startTimeMillis,
      query: JSON.stringify(methodArgs[0]),
      duration: probeData.timer.timeDelta,
      method: method,
      collection: collectionName,
      count: count,
    });
  }
};

/*
 * Heavyweight request probes for MongoDB queries
 */
MongoProbe.prototype.requestStart = function(probeData, target, method, methodArgs) {
  probeData.req = request.startRequest('mongo', method + '(' + target.collectionName + ')', false, probeData.timer);
};

MongoProbe.prototype.requestEnd = function(probeData, method, methodArgs) {
  if (probeData && probeData.req) probeData.req.stop({ query: JSON.stringify(methodArgs[0]) });
};

module.exports = MongoProbe;
