content: import $ from 'jquery';
  import URL from 'js-handy-url';
  import moment from 'moment';
  import React from 'react';
  import tl from  './translator';
  import actions from './actions';
  import app from '../app/app';
  import DynForm from './DynForm';
  import messaging from './messaging';
  import DataTableView from '../view/DataTableView';
  import debug from '../util/debug';
  	
  const SELECT = {
  		  NONE : 'none'
  		, ALL : 'all'
  	}
  
  // helper functions
  
    , getFilteredDefinitionFields = function(entityDefinition, ignore_fields, customColls, excludeActions, excludeSelect){
  		let result = [], previewHeader;
  		if (! ignore_fields) ignore_fields = [];
  
  		if (customColls) {
  			for (let i = 0; i < customColls.length; i++) {
  				result.push({name: customColls[i].name, input: customColls[i].input});
  			}
  		}
  
  		for (let i = 0; i < entityDefinition.length; i++){
  			let def = entityDefinition[i],
  				field = def.name
  				;
  
  			if (ignore_fields.indexOf(field) === -1) {
  
  				if (def.input === 'multi_select') {
  					// we are looping over all the options
  					if (def.options) {
  						for (var k = 0; k < def.options.length; k++) {
  							if (def.options[k].key) result.push({
  								name: field + '-' + def.options[k].key,
  								label: <span className="widget-tooltip" title={def.options[k].label + ' (' + def.options[k].shortlabel + ')'}>{def.options[k].shortlabel}</span>,
  								input: 'multi_select'
  							});
  						}
  					}
  				} else if( def.preview) { // the preview attribute is set in case we have an entity, which should be displayed already on this level
  					var additionalHeaders = getFilteredDefinitionFields(def.preview, null, null, true, true);
  					for (var k = 0; k < additionalHeaders.length; k++){
  
  						previewHeader = additionalHeaders[k];
  						previewHeader.preview_property = field;
  
  						result.push(previewHeader);
  					}
  				} else {
  					result.push({name: field});
  				}
  			}
  		}
  
  		// adding of the action and select collumns
  		if(! excludeActions) result.push({name: 'actions'});
  		if(! excludeSelect) result.push({name: 'select', label: '', input: 'checkbox'});
  
  		return result;
  	}
  ;
  
  export default class DataTable extends React.Component {
  
  	constructor(props) {
  		super(props);
  
  		let url = new URL();
  		let data = props.data ? props.data : [];
  
  		this.state = {
              ignore_fields: props.ignore_fields ? props.ignore_fields : [],
  			definition: props.definition ? props.definition : null,
  			sort_data_by: props.sortBy ? props.sortBy : 'id',
  			selectButton: SELECT.NONE,
  			populated: {},
  			itemCount: props.itemCount ? props.itemCount : data.length,
  			orderBy: {id : 'ASC'},
  			currentPage: parseInt(url.getParameter('page', 1))
  		};
  
  		this.items = this.sortData(data, this.state.sort_data_by, 'ASC'); // the items to be rendered
  	}
  
  	componentDidMount(){
  		// we are populating what needs to be populated
  		let k, def,
  			pop, fields,
  			entityName, entityField,
  
              handleDataPopulation = function () {
                  fields = this.state.definition.concat(this.state.customColls ? this.props.customColls : []);
                  for (k = 0; k < fields.length; k++ ){
                      def = fields[k];
                      if (def.populate === true){
  
                          pop = this.state.populated;
                          entityName = def.type;
                          entityField = def.name;
                          pop[entityField] = false;
                          this.setState({populated: pop});
  
                          app.entity.populate(entityName, null, entityField, def, function(entityName, entityField){
                              var pop = this.state.populated;
                              pop[entityField] = true;
                              this.setState({populated: pop});
  
                              // initilization of the multi_select fields
                              /*
                               $(ReactDOM.findDOMNode(this)).find('thead .widget-tooltip').tooltipster({
                               animation : 'fade'
                               , maxWidth : '300'
                               , speed : 120
                               , delay : 50
                               // , autoClose: false
                               });
  
                               */
                          }.bind(this));
                      }
                  }
              }.bind(this)
  		;
  
  		// do we need to load the original definition first?
          if (this.state.definition) {
              handleDataPopulation();
          } else if (this.props.entityName) {
              app.loadDataDefinition(this.props.entityName).then(function ( {default: definition} ) {
                  this.setState({definition: definition.getFields()});
                  handleDataPopulation();
              }.bind(this))
          } else {
              debug.error('DataTable initialized without definition or EntityName - no data can be shown.');
          }
  	}
  
  	hasAction(action){
  		return this.props.actions && this.props.actions.indexOf(action) > -1
  	}
  
  	sortData(dataArray, keyVal, direction){
  		direction = direction == 'DESC' ? -1 : 1;
  		keyVal = keyVal ? keyVal : this.state.sort_data_by;
  
  		dataArray.sort(function(a,b){
  			var numberVal1 = 0, numberVal2 = 0,
  				parts = keyVal.split(/\.|-/),
  				valA = a, //  = a[keyVal] === null ? numberVal1 : a[keyVal],
  				valB = b, // = b[keyVal] === null ? numberVal2 : b[keyVal]
  				i = 0
  			;
  
  			// diffing into the structure
  			for (i; i < parts.length; i++){
  				if (valA !== null && typeof valA == 'object') valA = valA[parts[i]];
  				if (valB !== null && typeof valB == 'object') valB = valB[parts[i]];
  			}
  
  
  			if ( (valA === null || valA === undefined) && (valB !== null && valB !== undefined)) {
  				return direction * -1; // b is null, but the other value not
  			}
  
  			if ( (valB === null || valB === undefined) && (valA !== null && valA !== undefined)) {
  				return direction; // b is null, but the other value not
  			}
  
  			if ((valA === null || valA === undefined) && (valB === null || valB === undefined)) {
  				return 0;
  			}
  
  
  			switch (typeof valA){
  				default:
  					return valA.toString().localeCompare(valB.toString()) * direction;
  					break;
  				case "undefined":
  					numberVal1 = a.id;
  					numberVal2 = b.id;
  					break;
  				case "object":
  
  					if (valA.date) {
  						numberVal1 = moment(valA.date).valueOf();
  						numberVal2 = moment(valB.date).valueOf();
  					} else if (valA.currencyCode) {
  						numberVal1 = valA.value;
  						numberVal2 = valB.value;
  					} else if (valA[tl.getDefaultLocale()] !== undefined) {
  						return valA[tl.getDefaultLocale()].toString().localeCompare(valB[tl.getDefaultLocale()].toString()) * direction
  					} else {
  						debug.log(valA);
  						return 0;
  					}
  					break;
  				case "number":
  					numberVal1 = valA;
  					numberVal2 = valB;
  					break;
  			}
  
  
  			if (numberVal1 - numberVal2 == 0) {
  				numberVal1 = a.id;
  				numberVal2 = b.id;
  			}
  
  			return (numberVal1 - numberVal2) * direction;
  		});
  
  		return dataArray;
  	}
  
  		// the callback gets this again as this, so no that workarund needed there
  	prepareEntityForm(callback) {
  		// first we load all the definitions, in case we have to deal with entity fields here
  		var i, definitions = [], dev, that = this;
  
  		for (i in this.state.definition) {
  			dev = this.state.definition[i];
  			if (dev.populate || (dev.input && dev.input.toLowerCase() == 'entity')){
  
  				// removing dublicates
  				if (definitions.indexOf(dev.type) == -1) {
  					definitions.push(dev.type)
  				}
  			}
  		}
  
  		// we load the data entity
  		Promise.all(definitions.map(function(dev) { return app.loadDataDefinition(dev)})).then(function(defs) {
  			var fieldDefs = {};
              for (i in definitions) {
                  fieldDefs[definitions[i]] = defs[i].default.getFields();
              }
  
              if (callback) {
                  callback.call(that, fieldDefs);
              }
  
          });
  	}
  
  	createEntity(){
  		this.prepareEntityForm(function(fieldDefs){
  			let modal = messaging.createModal(tl.tf( 'modal.create.title', tl.t('entity.name.' + this.props.entityName)), <DynForm className={this.props.entityName + '-form'} action={  app.util.getAppUrl(app.util.getCurrentPath(), 'create') } onSuccess={this.addEntity.bind(this)} data={this.state.definition} fieldDefs={ fieldDefs } />);
  			modal.show();
  		});
  	}
  
  	/**
  	 * adds and updates entitys
  	 *
  	 * @param entity
  	 * @param message
  	 */
  	addEntity(entity, message){
  		messaging.getCurrentModal().hide();
  		if (message){
  			app.messanger.addMessage(message, 'success');
  		}
  
  		var isNew = true;
  
  		for (var i = 0; i < this.items.length; i++) {
  			if (this.items[i].id == entity.id) {
  				this.items[i] = entity;
  				isNew = false;
  				break;
  			}
  		}
  
  		if (isNew) {
  			this.items.push(entity);
  		}
  
  		this.setState({data: this.sortData(this.items)});
  	}
  		// adds and updates entitys
  	removeEntity(entity, message){
  		messaging.getCurrentModal().hide();
  
  		if (message) {
  			app.messanger.addMessage(message, 'success');
  		}
  
  		this.items.splice( this.items.indexOf(entity), 1 ); // removes the element from the list
  		this.setState({});
  	}
  
  		// url is the url object
  	editEntity(entity, url){
  
  		this.prepareEntityForm(function(fieldDefs){
  			// finally show the modal
  			let modal = messaging.createModal(tl.tf( 'modal.edit.title', tl.t('entity.name.' + this.props.entityName), entity.id), <DynForm className={this.props.entityName + '-form'} action={ url } loadData={ url } onSuccess={ this.addEntity.bind(this) } data={ this.state.definition } fieldDefs={ fieldDefs } />);
  			// modal.showLoading();
  			modal.show();
  		});
  
  	}
  
  	deleteEntity(entity, url){
  		let that = this
  			, confirmDelete = function(){
  
  			url = new URL(url);
  
  			modal.showLoading();
  
  			$.ajax({
  				url: url.addParameter('format', 'ajax').toString(),
  				dataType: 'json',
  				type: 'POST',
  
  				success: function(data) {
  					modal.hideLoading();
  
  					if(data.success){
  						this.removeEntity(entity, data.message);
  					} else {
  						app.messanger.addMessage(data.message, 'danger');
  					}
  
  
  				}.bind(that),
  
  				error: function(xhr, status, err) {
  					modal.hideLoading();
  					app.messanger.addMessage(tl.tf('error.unexpected_ajax_exception', xhr.responseJSON ? xhr.responseJSON.message : err.toString()), 'danger');
  				}.bind(that),
  
  				complete: function(){
  					modal.hide();
  				}.bind(that)
  
  			});
  		}
  			, modal = messaging.createModal(tl.tf( 'modal.delete.title', tl.t('entity.name.' + this.props.entityName)), (
  			<div>
  				<p>{tl.tf('entity.delete.confirmation', tl.t('entity.name.' + this.props.entityName), entity.id)}</p>
  				<div className="text-right">
  					<button type="button" className="btn btn-primary" onClick={confirmDelete}>{tl.t('global.delete_confirm')}</button> <button type="button" onClick={messaging.closeCurrentModal} className="btn btn-link">{tl.t('global.close')}</button>
  				</div>
  			</div>
  		) );
  		modal.show();
  	}
  
      setData (data, item_count) {
          this.items = data ? data : [];
          this.setState({
              itemCount : item_count !== null ? item_count : data.length()
          });
      }
  
      /**
       * @param {URL} url - must be a URL Object
       * @param e
       */
  	dataUpdate(url, e) {
  		if(e) e.preventDefault();
  
  		this.refs.view.refs.loader.show();
  		app.addUrlToBrowserHistory(url.toString());
  
  		$.ajax({
  			url: url.addParameter('format', 'ajax').toString(),
  			dataType: 'json',
  			type: 'POST',
  
  			success: function(data) {
                  this.setData(data.data, data.meta.item_count);
  				this.setState({currentPage: parseInt(url.getParameter('page', 1))});
  				this.refs.view.refs.loader.hide();
  			}.bind(this),
  
  			error: function(xhr, status, err) {
  				this.refs.view.refs.loader.hide();
  				this.refs.view.refs.messagebar.setState({hide: false, severity: 'danger', message: tl.tf('error.unexpected_ajax_exception', err.toString())});
  				$(messaging.getModalRoot()).find('.modal').scrollTop(0);
  			}.bind(this)
  
  		});
  	}
  
  	sortBy(fieldname, e){
  
  		let direction = 'ASC', sort = {};
  
  		if (this.state.orderBy[fieldname]) {
  			direction = this.state.orderBy[fieldname] == 'ASC' ? 'DESC' : 'ASC';
  		}
  
  		sort[fieldname] = direction;
  
  		if (this.state.itemCount > this.items.length) {
  			var url = new URL();
  
  			url.addParameter('orderBy', fieldname + ':' + direction);
  
  			this.dataUpdate(url);
  		} else {
  			this.items = this.sortData(this.items, fieldname, direction);
  		}
  
  		this.setState({orderBy : sort, sort_data_by : fieldname})
  	}
  
  	getCurrentFilterValue(filterAttribute){
  		var filters = (new URL()).getParameter('filter', '').split('|'),
  			value = ''
  		;
  
  		filters.forEach(function(filter){
  			var parts = filter.split(':');
  
  			if (parts.length > 1 && parts[0] == filterAttribute) {
  				value = parts[1];
  			}
  		});
  
  		return value;
  	}
  
  	getFilterOptions(definition){
  		var options = [], now, start;
  
  		switch (definition.type) {
  			case 'boolean':
  				options = [
  					{key :  '', label: 'All'},
  					{key : '1', label: 'Yes'},
  					{key : '0', label: 'No'}
  				];
  				break;
  			case 'datetime':
  				// kiss workaround to fake the start date
  				options.push({key :  '', label: 'All'});
  
  				now = moment().year();
  				start = 2014;
  				while (start <= (now + 1) ) {
  					options.push({key :  start, label: start});
  					start++;
  				}
  				break;
  			default:
  				options = definition.options;
  				break;
  		}
  
  		return options;
  	}
  
  	filter(attr, e){
  
  		if (e.target.tagName.toLocaleUpperCase() != 'INPUT' || e.keyCode == 13) {
  			var url = (new URL()).removeParameter('page'),
  				val = e.target.value.trim(),
  				currentFilters = url.getParameter('filter', '').split('|'),
  				newFilters = [],
  				filterFound = false
  			;
  
  			$(e.target).val(val);
  
  			currentFilters.forEach( function(filter){
  				var parts = filter.split(':');
  				if (parts.length > 1) {
  					if (attr == parts[0]) {
  						filterFound = true;
  						if (val) {
  							newFilters.push(attr + ':' + val);
  						}
  					} else {
  						newFilters.push(filter);
  					}
  				} else {
  					// this means the attribute was not set
  					filterFound = true;
  					newFilters.push(attr + ':' + val);
  				}
  			});
  
  			if (! filterFound) newFilters.push(attr + ':' + val);
  
  			if (newFilters.length) {
  				url.addParameter('filter', newFilters.join('|'));
  			} else {
  				url.removeParameter('filter');
  			}
  
  			this.dataUpdate(url);
  		}
  	}
  
  	editAnyEntity (entityName, id, e) {
  
  		e.preventDefault();
  
  		app.loadDataDefinition(entityName).then(function ( {default: definition} ) {
              const fieldDev = definition.getFields();
              const url = app.util.getAppUrl('show', entityName, 'item', id, 'edit');
              let dev, definitions = [];
  
              for (let i in fieldDev) {
                  dev = fieldDev[i];
                  if (dev.populate || (dev.input && dev.input.toLowerCase() === 'entity')){
  
                      // removing dublicates
                      if (definitions.indexOf(dev.type) === -1) {
                          definitions.push(dev.type)
                      }
                  }
              }
  
              // we load the data entity
              Promise.all(definitions.map(function(dev) { return app.loadDataDefinition(dev) })).then(function(defs) {
  
              	const fieldDefs = {};
                  for (let i in arguments) {
                      fieldDefs[definitions[i]] = defs[i].default.getFields();
                  }
  
                  // finally show the modal
                  let modal = messaging.createModal(
                      tl.tf('modal.edit.title', tl.t('entity.name.' + entityName), id),
  					<DynForm
  						className={entityName + '-form'}
  						action={ url }
  						loadData={ url }
  						data={ fieldDev }
  						fieldDefs={ fieldDefs }
  					/>
                  );
                  // modal.showLoading();
                  modal.show();
              });
          });
  	}
  
  	clickSelect(tc, e){
  		let checked = $('th.' + tc + ' input').prop('checked'),
  			selector = 'input.' + tc + ':' + (checked ? 'unchecked' : 'checked'),
  			refId
  		;
  
  		if (tc === 'field-select') {
  			for (refId in this.refs.view.refs) {
  				if (refId.substr(0, 5) === 'line_'){
  					this.refs.view.refs[refId].selectAction(null, checked);
  				}
  			}
  		} else {
  			$(selector).click()
  		}
  	}
  
  	render() {
  		return this.state.definition ? <DataTableView
  			ref="view"
  			controller={this}
  			entityName={this.props.entityName}
  			definition={this.state.definition}
  			itemCount={this.state.itemCount ? this.state.itemCount : this.items.length}
  			pageSizes={[150, 300, 500, 1000]}
  			currentPage={this.state.currentPage}
  			orderBy={this.state.orderBy}
  			showAddButton={this.hasAction(actions.CREATE)}
  			showSearch={this.props.showSearch}
  			toolbarElements={this.props.toolbarElements}
  
  			items={this.items}
  			customColls={this.props.customColls}
  			headerCells={ getFilteredDefinitionFields(this.state.definition, this.state.ignore_fields, this.props.customColls, false, ! this.hasAction(actions.SELECT)) }
  			getFilterOptions={this.getFilterOptions}
  			actions={this.props.actions}
  			customActions={this.props.customActions}
  			ignore_fields={this.state.ignore_fields}
  			callbacks={ {
  				edit: this.editEntity,
  				delete: this.deleteEntity
  			} }
  
  			edit_any_entity={this.editAnyEntity.bind(this)}
  			display_class_callback={this.props.display_class_callback}
  			handle_createEntity={this.createEntity}
  
  		/>: <div className="ajax-loader">{tl.tf('global.load.entity', tl.t('entity.name.' + this.props.entityName))}</div>;
  	}
  }
  	
  