// Returns all entities in the EntityManager that match a given search term.
// Matching logic is currently just a substring match.
func FindEntities(entitySearch server.EntitySearch) webapp.UserHttpHandler {
	return func(w http.ResponseWriter, r *http.Request, userId int) {
		w.Header().Set("Content-Type", "application/json")

		searchString := strings.TrimSpace(strings.ToLower(strutil.LastToken(r.URL.Path, "/")))

		var response map[string][]server.DisplayEntity
		if !strutil.IsEmpty(searchString) {
			searchResults := entitySearch.Find(searchString)
			response = map[string][]server.DisplayEntity{
				"Person": searchResults[server.PersonEntity],
				"Org":    searchResults[server.OrgEntity],
				"Place":  searchResults[server.PlaceEntity],
			}
		} else {
			noEntities := []server.DisplayEntity{}
			response = map[string][]server.DisplayEntity{
				"Person": noEntities,
				"Org":    noEntities,
				"Place":  noEntities,
			}
		}

		sendJsonResponse(response, w)
	}
}
// PA-241/PA-198: Support for disjunctive querying.
func GetAllEntityInfo(mgr *server.EntityManager) webapp.UserHttpHandler {
	return func(w http.ResponseWriter, r *http.Request, userId int) {
		w.Header().Set("Content-Type", "application/json")

		entityStr2entityType := map[string]server.EntityType{
			"Person": server.PersonEntity,
			"Org":    server.OrgEntity,
			"Place":  server.PlaceEntity,
		}

		// This function calculates the 'AND' co-occurence of the conjunct expression
		// of the form: ["and", "<entityType>:<id>", "<entityType:id", ...]
		calcConjunctExpr := func(g *server.ContentBuffer, expr ConjunctiveExpr) (*server.ContentBuffer, error) {
			logger.Printf("Calculating co-occurences for conjuncts: %v", expr.And)
			for _, entity := range expr.And {
				logger.Printf("Filtering on %v", entity.Id)
				entityTypeStr, entityId, err := parseEntityStr(entity.Id)
				if err != nil {
					return nil, err
				}

				entityType, isValidEntityType := entityStr2entityType[entityTypeStr]
				if !isValidEntityType {
					return nil, errors.New(fmt.Sprintf("Unknown entity type: '%v'", entityTypeStr))
				}

				g = g.FilterOnEntity(entityType, entityId)
			}

			return g, nil
		}

		// Parse the posted data into the filter query in JSON format.
		// This query is essentially a LISP expression of the form:
		//
		//     (op arg1 arg2 ...)
		//
		// where op is (for now) an 'or' operator (i.e. a disjunction).
		// Each arg is a nested LISP conjunct expression of the form (and arg1 arg2...),
		// where each arg is a string representing a distinct entity of the form
		// "<entity type>:<entity id>" (e.g. "Person:9283742").
		getHttpRequestBody(w, r, func(postedData []byte) {
			var stats server.EntityStats
			var err error
			var finalContentBuffer *server.ContentBuffer

			// Default filter query: no time range specified, and no entity filters specified.
			filterQuery := FilterQuery{}

			httpRequestContainsData := !strutil.IsEmpty(string(postedData))
			if httpRequestContainsData {
				if err = json.Unmarshal(postedData, &filterQuery); err != nil {
					http.Error(w, fmt.Sprintf("User %v: Error parsing posted JSON: %v", userId, err), http.StatusBadRequest)
					return
				}
			}

			var baseContentBuffer *server.ContentBuffer
			if filterQuery.IsTimeRangeSpecified() {
				timeRange := time.Duration(filterQuery.TimeRangeInHours) * time.Hour
				logger.Printf("Getting content buffer for timeRange=%v", timeRange)
				baseContentBuffer = mgr.ContentBufferForTimeRange(timeRange)
			} else {
				logger.Printf("Getting global content buffer")
				baseContentBuffer = mgr.ContentBuffer()
			}

			if filterQuery.IsEntityFilterSpecified() {
				logger.Printf("Calculating entity co-occurrences with disjunct query: %+V", filterQuery.Or)
				tmpContentBuffer := baseContentBuffer
				finalContentBuffer = server.NewContentBuffer()
				// Each disjunct is itself a conjunct expression of the form (and arg1 arg2 ...)
				for _, conjunctiveExpr := range filterQuery.Or {
					tmpContentBuffer, err = calcConjunctExpr(baseContentBuffer, conjunctiveExpr)
					if err != nil {
						http.Error(w, fmt.Sprintf("User %v: Error processing conjunct expression: %v", userId, err), http.StatusBadRequest)
						return
					}
					finalContentBuffer = finalContentBuffer.Union(tmpContentBuffer)
				}

				stats = finalContentBuffer.CalcEntityStats()
			} else {
				// Use the global stats, since this user has no filter set.
				logger.Printf("No entity filter provided, so using global entity stats.")
				finalContentBuffer = baseContentBuffer
				stats = finalContentBuffer.LatestEntityStats()
			}

			topEntities := map[string][]server.Entity{
				"Person": annotateEntities(mgr.ContentDAO.PersonDAO, stats.TopPersons),
				"Org":    annotateEntities(mgr.ContentDAO.OrgDAO, stats.TopOrgs),
				"Place":  annotateEntities(mgr.ContentDAO.PlaceDAO, stats.TopPlaces),
			}

			entityTimes, entityValues := stats.EntityTrend.Data()

			latestNews := make([]server.NewsArticle, 0, len(stats.LatestNews))
			for _, doc := range stats.LatestNews {
				newsArticle := server.NewsArticle{
					Document: doc,
					Persons:  annotateEntities(mgr.ContentDAO.PersonDAO, makeEntities(finalContentBuffer.PersonGraph.EntityIdsForDocument(doc.Id).Items())),
					Orgs:     annotateEntities(mgr.ContentDAO.OrgDAO, makeEntities(finalContentBuffer.OrgGraph.EntityIdsForDocument(doc.Id).Items())),
					Places:   annotateEntities(mgr.ContentDAO.PlaceDAO, makeEntities(finalContentBuffer.PlaceGraph.EntityIdsForDocument(doc.Id).Items())),
				}
				latestNews = append(latestNews, newsArticle)
			}

			response := map[string]interface{}{
				"EntityTrend": EntityTrend{entityTimes, entityValues},
				"TopEntities": topEntities,
				"LatestNews":  latestNews,
			}

			sendJsonResponse(response, w)
		}) // End getHttpRequestBody()
	}
}