/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.portal.search.elasticsearch.internal;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.search.BaseIndexWriter;
import com.liferay.portal.kernel.search.Document;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.IndexWriter;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.SearchException;
import com.liferay.portal.kernel.search.suggest.SpellCheckIndexWriter;
import com.liferay.portal.kernel.util.PortalRunMode;
import com.liferay.portal.search.elasticsearch.connection.ElasticsearchConnectionManager;
import com.liferay.portal.search.elasticsearch.document.ElasticsearchUpdateDocumentCommand;
import com.liferay.portal.search.elasticsearch.index.IndexNameBuilder;
import com.liferay.portal.search.elasticsearch.internal.util.DocumentTypes;
import com.liferay.portal.search.elasticsearch.internal.util.LogUtil;

import java.util.Collection;

import org.elasticsearch.action.admin.indices.refresh.RefreshRequestBuilder;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequestBuilder;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
 * @author Michael C. Han
 * @author Milen Dyankov
 */
@Component(
	immediate = true, property = {"search.engine.impl=Elasticsearch"},
	service = IndexWriter.class
)
public class ElasticsearchIndexWriter extends BaseIndexWriter {

	@Override
	public void addDocument(SearchContext searchContext, Document document)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocument(
			DocumentTypes.LIFERAY, searchContext, document, false);
	}

	@Override
	public void addDocuments(
			SearchContext searchContext, Collection<Document> documents)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocuments(
			DocumentTypes.LIFERAY, searchContext, documents, false);
	}

	@Override
	public void commit(SearchContext searchContext) throws SearchException {
		try {
			AdminClient adminClient =
				elasticsearchConnectionManager.getAdminClient();

			IndicesAdminClient indicesAdminClient = adminClient.indices();

			RefreshRequestBuilder refreshRequestBuilder =
				indicesAdminClient.prepareRefresh(
					indexNameBuilder.getIndexName(
						searchContext.getCompanyId()));

			RefreshResponse refreshResponse = refreshRequestBuilder.get();

			LogUtil.logActionResponse(_log, refreshResponse);
		}
		catch (Exception e) {
			throw new SearchException("Unable to commit indices", e);
		}
	}

	@Override
	public void deleteDocument(SearchContext searchContext, String uid)
		throws SearchException {

		String indexName = indexNameBuilder.getIndexName(
			searchContext.getCompanyId());

		try {
			Client client = elasticsearchConnectionManager.getClient();

			DeleteRequestBuilder deleteRequestBuilder = client.prepareDelete(
				indexName, DocumentTypes.LIFERAY, uid);

			if (PortalRunMode.isTestMode() ||
				searchContext.isCommitImmediately()) {

				deleteRequestBuilder.setRefresh(true);
			}

			DeleteResponse deleteResponse = deleteRequestBuilder.get();

			LogUtil.logActionResponse(_log, deleteResponse);
		}
		catch (IndexNotFoundException infe) {
			if (_log.isInfoEnabled()) {
				_log.info(
					"No index found while attempting to delete " + uid +
						" in index " + indexName);
			}
		}
		catch (Exception e) {
			throw new SearchException("Unable to delete document " + uid, e);
		}
	}

	@Override
	public void deleteDocuments(
			SearchContext searchContext, Collection<String> uids)
		throws SearchException {

		try {
			Client client = elasticsearchConnectionManager.getClient();

			BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();

			for (String uid : uids) {
				DeleteRequestBuilder deleteRequestBuilder =
					client.prepareDelete(
						indexNameBuilder.getIndexName(
							searchContext.getCompanyId()),
						DocumentTypes.LIFERAY, uid);

				bulkRequestBuilder.add(deleteRequestBuilder);
			}

			if (PortalRunMode.isTestMode() ||
				searchContext.isCommitImmediately()) {

				bulkRequestBuilder.setRefresh(true);
			}

			BulkResponse bulkResponse = bulkRequestBuilder.get();

			LogUtil.logActionResponse(_log, bulkResponse);
		}
		catch (Exception e) {
			throw new SearchException("Unable to delete documents " + uids, e);
		}
	}

	@Override
	public void deleteEntityDocuments(
			SearchContext searchContext, String className)
		throws SearchException {

		SearchResponseScroller searchResponseScroller = null;

		try {
			Client client = elasticsearchConnectionManager.getClient();

			MatchAllQueryBuilder matchAllQueryBuilder =
				QueryBuilders.matchAllQuery();

			TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(
				Field.ENTRY_CLASS_NAME, className);

			BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

			boolQueryBuilder.filter(termQueryBuilder);
			boolQueryBuilder.must(matchAllQueryBuilder);

			searchResponseScroller = new SearchResponseScroller(
				client, searchContext, indexNameBuilder, boolQueryBuilder,
				TimeValue.timeValueSeconds(30), DocumentTypes.LIFERAY);

			searchResponseScroller.prepare();

			searchResponseScroller.scroll(_searchHitsProcessor);
		}
		catch (IndexNotFoundException infe) {
			if (_log.isInfoEnabled()) {
				_log.info(
					"No index found while attempting to delete documents for " +
						className + " in index " +
							indexNameBuilder.getIndexName(
								searchContext.getCompanyId()));
			}
		}
		catch (Exception e) {
			throw new SearchException(
				"Unable to delete data for entity " + className, e);
		}
		finally {
			if (searchResponseScroller != null) {
				searchResponseScroller.close();
			}
		}
	}

	@Override
	public void partiallyUpdateDocument(
			SearchContext searchContext, Document document)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocument(
			DocumentTypes.LIFERAY, searchContext, document, false);
	}

	@Override
	public void partiallyUpdateDocuments(
			SearchContext searchContext, Collection<Document> documents)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocuments(
			DocumentTypes.LIFERAY, searchContext, documents, false);
	}

	@Override
	@Reference(target = "(search.engine.impl=Elasticsearch)", unbind = "-")
	public void setSpellCheckIndexWriter(
		SpellCheckIndexWriter spellCheckIndexWriter) {

		super.setSpellCheckIndexWriter(spellCheckIndexWriter);
	}

	@Override
	public void updateDocument(SearchContext searchContext, Document document)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocument(
			DocumentTypes.LIFERAY, searchContext, document, true);
	}

	@Override
	public void updateDocuments(
			SearchContext searchContext, Collection<Document> documents)
		throws SearchException {

		elasticsearchUpdateDocumentCommand.updateDocuments(
			DocumentTypes.LIFERAY, searchContext, documents, true);
	}

	@Reference(unbind = "-")
	protected ElasticsearchConnectionManager elasticsearchConnectionManager;

	@Reference(unbind = "-")
	protected ElasticsearchUpdateDocumentCommand
		elasticsearchUpdateDocumentCommand;

	@Reference(unbind = "-")
	protected IndexNameBuilder indexNameBuilder;

	private static final Log _log = LogFactoryUtil.getLog(
		ElasticsearchIndexWriter.class);

	private final SearchHitsProcessor _searchHitsProcessor =
		new DeleteDocumentsSearchHitsProcessor(this);

}