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();
  });

});