package ca.uhn.fhir.jpa.sp;

/*-
 * #%L
 * HAPI FHIR JPA Server
 * %%
 * Copyright (C) 2014 - 2017 University Health Network
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import ca.uhn.fhir.jpa.dao.DaoConfig;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;

import ca.uhn.fhir.jpa.dao.data.ISearchParamDao;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SearchParam;
import ca.uhn.fhir.jpa.entity.SearchParamPresent;

public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {

	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamPresenceSvcImpl.class);

	private Map<Pair<String, String>, SearchParam> myResourceTypeToSearchParamToEntity = new ConcurrentHashMap<Pair<String, String>, SearchParam>();

	@Autowired
	private ISearchParamDao mySearchParamDao;

	@Autowired
	private ISearchParamPresentDao mySearchParamPresentDao;

	@Autowired
	private DaoConfig myDaoConfig;

	@Override
	public void updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
		if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
			return;
		}

		Map<String, Boolean> presenceMap = new HashMap<String, Boolean>(theParamNameToPresence);
		List<SearchParamPresent> entitiesToSave = new ArrayList<SearchParamPresent>();
		List<SearchParamPresent> entitiesToDelete = new ArrayList<SearchParamPresent>();

		Collection<SearchParamPresent> existing;
		existing = mySearchParamPresentDao.findAllForResource(theResource);

		for (SearchParamPresent nextExistingEntity : existing) {
			String nextSearchParamName = nextExistingEntity.getSearchParam().getParamName();
			Boolean existingValue = presenceMap.remove(nextSearchParamName);
			if (existingValue == null) {
				entitiesToDelete.add(nextExistingEntity);
			} else if (existingValue.booleanValue() == nextExistingEntity.isPresent()) {
				ourLog.trace("No change for search param {}", nextSearchParamName);
			} else {
				nextExistingEntity.setPresent(existingValue);
				entitiesToSave.add(nextExistingEntity);
			}
		}

		for (Entry<String, Boolean> next : presenceMap.entrySet()) {
			String resourceType = theResource.getResourceType();
			String paramName = next.getKey();
			Pair<String, String> key = Pair.of(resourceType, paramName);

			SearchParam searchParam = myResourceTypeToSearchParamToEntity.get(key);
			if (searchParam == null) {
				searchParam = mySearchParamDao.findForResource(resourceType, paramName);
				if (searchParam != null) {
					myResourceTypeToSearchParamToEntity.put(key, searchParam);
				} else {
					searchParam = new SearchParam();
					searchParam.setResourceName(resourceType);
					searchParam.setParamName(paramName);
					searchParam = mySearchParamDao.save(searchParam);
					ourLog.info("Added search param {} with pid {}", paramName, searchParam.getId());
					// Don't add the newly saved entity to the map in case the save fails
				}
			}

			SearchParamPresent present = new SearchParamPresent();
			present.setResource(theResource);
			present.setSearchParam(searchParam);
			present.setPresent(next.getValue());
			entitiesToSave.add(present);

		}

		mySearchParamPresentDao.deleteInBatch(entitiesToDelete);
		mySearchParamPresentDao.save(entitiesToSave);

	}

	@Override
	public void flushCachesForUnitTest() {
		myResourceTypeToSearchParamToEntity.clear();
	}

}
