text: import { Meteor } from 'meteor/meteor';
  import ChatpalLogger from '../utils/logger';
  import { Random } from 'meteor/random';
  
  /**
   * Enables HTTP functions on Chatpal Backend
   */
  class Backend {
  
  	constructor(options) {
  		this._options = options;
  	}
  
  	/**
  	 * index a set of Sorl documents
  	 * @param docs
  	 * @returns {boolean}
  	 */
  	index(docs) {
  		const options = {
  			data:docs,
  			params:{ language:this._options.language },
  			...this._options.httpOptions,
  		};
  
  		try {
  
  			const response = HTTP.call('POST', `${ this._options.baseurl }${ this._options.updatepath }`, options);
  
  			if (response.statusCode >= 200 && response.statusCode < 300) {
  				ChatpalLogger.debug(`indexed ${ docs.length } documents`, JSON.stringify(response.data, null, 2));
  			} else {
  				throw new Error(response);
  			}
  
  		} catch (e) {
  			// TODO how to deal with this
  			ChatpalLogger.error('indexing failed', JSON.stringify(e, null, 2));
  			return false;
  		}
  
  	}
  
  	/**
  	 * remove an entry by type and id
  	 * @param type
  	 * @param id
  	 * @returns {boolean}
  	 */
  	remove(type, id) {
  		ChatpalLogger.debug(`Remove ${ type }(${ id }) from Index`);
  
  		const options = {
  			data:{
  				delete: {
  					query: `id:${ id } AND type:${ type }`,
  				},
  				commit:{},
  			},
  			...this._options.httpOptions,
  		};
  
  		try {
  			const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options);
  
  			return response.statusCode >= 200 && response.statusCode < 300;
  		} catch (e) {
  			return false;
  		}
  	}
  
  	count(type) {
  		return this.query({ type, rows:0, text:'*' })[type].numFound;
  	}
  
  	/**
  	 * query with params
  	 * @param params
  	 * @param callback
  	 */
  	query(params, callback) {
  
  		const options = {
  			params,
  			...this._options.httpOptions,
  		};
  
  		ChatpalLogger.debug('query: ', JSON.stringify(options, null, 2));
  
  		try {
  			if (callback) {
  				HTTP.call('POST', this._options.baseurl + this._options.searchpath, options, (err, result) => {
  					if (err) { return callback(err); }
  
  					callback(undefined, result.data);
  				});
  			} else {
  
  				const response = HTTP.call('POST', this._options.baseurl + this._options.searchpath, options);
  
  				if (response.statusCode >= 200 && response.statusCode < 300) {
  					return response.data;
  				} else {
  					throw new Error(response);
  				}
  			}
  		} catch (e) {
  			ChatpalLogger.error('query failed', JSON.stringify(e, null, 2));
  			throw e;
  		}
  	}
  
  	suggest(params, callback) {
  
  		const options = {
  			params,
  			...this._options.httpOptions,
  		};
  
  		HTTP.call('POST', this._options.baseurl + this._options.suggestionpath, options, (err, result) => {
  			if (err) { return callback(err); }
  
  			try {
  				callback(undefined, result.data.suggestion);
  			} catch (e) {
  				callback(e);
  			}
  		});
  	}
  
  	clear() {
  		ChatpalLogger.debug('Clear Index');
  
  		const options = {
  			data:{
  				delete: {
  					query: '*:*',
  				},
  				commit:{},
  			}, ...this._options.httpOptions,
  		};
  
  		try {
  			const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options);
  
  			return response.statusCode >= 200 && response.statusCode < 300;
  		} catch (e) {
  			return false;
  		}
  	}
  
  	/**
  	 * statically ping with configuration
  	 * @param options
  	 * @returns {boolean}
  	 */
  	static ping(config) {
  
  		const options = {
  			params: {
  				stats:true,
  			},
  			...config.httpOptions,
  		};
  
  		try {
  			const response = HTTP.call('GET', config.baseurl + config.pingpath, options);
  
  			if (response.statusCode >= 200 && response.statusCode < 300) {
  				return response.data.stats;
  			} else {
  				return false;
  			}
  		} catch (e) {
  			return false;
  		}
  	}
  
  }
  
  /**
   * Enabled batch indexing
   */
  class BatchIndexer {
  
  	constructor(size, func, ...rest) {
  		this._size = size;
  		this._func = func;
  		this._rest = rest;
  		this._values = [];
  	}
  
  	add(value) {
  		this._values.push(value);
  		if (this._values.length === this._size) {
  			this.flush();
  		}
  	}
  
  	flush() {
  		this._func(this._values, this._rest);// TODO if flush does not work
  		this._values = [];
  	}
  }
  
  /**
   * Provides index functions to chatpal provider
   */
  export default class Index {
  
  	/**
  	 * Creates Index Stub
  	 * @param options
  	 * @param clear if a complete reindex should be done
  	 */
  	constructor(options, clear, date) {
  
  		this._id = Random.id();
  
  		this._backend = new Backend(options);
  
  		this._options = options;
  
  		this._batchIndexer = new BatchIndexer(this._options.batchSize || 100, (values) => this._backend.index(values));
  
  		this._bootstrap(clear, date);
  	}
  
  	/**
  	 * prepare solr documents
  	 * @param type
  	 * @param doc
  	 * @returns {*}
  	 * @private
  	 */
  	_getIndexDocument(type, doc) {
  		switch (type) {
  			case 'message':
  				return {
  					id: doc._id,
  					rid: doc.rid,
  					user: doc.u._id,
  					created: doc.ts,
  					updated: doc._updatedAt,
  					text: doc.msg,
  					type,
  				};
  			case 'room':
  				return {
  					id: doc._id,
  					rid: doc._id,
  					created: doc.createdAt,
  					updated: doc.lm ? doc.lm : doc._updatedAt,
  					type,
  					room_name: doc.name,
  					room_announcement: doc.announcement,
  					room_description: doc.description,
  					room_topic: doc.topic,
  				};
  			case 'user':
  				return {
  					id: doc._id,
  					created: doc.createdAt,
  					updated: doc._updatedAt,
  					type,
  					user_username: doc.username,
  					user_name: doc.name,
  					user_email: doc.emails && doc.emails.map((e) => e.address),
  				};
  			default: throw new Error(`Cannot index type '${ type }'`);
  		}
  	}
  
  	/**
  	 * return true if there are messages in the databases which has been created before *date*
  	 * @param date
  	 * @returns {boolean}
  	 * @private
  	 */
  	_existsDataOlderThan(date) {
  		return RocketChat.models.Messages.model.find({ ts:{ $lt: new Date(date) }, t:{ $exists:false } }, { limit:1 }).fetch().length > 0;
  	}
  
  	_doesRoomCountDiffer() {
  		return RocketChat.models.Rooms.find({ t:{ $ne:'d' } }).count() !== this._backend.count('room');
  	}
  
  	_doesUserCountDiffer() {
  		return Meteor.users.find({ active:true }).count() !== this._backend.count('user');
  	}
  
  	/**
  	 * Index users by using a database cursor
  	 */
  	_indexUsers() {
  		const cursor = Meteor.users.find({ active:true });
  
  		ChatpalLogger.debug(`Start indexing ${ cursor.count() } users`);
  
  		cursor.forEach((user) => {
  			this.indexDoc('user', user, false);
  		});
  
  		ChatpalLogger.info(`Users indexed successfully (index-id: ${ this._id })`);
  	}
  
  	/**
  	 * Index rooms by database cursor
  	 * @private
  	 */
  	_indexRooms() {
  		const cursor = RocketChat.models.Rooms.find({ t:{ $ne:'d' } });
  
  		ChatpalLogger.debug(`Start indexing ${ cursor.count() } rooms`);
  
  		cursor.forEach((room) => {
  			this.indexDoc('room', room, false);
  		});
  
  		ChatpalLogger.info(`Rooms indexed successfully (index-id: ${ this._id })`);
  	}
  
  	_indexMessages(date, gap) {
  
  		const start = new Date(date - gap);
  		const end = new Date(date);
  
  		const cursor = RocketChat.models.Messages.model.find({ ts:{ $gt: start, $lt: end }, t:{ $exists:false } });
  
  		ChatpalLogger.debug(`Start indexing ${ cursor.count() } messages between ${ start.toString() } and ${ end.toString() }`);
  
  		cursor.forEach((message) => {
  			this.indexDoc('message', message, false);
  		});
  
  		ChatpalLogger.info(`Messages between ${ start.toString() } and ${ end.toString() } indexed successfully (index-id: ${ this._id })`);
  
  		return start.getTime();
  	}
  
  	_run(date, resolve, reject) {
  
  		this._running = true;
  
  		if (this._existsDataOlderThan(date) && !this._break) {
  
  			Meteor.setTimeout(() => {
  				date = this._indexMessages(date, (this._options.windowSize || 24) * 3600000);
  
  				this._run(date, resolve, reject);
  
  			}, this._options.timeout || 1000);
  		} else if (this._break) {
  			ChatpalLogger.info(`stopped bootstrap (index-id: ${ this._id })`);
  
  			this._batchIndexer.flush();
  
  			this._running = false;
  
  			resolve();
  		} else {
  
  			ChatpalLogger.info(`No messages older than already indexed date ${ new Date(date).toString() }`);
  
  			if (this._doesUserCountDiffer() && !this._break) {
  				this._indexUsers();
  			} else {
  				ChatpalLogger.info('Users already indexed');
  			}
  
  			if (this._doesRoomCountDiffer() && !this._break) {
  				this._indexRooms();
  			} else {
  				ChatpalLogger.info('Rooms already indexed');
  			}
  
  			this._batchIndexer.flush();
  
  			ChatpalLogger.info(`finished bootstrap (index-id: ${ this._id })`);
  
  			this._running = false;
  
  			resolve();
  		}
  	}
  
  	_bootstrap(clear, date) {
  
  		ChatpalLogger.info('Start bootstrapping');
  
  		return new Promise((resolve, reject) => {
  
  			if (clear) {
  				this._backend.clear();
  				date = new Date().getTime();
  			}
  
  			this._run(date, resolve, reject);
  
  		});
  	}
  
  	static ping(options) {
  		return Backend.ping(options);
  	}
  
  	stop() {
  		this._break = true;
  	}
  
  	reindex() {
  		if (!this._running) {
  			this._bootstrap(true);
  		}
  	}
  
  	indexDoc(type, doc, flush = true) {
  		this._batchIndexer.add(this._getIndexDocument(type, doc));
  
  		if (flush) { this._batchIndexer.flush(); }
  
  		return true;
  	}
  
  	removeDoc(type, id) {
  		return this._backend.remove(type, id);
  	}
  
  	query(text, language, acl, type, start, rows, callback, params = {}) {
  		this._backend.query({
  			text,
  			language,
  			acl,
  			type,
  			start,
  			rows,
  			...params,
  		}, callback);
  	}
  
  	suggest(text, language, acl, type, callback) {
  		this._backend.suggest({
  			text,
  			language,
  			acl,
  			type,
  		}, callback);
  	}
  
  }
  