define([

'angular',
'lodash',
'config',
'kbn'

], function (angular, _, config, kbn) {

'use strict';

var module = angular.module('kibana.services');

module.service('querySrv', function(dashboard, ejsResource, filterSrv, esVersion, $q) {

  // Save a reference to this
  var self = this;

  // Create an object to hold our service state on the dashboard
  dashboard.current.services.query = dashboard.current.services.query || {};
  _.defaults(dashboard.current.services.query,{
    list : {},
    ids : [],
  });

  this.colors = [
    "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
    "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
    "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
    "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
    "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
    "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
    "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"  //7
  ];

  // For convenience
  var ejs = ejsResource(config.elasticsearch);

  // Holds all actual queries, including all resolved abstract queries
  var resolvedQueries = [];

  // Defaults for generic query object
  var _query = {
    alias: '',
    pin: false,
    type: 'lucene',
    enable: true
  };

  // Defaults for specific query types
  var _dTypes = {
    "lucene": {
      query: "*"
    },
    "regex": {
      query: ".*"
    },
    "topN": {
      query: "*",
      field: "_type",
      size: 5,
      union: 'AND'
    }
  };

  // query type meta data that is not stored on the dashboard object
  this.queryTypes = {
    lucene: {
      require:">=0.17.0",
      icon: "icon-circle",
      resolve: function(query) {
        // Simply returns itself
        var p = $q.defer();
        p.resolve(_.extend(query,{parent:query.id}));
        return p.promise;
      }
    },
    regex: {
      require:">=0.90.12",
      icon: "icon-circle",
      resolve: function(query) {
        // Simply returns itself
        var p = $q.defer();
        p.resolve(_.extend(query,{parent:query.id}));
        return p.promise;
      }
    },
    topN : {
      require:">=0.90.3",
      icon: "icon-cog",
      resolve: function(q) {
        var suffix = '';
        if (q.union === 'AND') {
          suffix = ' AND (' + (q.query||'*') + ')';
        } else if (q.union === 'OR') {
          suffix = ' OR (' + (q.query||'*') + ')';
        }

        var request = ejs.Request().indices(dashboard.indices);
        // Terms mode
        request = request
          .facet(ejs.TermsFacet('query')
            .field(q.field)
            .size(q.size)
            .facetFilter(ejs.QueryFilter(
              ejs.FilteredQuery(
                ejs.QueryStringQuery(q.query || '*'),
                filterSrv.getBoolFilter(filterSrv.ids())
                )))).size(0);

        var results = request.doSearch();
        // Like the regex and lucene queries, this returns a promise
        return results.then(function(data) {
          var _colors = kbn.colorSteps(q.color,data.facets.query.terms.length);
          var i = -1;
          return _.map(data.facets.query.terms,function(t) {
            ++i;
            return self.defaults({
              query  : q.field+':"'+kbn.addslashes('' + t.term)+'"'+suffix,
              alias  : t.term + (q.alias ? " ("+q.alias+")" : ""),
              type   : 'lucene',
              color  : _colors[i],
              parent : q.id
            });
          });
        });
      }
    }
  };

  self.types = [];
  _.each(self.queryTypes,function(type,name){
    esVersion.is(type.require).then(function(is) {
      if(is) {
        self.types.push(name);
      }
    });
  });

  this.list = function () {
    return dashboard.current.services.query.list;
  };

  this.ids = function () {
    return dashboard.current.services.query.ids;
  };

  this.init = function() {

    dashboard.current.services.query.ids =
      _.intersection(_.map(dashboard.current.services.query.list,
        function(v,k){return parseInt(k,10);}),self.ids());

    // Check each query object, populate its defaults
    _.each(dashboard.current.services.query.list,function(query) {
      query = self.defaults(query);
    });

    if (dashboard.current.services.query.ids.length === 0) {
      self.set({});
    }
  };

  // This is used both for adding queries and modifying them. If an id is passed,
  // the query at that id is updated
  this.set = function(query,id) {
    if(!_.isUndefined(id)) {
      if(!_.isUndefined(dashboard.current.services.query.list[id])) {
        _.extend(dashboard.current.services.query.list[id],query);
        return id;
      } else {
        return false;
      }
    } else {
      // Query must have an id and color already
      query.id = _.isUndefined(query.id) ? nextId() : query.id;
      query.color = query.color || colorAt(query.id);
      // Then it can get defaults
      query = self.defaults(query);
      dashboard.current.services.query.list[query.id] = query;
      dashboard.current.services.query.ids.push(query.id);
      return query.id;
    }
  };

  this.defaults = function(query) {
    _.defaults(query,_query);
    _.defaults(query,_dTypes[query.type]);
    query.color = query.color || colorAt(query.id);
    return query;
  };

  this.remove = function(id) {
    if(!_.isUndefined(dashboard.current.services.query.list[id])) {
      delete dashboard.current.services.query.list[id];
      // This must happen on the full path also since _.without returns a copy
      dashboard.current.services.query.ids = _.without(dashboard.current.services.query.ids,id);
      return true;
    } else {
      return false;
    }
  };

  // These are the only query types that can be returned by a compound query.
  this.toEjsObj = function (q) {
    switch(q.type)
    {
    case 'lucene':
      return ejs.QueryStringQuery(q.query || '*');
    case 'regex':
      return ejs.RegexpQuery('_all',q.query);
    default:
      return false;
    }
  };

  //
  this.getQueryObjs = function(ids) {
    if(_.isUndefined(ids)) {
      return resolvedQueries;
    } else {
      return _.flatten(_.map(ids,function(id) {
        return _.where(resolvedQueries,{parent:id});
      }));
    }
  };

  // BROKEN
  this.idsByMode = function(config) {
    switch(config.mode)
    {
    case 'all':
      return _.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id');
    case 'pinned':
      return _.pluck(_.where(dashboard.current.services.query.list,{pin:true,enable:true}),'id');
    case 'unpinned':
      return _.pluck(_.where(dashboard.current.services.query.list,{pin:false,enable:true}),'id');
    case 'selected':
      return _.intersection(_.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id'),config.ids);
    default:
      return _.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id');
    }
  };

  // This populates the internal query list and returns a promise containing it
  this.resolve = function() {
    // Find ids of all abstract queries
    // Get a list of resolvable ids, constrast with total list to get abstract ones
    return $q.all(_.map(dashboard.current.services.query.ids,function(q) {
      return self.queryTypes[dashboard.current.services.query.list[q].type].resolve(
        _.clone(dashboard.current.services.query.list[q])).then(function(data){
        return data;
      });
    })).then(function(data) {
      resolvedQueries = _.flatten(data);
      _.each(resolvedQueries,function(q,i) {
        q.id = i;
      });
      return resolvedQueries;
    });
  };

  var nextId = function() {
    var idCount = dashboard.current.services.query.ids.length;
    if(idCount > 0) {
      // Make a sorted copy of the ids array
      var ids = _.sortBy(_.clone(dashboard.current.services.query.ids),function(num){
        return num;
      });
      return kbn.smallestMissing(ids);
    } else {
      // No ids currently in list
      return 0;
    }
  };

  var colorAt = function(id) {
    return self.colors[id % self.colors.length];
  };

  self.init();
});

});