func elementInSlice(element string, slice []string) bool {
	for _, el := range slice {
		if element == el {
			return true
		}
	}
	return false
}
elementInSlice tests if a string element is in a larger slice of strings
package server
import (
	"errors"
	"fmt"
	"net/url"
	"reflect"
	"strconv"
	"time"
	"github.com/intervention-engine/fhir/models"
	"github.com/intervention-engine/fhir/search"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)
type MasterSession struct {
	session *mgo.Session
	dbname  string
}
type WorkerSession struct {
	session *mgo.Session
	dbname  string
}
func NewMasterSession(session *mgo.Session, dbname string) (master *MasterSession) {
	return &MasterSession{
		session: session,
		dbname:  dbname,
	}
}
func (ms *MasterSession) GetWorkerSession() (worker *WorkerSession) {
	if ms.session == nil {
		return nil
	}
	return &WorkerSession{
		session: ms.session.Copy(),
		dbname:  ms.dbname,
	}
}
func (ws *WorkerSession) SetTimeout(d time.Duration) {
	if ws.session != nil {
		ws.session.SetSocketTimeout(d)
	}
}
func (ws *WorkerSession) DB() (db *mgo.Database) {
	if ws.session != nil && ws.dbname != "" {
		db = ws.session.DB(ws.dbname)
	}
	return
}
func (ws *WorkerSession) Close() {
	if ws.session != nil {
		ws.session.Close()
	}
}
func NewMongoDataAccessLayer(ms *MasterSession, interceptors map[string]InterceptorList) DataAccessLayer {
	return &mongoDataAccessLayer{
		MasterSession: ms,
		Interceptors:  interceptors,
	}
}
type mongoDataAccessLayer struct {
	MasterSession *MasterSession
	Interceptors  map[string]InterceptorList
}
type InterceptorList []Interceptor
type Interceptor struct {
	ResourceType string
	Handler      InterceptorHandler
}
type InterceptorHandler interface {
	Before(resource interface{})
	After(resource interface{})
	OnError(err error, resource interface{})
}
func (dal *mongoDataAccessLayer) invokeInterceptorsBefore(op, resourceType string, resource interface{}) {
	for _, interceptor := range dal.Interceptors[op] {
		if interceptor.ResourceType == resourceType || interceptor.ResourceType == "*" {
			interceptor.Handler.Before(resource)
		}
	}
}
func (dal *mongoDataAccessLayer) invokeInterceptorsAfter(op, resourceType string, resource interface{}) {
	for _, interceptor := range dal.Interceptors[op] {
		if interceptor.ResourceType == resourceType || interceptor.ResourceType == "*" {
			interceptor.Handler.After(resource)
		}
	}
}
func (dal *mongoDataAccessLayer) invokeInterceptorsOnError(op, resourceType string, err error, resource interface{}) {
	for _, interceptor := range dal.Interceptors[op] {
		if interceptor.ResourceType == resourceType || interceptor.ResourceType == "*" {
			interceptor.Handler.OnError(err, resource)
		}
	}
}
func (dal *mongoDataAccessLayer) hasInterceptorsForOpAndType(op, resourceType string) bool {
	if len(dal.Interceptors[op]) > 0 {
		for _, interceptor := range dal.Interceptors[op] {
			if interceptor.ResourceType == resourceType || interceptor.ResourceType == "*" {
				return true
			}
		}
	}
	return false
}
func (dal *mongoDataAccessLayer) Get(id, resourceType string) (result interface{}, err error) {
	bsonID, err := convertIDToBsonID(id)
	if err != nil {
		return nil, convertMongoErr(err)
	}
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	collection := worker.DB().C(models.PluralizeLowerResourceName(resourceType))
	result = models.NewStructForResourceName(resourceType)
	if err = collection.FindId(bsonID.Hex()).One(result); err != nil {
		return nil, convertMongoErr(err)
	}
	return
}
func (dal *mongoDataAccessLayer) Post(resource interface{}) (id string, err error) {
	id = bson.NewObjectId().Hex()
	err = convertMongoErr(dal.PostWithID(id, resource))
	return
}
func (dal *mongoDataAccessLayer) PostWithID(id string, resource interface{}) error {
	bsonID, err := convertIDToBsonID(id)
	if err != nil {
		return convertMongoErr(err)
	}
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(bsonID.Hex())
	resourceType := reflect.TypeOf(resource).Elem().Name()
	collection := worker.DB().C(models.PluralizeLowerResourceName(resourceType))
	updateLastUpdatedDate(resource)
	dal.invokeInterceptorsBefore("Create", resourceType, resource)
	err = collection.Insert(resource)
	if err == nil {
		dal.invokeInterceptorsAfter("Create", resourceType, resource)
	} else {
		dal.invokeInterceptorsOnError("Create", resourceType, err, resource)
	}
	return convertMongoErr(err)
}
func (dal *mongoDataAccessLayer) Put(id string, resource interface{}) (createdNew bool, err error) {
	bsonID, err := convertIDToBsonID(id)
	if err != nil {
		return false, convertMongoErr(err)
	}
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	resourceType := reflect.TypeOf(resource).Elem().Name()
	collection := worker.DB().C(models.PluralizeLowerResourceName(resourceType))
	reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(bsonID.Hex())
	updateLastUpdatedDate(resource)
	if dal.hasInterceptorsForOpAndType("Update", resourceType) {
		oldResource, getError := dal.Get(id, resourceType)
		if getError == nil {
			dal.invokeInterceptorsBefore("Update", resourceType, oldResource)
		}
	}
	info, err := collection.UpsertId(bsonID.Hex(), resource)
	if err == nil {
		createdNew = (info.Updated == 0)
		if createdNew {
			dal.invokeInterceptorsAfter("Create", resourceType, resource)
		} else {
			dal.invokeInterceptorsAfter("Update", resourceType, resource)
		}
	} else {
		dal.invokeInterceptorsOnError("Update", resourceType, err, resource)
	}
	return createdNew, convertMongoErr(err)
}
func (dal *mongoDataAccessLayer) ConditionalPut(query search.Query, resource interface{}) (id string, createdNew bool, err error) {
	if IDs, err := dal.FindIDs(query); err == nil {
		switch len(IDs) {
		case 0:
			id = bson.NewObjectId().Hex()
		case 1:
			id = IDs[0]
		default:
			return "", false, ErrMultipleMatches
		}
	} else {
		return "", false, err
	}
	createdNew, err = dal.Put(id, resource)
	return id, createdNew, err
}
func (dal *mongoDataAccessLayer) Delete(id, resourceType string) error {
	bsonID, err := convertIDToBsonID(id)
	if err != nil {
		return convertMongoErr(err)
	}
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	var resource interface{}
	var getError error
	hasInterceptor := dal.hasInterceptorsForOpAndType("Delete", resourceType)
	if hasInterceptor {
		resource, getError = dal.Get(id, resourceType)
		dal.invokeInterceptorsBefore("Delete", resourceType, resource)
	}
	collection := worker.DB().C(models.PluralizeLowerResourceName(resourceType))
	err = collection.RemoveId(bsonID.Hex())
	if hasInterceptor {
		if err == nil && getError == nil {
			dal.invokeInterceptorsAfter("Delete", resourceType, resource)
		} else {
			dal.invokeInterceptorsOnError("Delete", resourceType, err, resource)
		}
	}
	return convertMongoErr(err)
}
func (dal *mongoDataAccessLayer) ConditionalDelete(query search.Query) (count int, err error) {
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	resourceType := query.Resource
	searcher := search.NewMongoSearcher(worker.DB())
	collection := worker.DB().C(models.PluralizeLowerResourceName(resourceType))
	defaultQueryObject := searcher.CreateQueryObject(query)
	var queryObject bson.M
	if dal.hasInterceptorsForOpAndType("Delete", resourceType) {
		var bundle *models.Bundle
		bundle, err = dal.Search(url.URL{}, query)  
		if err == nil {
			resourceIds := getResourceIdsFromBundle(bundle)
			queryObject = bson.M{"_id": bson.M{"$in": resourceIds}}
			for _, elem := range bundle.Entry {
				dal.invokeInterceptorsBefore("Delete", resourceType, elem.Resource)
			}
			info, err := collection.RemoveAll(queryObject)
			successfulIds := make([]string, len(resourceIds))
			if info != nil {
				count = info.Removed
			}
			if err != nil {
				for _, elem := range bundle.Entry {
					dal.invokeInterceptorsOnError("Delete", resourceType, err, elem.Resource)
				}
				return count, convertMongoErr(err)
			}
			var failBundle *models.Bundle
			var searchErr error
			if count < len(resourceIds) {
				failBundle, searchErr = dal.Search(url.URL{}, query)  
				successfulIds = setDiff(resourceIds, getResourceIdsFromBundle(failBundle))
			} else {
				successfulIds = resourceIds
			}
			if searchErr == nil {
				for _, elem := range bundle.Entry {
					id := reflect.ValueOf(elem.Resource).Elem().FieldByName("Id").String()
					if elementInSlice(id, successfulIds) {
						dal.invokeInterceptorsAfter("Delete", resourceType, elem.Resource)
					} else {
						resourceErr := errors.New(fmt.Sprintf("ConditionalDelete: failed to delete resource %s with ID %s", resourceType, id))
						dal.invokeInterceptorsOnError("Delete", resourceType, resourceErr, elem.Resource)
					}
				}
			}
			return count, convertMongoErr(err)
		}
	} else {
		queryObject = defaultQueryObject
	}
	info, err := collection.RemoveAll(queryObject)
	if info != nil {
		count = info.Removed
	}
	return count, convertMongoErr(err)
}
func (dal *mongoDataAccessLayer) Search(baseURL url.URL, searchQuery search.Query) (*models.Bundle, error) {
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	searcher := search.NewMongoSearcher(worker.DB())
	var result interface{}
	var err error
	usesIncludes := len(searchQuery.Options().Include) > 0
	usesRevIncludes := len(searchQuery.Options().RevInclude) > 0
	if usesIncludes || usesRevIncludes {
		result = models.NewSlicePlusForResourceName(searchQuery.Resource, 0, 0)
		err = searcher.CreatePipeline(searchQuery).All(result)
	} else {
		result = models.NewSliceForResourceName(searchQuery.Resource, 0, 0)
		err = searcher.CreateQuery(searchQuery).All(result)
	}
	if err != nil {
		return nil, convertMongoErr(err)
	}
	includesMap := make(map[string]interface{})
	var entryList []models.BundleEntryComponent
	resultVal := reflect.ValueOf(result).Elem()
	for i := 0; i < resultVal.Len(); i++ {
		var entry models.BundleEntryComponent
		entry.Resource = resultVal.Index(i).Addr().Interface()
		entry.Search = &models.BundleEntrySearchComponent{Mode: "match"}
		entryList = append(entryList, entry)
		if usesIncludes || usesRevIncludes {
			rpi, ok := entry.Resource.(ResourcePlusRelatedResources)
			if ok {
				for k, v := range rpi.GetIncludedAndRevIncludedResources() {
					includesMap[k] = v
				}
			}
		}
	}
	for _, v := range includesMap {
		var entry models.BundleEntryComponent
		entry.Resource = v
		entry.Search = &models.BundleEntrySearchComponent{Mode: "include"}
		entryList = append(entryList, entry)
	}
	var bundle models.Bundle
	bundle.Id = bson.NewObjectId().Hex()
	bundle.Type = "searchset"
	bundle.Entry = entryList
	options := searchQuery.Options()
	var total uint32
	if resultVal.Len() == options.Count || resultVal.Len() == 0 {
		intTotal, err := searcher.CreateQueryWithoutOptions(searchQuery).Count()
		if err != nil {
			return nil, convertMongoErr(err)
		}
		total = uint32(intTotal)
	} else {
		total = uint32(options.Offset + resultVal.Len())
	}
	bundle.Total = &total
	bundle.Link = generatePagingLinks(baseURL, searchQuery, total)
	return &bundle, nil
}
func (dal *mongoDataAccessLayer) FindIDs(searchQuery search.Query) (IDs []string, err error) {
	worker := dal.MasterSession.GetWorkerSession()
	defer worker.Close()
	oldParams := searchQuery.URLQueryParameters(false)
	newParams := search.URLQueryParameters{}
	for _, param := range oldParams.All() {
		switch param.Key {
		case search.ContainedParam, search.ContainedTypeParam, search.ElementsParam, search.IncludeParam,
			search.RevIncludeParam, search.SummaryParam:
			continue
		default:
			newParams.Add(param.Key, param.Value)
		}
	}
	newQuery := search.Query{Resource: searchQuery.Resource, Query: newParams.Encode()}
	searcher := search.NewMongoSearcher(worker.DB())
	mgoQuery := searcher.CreateQuery(newQuery).Select(bson.M{"_id": 1})
	results := []struct {
		ID string `bson:"_id"`
	}{}
	if err := mgoQuery.All(&results); err != nil {
		return nil, err
	}
	IDs = make([]string, len(results))
	for i := range results {
		IDs[i] = results[i].ID
	}
	return IDs, nil
}
type ResourcePlusRelatedResources interface {
	GetIncludedAndRevIncludedResources() map[string]interface{}
	GetIncludedResources() map[string]interface{}
	GetRevIncludedResources() map[string]interface{}
}
func generatePagingLinks(baseURL url.URL, query search.Query, total uint32) []models.BundleLinkComponent {
	links := make([]models.BundleLinkComponent, 0, 5)
	params := query.URLQueryParameters(true)
	offset := 0
	if pOffset := params.Get(search.OffsetParam); pOffset != "" {
		offset, _ = strconv.Atoi(pOffset)
		if offset < 0 {
			offset = 0
		}
	}
	count := search.NewQueryOptions().Count
	if pCount := params.Get(search.CountParam); pCount != "" {
		count, _ = strconv.Atoi(pCount)
		if count < 1 {
			count = search.NewQueryOptions().Count
		}
	}
	links = append(links, newLink("self", baseURL, params, offset, count))
	links = append(links, newLink("first", baseURL, params, 0, count))
	if offset > 0 {
		prevOffset := offset - count
		if prevOffset < 0 {
			prevOffset = 0
		}
		prevCount := offset - prevOffset
		links = append(links, newLink("previous", baseURL, params, prevOffset, prevCount))
	}
	if total > uint32(offset+count) {
		nextOffset := offset + count
		links = append(links, newLink("next", baseURL, params, nextOffset, count))
	}
	remainder := (int(total) - offset) % count
	if int(total) < offset {
		remainder = 0
	}
	newOffset := int(total) - remainder
	if remainder == 0 && int(total) > count {
		newOffset = int(total) - count
	}
	links = append(links, newLink("last", baseURL, params, newOffset, count))
	return links
}
func newLink(relation string, baseURL url.URL, params search.URLQueryParameters, offset int, count int) models.BundleLinkComponent {
	params.Set(search.OffsetParam, strconv.Itoa(offset))
	params.Set(search.CountParam, strconv.Itoa(count))
	baseURL.RawQuery = params.Encode()
	return models.BundleLinkComponent{Relation: relation, Url: baseURL.String()}
}
func convertIDToBsonID(id string) (bson.ObjectId, error) {
	if bson.IsObjectIdHex(id) {
		return bson.ObjectIdHex(id), nil
	}
	return bson.ObjectId(""), models.NewOperationOutcome("fatal", "exception", "Id must be a valid BSON ObjectId")
}
func updateLastUpdatedDate(resource interface{}) {
	m := reflect.ValueOf(resource).Elem().FieldByName("Meta")
	if m.IsNil() {
		newMeta := &models.Meta{}
		m.Set(reflect.ValueOf(newMeta))
	}
	now := &models.FHIRDateTime{Time: time.Now(), Precision: models.Timestamp}
	m.Elem().FieldByName("LastUpdated").Set(reflect.ValueOf(now))
}
func convertMongoErr(err error) error {
	switch err {
	default:
		return err
	case mgo.ErrNotFound:
		return ErrNotFound
	}
}
func getResourceIdsFromBundle(bundle *models.Bundle) []string {
	resourceIds := make([]string, int(*bundle.Total))
	for i, elem := range bundle.Entry {
		resourceIds[i] = reflect.ValueOf(elem.Resource).Elem().FieldByName("Id").String()
	}
	return resourceIds
}
func setDiff(X, Y []string) []string {
	m := make(map[string]int)
	for _, y := range Y {
		m[y]++
	}
	var ret []string
	for _, x := range X {
		if m[x] > 0 {
			m[x]--
			continue
		}
		ret = append(ret, x)
	}
	return ret
}