func CountCategories() int {
	cacheKey := "count:categories"
	if c, ok := miscCache.Get(cacheKey); ok {
		return c.(int)
	}
	all, err := GetCategories()
	if err != nil {
		return -1
	}
	miscCache.SetDefault(cacheKey, len(all))
	return len(all)
}
/*
CountCategories counts the number of categories
package events
import (
	"fmt"
	log "github.com/golang/glog"
	"github.com/muety/kitsquid/app/config"
	"github.com/muety/kitsquid/app/util"
	"github.com/patrickmn/go-cache"
	"github.com/timshannon/bolthold"
	"go.etcd.io/bbolt"
	"math"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"time"
)
var (
	db             *bolthold.Store
	cfg            *config.Config
	eventsCache    *cache.Cache
	bookmarksCache *cache.Cache
	miscCache      *cache.Cache
	reviewsCache   *cache.Cache
	commentsCache  *cache.Cache
)
func initStore(store *bolthold.Store) {
	cfg = config.Get()
	db = store
	eventsCache = cache.New(cfg.CacheDuration("events", 30*time.Minute), cfg.CacheDuration("events", 30*time.Minute)*2)
	miscCache = cache.New(cfg.CacheDuration("misc", 30*time.Minute), cfg.CacheDuration("misc", 30*time.Minute)*2)
	bookmarksCache = cache.New(cfg.CacheDuration("bookmarks", 30*time.Minute), cfg.CacheDuration("bookmarks", 30*time.Minute)*2)
	reviewsCache = cache.New(cfg.CacheDuration("reviews", 30*time.Minute), cfg.CacheDuration("reviews", 30*time.Minute)*2)
	commentsCache = cache.New(cfg.CacheDuration("comments", 30*time.Minute), cfg.CacheDuration("comments", 30*time.Minute)*2)
	setup()
	if !cfg.QuickStart {
		Reindex()
	}
}
func Reindex() {
	r := func(name string) {
		if r := recover(); r != nil {
			log.Errorf("failed to reindex %s store\n", name)
		}
	}
	for _, t := range []interface{}{&Event{}, &Bookmark{}} {
		tn := reflect.TypeOf(t).String()
		log.Infof("reindexing %s", tn)
		func() {
			defer r(tn)
			db.ReIndex(t, nil)
		}()
	}
}
func FlushCaches() {
	log.Infoln("flushing events caches")
	eventsCache.Flush()
	miscCache.Flush()
	bookmarksCache.Flush()
	reviewsCache.Flush()
	commentsCache.Flush()
}
func setup() {}
func Get(id string) (*Event, error) {
	cacheKey := fmt.Sprintf("get:%s", id)
	if l, ok := eventsCache.Get(cacheKey); ok {
		return l.(*Event), nil
	}
	var event Event
	if err := db.Get(id, &event); err != nil {
		return nil, err
	}
	eventsCache.SetDefault(cacheKey, &event)
	return &event, nil
}
func GetAll() ([]*Event, error) {
	return Find(nil)
}
func Find(query *EventQuery) ([]*Event, error) {
	cacheKey := fmt.Sprintf("find:%v", query)
	if ee, ok := eventsCache.Get(cacheKey); ok {
		return ee.([]*Event), nil
	}
	var foundEvents []*Event
	q := bolthold.Where("Id").Not().Eq("").Index("Id")
	if query != nil {
		if query.NameLike != "" {
			if re, err := regexp.Compile("(?i)" + query.NameLike); err == nil {
				q.And("Name").RegExp(re)
			}
		}
		if query.TypeEq != "" {
			q.And("Type").Eq(query.TypeEq).Index("Type")
		}
		if query.LecturerIdEq != "" {
			q.And("Lecturers").MatchFunc(func(ra *bolthold.RecordAccess) (bool, error) {
				if field := ra.Field(); field != nil {
					for _, item := range field.([]*Lecturer) {
						if item.Gguid == query.LecturerIdEq {
							return true, nil
						}
					}
				}
				return false, nil
			})
		}
		if query.SemesterEq != "" {
			q.And("Semesters").Contains(query.SemesterEq).Index("Semesters")
		}
		if len(query.CategoryIn) > 0 {
			cats := make([]interface{}, 0)
			for _, c := range query.CategoryIn {
				if c != "" {
					cats = append(cats, c)
				}
			}
			if len(cats) > 0 {
				q.And("Categories").ContainsAny(cats...).Index("Categories")
			}
		}
		if query.Skip > 0 {
			q.Skip(query.Skip)
		}
		if query.Limit > 0 {
			q.Limit(query.Limit)
		}
		if len(query.SortFields) > 0 {
			q.SortBy(query.SortFields...)
		}
	}
	err := db.Find(&foundEvents, q)
	if err == nil {
		eventsCache.SetDefault(cacheKey, foundEvents)
	}
	return foundEvents, err
}
func Count() int {
	cacheKey := "count"
	if c, ok := eventsCache.Get(cacheKey); ok {
		return c.(int)
	}
	all, err := GetAll()
	if err != nil {
		return -1
	}
	eventsCache.SetDefault(cacheKey, len(all))
	return len(all)
}
func Insert(event *Event, upsert bool, overwrite bool) error {
	f := db.Insert
	if upsert {
		if existing, err := Get(event.Id); err == nil {
			f = db.Upsert
			if !overwrite {
				updateEvent(event, existing)
			}
		}
	}
	eventsCache.Flush()
	miscCache.Flush()
	return f(event.Id, event)
}
func InsertMulti(events []*Event, upsert bool, overwrite bool) error {
	f := db.TxInsert
	if upsert {
		f = db.TxUpsert
	}
	tx, err := db.Bolt().Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	for _, e := range events {
		if upsert {
			if existing, err := Get(e.Id); err == nil {
				if !overwrite {
					updateEvent(e, existing)
				}
			}
		}
		if err := f(tx, e.Id, e); err != nil {
			log.Errorf("failed to insert event %s", e.Id)
			tx.Rollback()
		}
	}
	eventsCache.Flush()
	miscCache.Flush()
	return tx.Commit()
}
func Delete(key string) error {
	defer FlushCaches()
	return db.Bolt().Update(func(tx *bbolt.Tx) error {
		if err := db.TxDelete(tx, key, &Event{}); err != nil {
			return err
		}
		if err := db.TxDeleteMatching(tx, &Bookmark{}, bolthold.Where("EntityId").Eq(key)); err != nil {
			return err
		}
		return nil
	})
}
func updateEvent(newEvent, existingEvent *Event) {
	newEvent.Rating = existingEvent.Rating
	newEvent.InverseRating = existingEvent.InverseRating
	for _, c := range existingEvent.Semesters {
		if !util.ContainsString(c, newEvent.Semesters) {
			newEvent.Semesters = append(newEvent.Semesters, c)
		}
	}
}
func GetFaculties() ([]string, error) {
	return GetCategoriesAtIndex(config.FacultyIdx)
}
func CountFaculties() int {
	cacheKey := "count:faculties"
	if c, ok := miscCache.Get(cacheKey); ok {
		return c.(int)
	}
	all, err := GetFaculties()
	if err != nil {
		return -1
	}
	miscCache.SetDefault(cacheKey, len(all))
	return len(all)
}
func GetCategories() ([]string, error) {
	cacheKey := "get:categories"
	if fl, ok := miscCache.Get(cacheKey); ok {
		return fl.([]string), nil
	}
	categories := make([]string, 0)
	for i := 0; ; i++ {
		batch, err := GetCategoriesAtIndex(i)
		if err != nil || len(batch) == 0 {
			break
		}
		categories = append(categories, batch...)
	}
	miscCache.SetDefault(cacheKey, categories)
	return categories, nil
}
func GetCategoriesAtIndex(index int) ([]string, error) {
	cacheKey := fmt.Sprintf("get:categories:index:%d", index)
	if fl, ok := miscCache.Get(cacheKey); ok {
		return fl.([]string), nil
	}
	categoryMap := make(map[string]bool)
	events, err := GetAll()
	if err != nil {
		return []string{}, err
	}
	for _, l := range events {
		if len(l.Categories) > index {
			if _, ok := categoryMap[l.Categories[index]]; !ok {
				categoryMap[l.Categories[index]] = true
			}
		}
	}
	var i int
	categories := make([]string, len(categoryMap))
	for k := range categoryMap {
		categories[i] = k
		i++
	}
	miscCache.SetDefault(cacheKey, categories)
	return categories, nil
}