

var crypto = require('crypto');
var fs = require('fs');
var shttp = require('socks5-http-client');
var shttps = require('socks5-https-client');
var http = require('http');
var https = require('https');

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;

var querystring = require('querystring');



function construct(constructor, args) {
	function F() {
		return constructor.apply(this, args);
	}
	F.prototype = constructor.prototype;
	return new F();
}
exports.paginatedAggregate = function(aggregate){
	return {
		count: function(options, cb){
			aggregate
				.group({
					_id: null,
					count : {$sum:1}
				})
				.exec(function(err, result){
					if(result && result.length > 0){
						cb(err, result[0].count);
					}
				});
		},
		step: function(options, cb){
			options.perPage = options.perPage || 500;
			options.page = options.page || 0
			aggregate
				.skip(options.perPage * options.page)
				.limit(options.perPage)
				.exec(cb)
		}
	}
};
exports.sortByNamePredicate =  function(a,b){
	if(a.score == b.score){
		var A = a.name.toLowerCase();
		var B = b.name.toLowerCase();
		if (A < B){
			return -1;
		}else if (A > B){
			return  1;
		}else{
			return 0;
		}
	}else{
		if (a.score < b.score){
			return 1;
		}else if (a.score > b.score){
			return  -1;
		}else{
			return 0;
		}
	}
};
exports.crushTRChars = function(name){

	var trChars = {
		"ü":"u", "i":"i", "ı":"i","ö":"o","ç":"c","ş":"s","ğ":"g",
		"Ü":"U", "İ":"I", "I":"I","Ö":"O","Ç":"C","Ş":"S","Ğ":"G"};
	//if(!cache[bk1._id] && bk1.name){
	return name.allReplace(trChars).toLowerCase().replace(/\s+/g, " ").trim();
}
 
exports.crushTRCharsUrlData = function (data){
	var forbiddenWords = {
		"ü":"u", "i":"i", "ı":"i","ö":"o","ç":"c","ş":"s","ğ":"g",
		"Ü":"U", "İ":"I", "I":"I","Ö":"O","Ç":"C","Ş":"S","Ğ":"G",
		"<>":" ","{":" ","}":" ", ".morecontent": "",  "display: block": " ",
		":":"", ".morelink": "", "\\t": " ", "span": " "
	};
	var keys = Object.keys(data)  
	for(var index in keys){
		var key = keys[index]
		var val = data[key];
		if (typeof val == "string") {
			val = val.allReplace(forbiddenWords).fullTrim();
		}else if(val){
			val = exports.crushTRCharsUrlData(val)
		}
		data[key] = val;
	}
	return data;
};
exports.preProcessUrlData = function (data){
	if(!data || !data.name) return data;
	var forbiddenWords = {
		"ü":"u", "i":"i", "ı":"i","ö":"o","ç":"c","ş":"s","ğ":"g",
		"Ü":"U", "İ":"I", "I":"I","Ö":"O","Ç":"C","Ş":"S","Ğ":"G",
		"<>":" ","{":" ","}":" ", ".morecontent": "",  "display: block": " ",
		":":"", ".morelink": "", "\\t": " ", "span": " "
	};

	data.name = exports.crushTRChars(data.name);
	for(var key in data.properties){
		var val = data.properties[key];
		if (Object.prototype.toString.call(val) == "[object String]") {
			val = val.allReplace(forbiddenWords).toLowerCase().fullTrim();
		}
		data.properties[key] = val;
	}

	if(data.properties && data.properties.author && data.properties.publisher && data.properties.author.indexOf && data.properties.author.indexOf(data.properties.publisher) != -1){
		data.properties.author.replace(data.properties.publisher, "");
	}
	data.uniqueKey = data.uniqueKey.toLowerCase();
	return data;
};




exports.getParamNames = function (func) {
	var fnStr = func.toString().replace(STRIP_COMMENTS, '');
	var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
	if(result === null)
		result = [];
	return result;
};
exports.construct = function(constructor, args) {
	function F() {
		return constructor.apply(this, args);
	}
	F.prototype = constructor.prototype;
	return new F();
};
exports.downloadFile = function(url, path, filename, cb){
	var options = libraryModule.createHttpOptions(url, path);
	var req = shttp.request(options, function (res) {
		var imageData = '';
		res.setEncoding('binary');
		res.on('data', function (chunk) {
			imageData += chunk;
		});
		res.on('end', function () {
			fs.writeFile(filename, imageData, 'binary', function(err){
				console.log('File:4 ' + filename + " written!");
				cb(err);
			})
		});
	});
	req.on('error', function(e) {
		console.log('problem with request: ' + e.message);
	});
	req.end();

}
exports.createHttpTorOptions = function(url, parameters){
	var options = {
		socksPort: 9050, // Tor
		port: 80,
		hostname: url,
		protocol: 'http:',
		path: parameters
	};
	options.headers = {
		'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0',
		'Content-Type' : 'application/x-www-form-urlencoded',
		'Host': options.hostname
	};

	return options;
};
exports.createHttpsTorOptions = function(url, parameters){
	var options = {
		socksPort: 9050, // Tor
		port: 443,
		hostname: url,
		protocol: 'https:',
		path: parameters
	};

	options.headers = {
		'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0',
		'Content-Type' : 'application/x-www-form-urlencoded',
		'Host': options.hostname
	};
	return options;
};

exports.doHttpTorRequest = function(options, cb){
	exports.doRequest(options, shttp, cb);
};
exports.doHttpsTorRequest = function(options, cb){
	exports.doRequest(options, shttps, cb);
};
exports.doHttpRequest = function(options, cb){
	exports.doRequest(options, http, cb);
};
exports.doHttpsRequest = function(options, cb){
	exports.doRequest(options, https, cb);
};
exports.doRequest = function(options, http,  cb){
	var post_data

	if(options.method != "GET"){
		if(options.encodingType == "json"){
			post_data = new Buffer(JSON.stringify(options.postData), "utf-8");
			options.headers['Content-Length'] = post_data.length;
			options.headers['Content-Type'] = "application/json";

		}else if(options.encodingType == "binary"){
			post_data = options.postData;
			options.headers['Content-Length'] = post_data.length;

		}else{
			post_data = querystring.stringify(options.postData);
			options.headers['Content-Length'] = post_data.length;
			options.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=UTF-8";
		}
	}
	var req = http.request(options, function(res){
		res.setEncoding(options.encoding);
		var data = '';
		res.on('readable', function() {
			var dt = res.read()
			if(dt != null){
				data += dt;
			}
		});

		res.on('end', function () {
			cb(data, options, "end");
		});
	});
	if(options.method != "GET"){
		if(options.encodingType == "binary"){
			req.write(post_data,'binary');
		}else{
			var ret = req.write(post_data, "utf-8");
		}
	}

	req.on('error', function(e) {
		console.log('problem with request: ' + e.message);
		cb(undefined, options, "error")
	});
	req.setTimeout(options.timeout || 1 * 30 * 1000, function(e){
		console.log('request timeouted: ', e);
		cb(undefined, options, "timeout")

	});
	req.setSocketKeepAlive(1 * 30 * 1000);
	// GET request, so end without sending any data.
	req.end();
};



exports.createHttpPostOptions = function(url, parameters, data){
	var options = {
		socksPort: 9050, // Tor
		port: 80,
		hostname: url,
		protocol: 'http:',
		path: parameters,
		method: 'POST',
		postData: data,
		headers: {
			'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0',
			'Content-Type': 'application/form-data',
			'Content-Length': data.length
		}
	};

	return options;
};
exports.doRequestPost= function(options,  cb){
	var post_data
	if(options.encodingType == "json"){
		post_data = JSON.stringify(options.postData);
		options.headers['Content-Length'] = post_data.length;
		options.headers['Content-Type'] = "application/json";

	}else{
		post_data = querystring.stringify(options.postData);
		options.headers['Content-Length'] = post_data.length;
		options.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=UTF-8";
	}
	//console.log(post_data);
	var req = shttp.request(options, function(res){
		res.setEncoding('utf8');
		var data = '';
		res.on('readable', function() {
			var dt = res.read()
			if(dt != null){
				data += dt;
			}
		});

		res.on('end', function () {
			//console.log(data);
			cb(data, options, "end");

		});
	});
	req.write(post_data);

	req.on('error', function(e) {
		console.log('problem with request: ' + e.message);
		console.log('problem with request: ' + e);
		cb(undefined, options, "error")
	}); 
	req.setTimeout(options.timeout || 1 * 30 * 1000, function(){
		console.log('request timeouted: ');
		cb(undefined, options, "timeout")

	});
	// GET request, so end without sending any data.
	req.end();
};




exports.inheritStatic = function(parent, child){
	for(key in parent){
		child[key] = parent[key];
	}child._parent = parent;
}
exports.errors = function (errors) {
	var keys = Object.keys(errors);
	var errs = [];

	// if there is no validation error, just display a generic error
	if (!keys) {
		return ['Oops! There was an error']
	}

	keys.forEach(function (key) {
		if (errors[key]) errs.push(errors[key].message)
	});

	return errs
};

/**
 * Index of object within an array
 *
 * @param {Array} arr
 * @param {Object} obj
 * @return {Number}
 * @api public
 */

exports.indexof = function (arr, obj) {
	var index = -1; // not found initially
	var keys = Object.keys(obj);
	// filter the collection with the given criterias
	var result = arr.filter(function (doc, idx) {
		// keep a counter of matched key/value pairs
		var matched = 0;

		// loop over criteria
		for (var i = keys.length - 1; i >= 0; i--) {
			if (doc[keys[i]] === obj[keys[i]]) {
				matched++;

				// check if all the criterias are matched
				if (matched === keys.length) {
					index = idx;
					return idx;
				}
			}
		};
	});
	return index;
}
exports.isSameDBObject = function(a, b){
	if(a != undefined && b != undefined){
		return (a == b || a.id == b.id || a.id == b || a == b.id);
	}

}
/**
 * Find object in an array of objects that matches a condition
 *
 * @param {Array} arr
 * @param {Object} obj
 * @param {Function} cb - optional
 * @return {Object}
 * @api public
 */

exports.findByParam = function (arr, obj, cb) {
	var index = exports.indexof(arr, obj);
	if (~index && typeof cb === 'function') {
		return cb(undefined, arr[index])
	} else if (~index && !cb) {
		return arr[index]
	} else if (!~index && typeof cb === 'function') {
		return cb('not found')
	}
};

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
exports.getParamNames = function (func) {
	var fnStr = func.toString().replace(STRIP_COMMENTS, '')
	var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES)
	if(result === null)
		result = [];
	return result;

	// else undefined is returned
};

exports.instanceCreator = function(clas){
	return new clas();
};

exports.createInstanceWithParams = function(clas, parameterDic){
	//var args = this.getParamNames(clas.prototype.constructor);
	//var params = [];
	//for (var i = 0; i < args.length; i++) {
	//  var found = false;
	//  for(key in parameterDic){
	//    if (key == args[i]) {
	//      params.push(parameterDic[key]);
	//      found = true;
	//    };
	//  }
	//  if(!found){
	//    params.push(null);
	//  }
	//};
	var inst = this.instanceCreator(clas)
	for(key in parameterDic){
		inst[key] = parameterDic[key];
	}
	return inst
};

exports.filename = function(totalName){
	return totalName.replace(/\.[^/.]+$/, "")
};
exports.encrypt = function(text, password){
	algorithm = 'aes-256-ctr';
	var cipher = crypto.createCipher(algorithm, password);
	var crypted = cipher.update(text,'utf8','hex');
	crypted += cipher.final('hex');
	return crypted;
};
exports.decrypt = function(text, password){
	algorithm = 'aes-256-ctr';
	var decipher = crypto.createDecipher(algorithm, password);
	var dec = decipher.update(text,'hex','utf8');
	dec += decipher.final('utf8');
	return dec;
};

exports.fixTimeStamp = function(input, timezone,idx) {
	if (!input) {
		return "";
	}
	var date = new Date(input + timezone * 60 * 60 * 1000);

	var day = date.getDate();
	var month = date.getMonth() + 1;
	var year = date.getYear() + 1900;

	if (day < 10) {
		day = "0" + day;
	}
	if (month < 10) {
		month = "0" + month;
	}
	if (year < 10) {
		year = "0" + year;
	}
	var dateStr = day + "." + month + "." + year;

	var hours = date.getHours();
	var minutes = date.getMinutes();
	var seconds = date.getSeconds();

	if (hours < 10) {
		hours = "0" + hours;
	}
	if (minutes < 10) {
		minutes = "0" + minutes;
	}
	if (seconds < 10) {
		seconds = "0" + seconds;
	}
	var time = hours + ':' + minutes + ':' + seconds;
	return dateStr + " " + time;
}
exports.slugReplace = function(text) {
	var trMap = {
		'Ç':'Ç',
		'Ğ':'G',
		'Ş':'S',
		'Ü':'U',
		'İ':'I',
		'Ö':'O',
		'ç':'c',
		'ğ':'g',
		'ş':'s',
		'ü':'u',
		'ı':'i',
		'ö':'o'
	};
	for(var key in trMap) {
		text = text.replace(new RegExp('['+key+']','g'), trMap[key]);
	}
	return  text.replace(/[^-a-zA-Z0-9\s]+/ig, '') // remove non-alphanumeric chars
		.replace(/\s/gi, "-") // convert spaces to dashes
		.replace(/[-]+/gi, "-"); // trim repeated dashes
}
exports.md5 = function(str) {
	return crypto.createHash('md5').update(str).digest('hex')
}
exports.generateRandomID = function(){
	return "<id>" + new Date().getTime() + "<rand>" + Math.random() + "<id>";
}
exports.WaitingFunction = function(cb){
	var that = this;
	this.waitingList = {};
	this.cb = cb;
	this.checkStatus = function(){
		var isAllCalled = true;
		for(var key in that.waitingList){
			var fun = that.waitingList[key];
			isAllCalled = isAllCalled && fun.isCalled;
		};
		//console.log(that.waitingList);
		if(isAllCalled){
			that.cb(this.waitingList);
		}
	};

	this.generateFunctionWithID = function(id){
		var functionBody = "var id = '" + id + "';";
		functionBody += "arguments.callee.that.waitingList['"+id+"'].isCalled =  true;";
		functionBody += "arguments.callee.that.waitingList['"+id+"'].args =  arguments;";
		functionBody += "arguments.callee.that.checkStatus();";
		var func =  construct(Function, [functionBody]);
		func.that = that;
		return func;
	};
	this.getCbToWait = function(){
		var id = exports.generateRandomID();
		that.waitingList[id] = {
			isCalled: false,
		};
		var func = that.generateFunctionWithID(id);
		return func;
	}
};
exports.PeriodGrouper = function(){
	var that = this;
	var criterias = [];
	var preiodsList = [];
	var recognizedFuncions = {
		$eq : function(v, k){ return k == v},
		$lt : function(v, k){ return k > v},
		$lte : function(v, k){ return k >= v},
		$gt : function(v, k){ return k < v},
		$gte : function(v, k){ return k <= v},
	};
	this.setPeriodsList = function(list, predicate){ // should create something like {_id, $eq, $lte, $gte}
		criterias = [];
		preiodsList = list;
		for(var i in list){
			if(predicate){
				criterias.push(predicate(list[i]));
			}else{
				criterias.push(list[i]);
			}
		}
	};
	var isSatisfiesPredicate = function(value, predicate){
		var isSatisfies = true;
		for(var key in predicate){
			if(key in recognizedFuncions){
				isSatisfies = recognizedFuncions[key](value, predicate[key]);
			}
			if(!isSatisfies){
				break;
			}
		}
		return isSatisfies;
	};
	this.groupList = function(list, predicate, is){
		var periods = {};
		for(var i in list){
			var kvPair = predicate(list[i]); // return kv pailr {k:key, v:value};
			if(kvPair){
				for(j in criterias){
					var period = criterias[j];

					if(isSatisfiesPredicate(kvPair.v, period)){
						if(!periods[period._id]){
							periods[period._id] = [];
						}
						periods[period._id].push(list[i]);
					}
				}
			}
		}
		return periods;
	};
};
exports.createPeriodsForEventTimes = function(min, max, period){
	var list = [];
	list.push({_id:list.length, $lt:min});
	var cursor = min;
	while((cursor + period) < max){
		list.push({_id:list.length, $gte:cursor, $lt: cursor + period});
		cursor = cursor + period;
	}
	list.push({_id:list.length, $gte:cursor, $lt: max});
	list.push({_id:list.length, $gte: max});
	return  list;
};
exports.mergeByTime = function(lista, listb, fa, fb){
	var merged = [];
	while(lista.length > 0 || listb.length > 0){
		var peeka = lista.length > 0 ? lista[lista.length - 1] : undefined;
		var peekb = listb.length > 0 ? listb[listb.length - 1] : undefined;
		if(peeka && peekb){
			if(fa(peeka) > fb(peekb)){
				merged.push(lista.pop());
			}else{
				merged.push(listb.pop());
			}
		}else if(peeka){
			merged.push(lista.pop());
		}else if(peekb){
			merged.push(listb.pop());
		}

	}
	return merged;
}