// Returns configuration and runtime information about the deployed application.
func GetSystemInfo(mgr *server.EntityManager, cfg AppConfig) webapp.HttpHandler {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")

		contentBuffer := mgr.ContentBuffer()
		entityStats := contentBuffer.LatestEntityStats()

		// If no content received from the content source within the last minute
		// or so, we deem it "offline".
		timeSinceLatestContentReceived := time.Since(entityStats.NewestContent.Time())
		isContentSourceOnline := timeSinceLatestContentReceived <= (1 * time.Minute)

		timeRangesInHours := []int{}
		for _, duration := range cfg.TimeRanges {
			timeRangesInHours = append(timeRangesInHours, int(duration/time.Hour))
		}

		itemCounts := map[string]int{
			"Documents": contentBuffer.DocumentCount(),
			"Persons":   contentBuffer.PersonGraph.EntityCount(),
			"Orgs":      contentBuffer.OrgGraph.EntityCount(),
			"Places":    contentBuffer.PlaceGraph.EntityCount(),
		}

		runtimeInfo := map[string]interface{}{
			"Errors":              mgr.RecentErrors(),
			"ItemCounts":          itemCounts,
			"ContentSourceOnline": isContentSourceOnline,
			"TimeRangesInHours":   timeRangesInHours,
			"OldestContent":       entityStats.OldestContent.String(),
			"NewestContent":       entityStats.NewestContent.String(),
		}

		deploymentInfo := map[string]interface{}{
			"Version":       GIT_VERSION,
			"BuildDate":     BUILD_DATE,
			"MemDbEndpoint": cfg.MemDbConn,
		}

		systemIinfo := map[string]interface{}{
			"Runtime":    runtimeInfo,
			"Deployment": deploymentInfo,
		}

		sendJsonResponse(systemIinfo, w)
	}
}
// Saves the global data (a.k.a. the "content buffer") to disk.
func SaveGlobalData(entityMgr *server.EntityManager, cfg AppConfig) webapp.HttpHandler {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")

		logger.Printf("\n\n========================\n" +
			"SAVING GLOBAL DATA!\n" +
			"========================\n\n")

		dataDir := cfg.DataDir

		createDataDirIfNotExists(dataDir)

		refreshStatsLock.Lock()
		entityMgr.ContentBuffer().SaveState(dataDir)
		entityMgr.ContentDAO.Save(dataDir)
		refreshStatsLock.Unlock()

		logger.Printf("\n\n========================\n" +
			"GLOBAL DATA SAVED!\n" +
			"========================\n\n")
	}
}
// 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()
	}
}