/**
 * Copyright (c) 2000-2013 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.portlet.asset.service.impl;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.search.Document;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.Hits;
import com.liferay.portal.kernel.search.Indexer;
import com.liferay.portal.kernel.search.IndexerRegistryUtil;
import com.liferay.portal.kernel.search.QueryConfig;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.SearchException;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.OrderByComparator;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.model.User;
import com.liferay.portal.security.permission.ActionKeys;
import com.liferay.portal.security.permission.PermissionChecker;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.asset.model.AssetCategory;
import com.liferay.portlet.asset.model.AssetCategoryConstants;
import com.liferay.portlet.asset.model.AssetCategoryDisplay;
import com.liferay.portlet.asset.model.AssetVocabulary;
import com.liferay.portlet.asset.service.AssetCategoryLocalServiceUtil;
import com.liferay.portlet.asset.service.base.AssetCategoryServiceBaseImpl;
import com.liferay.portlet.asset.service.permission.AssetCategoryPermission;
import com.liferay.util.Autocomplete;
import com.liferay.util.dao.orm.CustomSQLUtil;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Provides the remote service for accessing, adding, deleting, moving, and
 * updating asset categories. Its methods include permission checks.
 *
 * @author Brian Wing Shun Chan
 * @author Jorge Ferrer
 * @author Alvaro del Castillo
 * @author Eduardo Lundgren
 * @author Bruno Farache
 */
public class AssetCategoryServiceImpl extends AssetCategoryServiceBaseImpl {

	/**
	 * @deprecated As of 6.2.0, Replaced by {@link #addCategory(long, long, Map,
	 *             Map, long, String[], ServiceContext)}
	 */
	@Deprecated
	@Override
	public AssetCategory addCategory(
			long parentCategoryId, Map<Locale, String> titleMap,
			Map<Locale, String> descriptionMap, long vocabularyId,
			String[] categoryProperties, ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), serviceContext.getScopeGroupId(),
			parentCategoryId, ActionKeys.ADD_CATEGORY);

		return assetCategoryLocalService.addCategory(
			getUserId(), parentCategoryId, titleMap, descriptionMap,
			vocabularyId, categoryProperties, serviceContext);
	}

	@Override
	public AssetCategory addCategory(
			long groupId, long parentCategoryId, Map<Locale, String> titleMap,
			Map<Locale, String> descriptionMap, long vocabularyId,
			String[] categoryProperties, ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), groupId, parentCategoryId,
			ActionKeys.ADD_CATEGORY);

		return assetCategoryLocalService.addCategory(
			getUserId(), groupId, parentCategoryId, titleMap, descriptionMap,
			vocabularyId, categoryProperties, serviceContext);
	}

	/**
	 * @deprecated As of 6.2.0, Replaced by {@link #addCategory(long, String,
	 *             long, ServiceContext)}
	 */
	@Deprecated
	@Override
	public AssetCategory addCategory(
			String title, long vocabularyId, ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), serviceContext.getScopeGroupId(),
			AssetCategoryConstants.DEFAULT_PARENT_CATEGORY_ID,
			ActionKeys.ADD_CATEGORY);

		return assetCategoryLocalService.addCategory(
			getUserId(), title, vocabularyId, serviceContext);
	}

	@Override
	public AssetCategory addCategory(
			long groupId, String title, long vocabularyId,
			ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), groupId,
			AssetCategoryConstants.DEFAULT_PARENT_CATEGORY_ID,
			ActionKeys.ADD_CATEGORY);

		return assetCategoryLocalService.addCategory(
			getUserId(), groupId, title, vocabularyId, serviceContext);
	}

	/**
	 * @deprecated As of 6.2.0, Replaced by {@link #deleteCategories(long[],
	 *             ServiceContext)}
	 */
	@Deprecated
	@Override
	public void deleteCategories(long[] categoryIds)
		throws PortalException, SystemException {

		deleteCategories(categoryIds, null);
	}

	@Override
	public List<AssetCategory> deleteCategories(
			long[] categoryIds, ServiceContext serviceContext)
		throws PortalException, SystemException {

		List<AssetCategory> failedCategories = new ArrayList<AssetCategory>();

		for (long categoryId : categoryIds) {
			try {
				AssetCategoryPermission.check(
					getPermissionChecker(), categoryId, ActionKeys.DELETE);

				assetCategoryLocalService.deleteCategory(categoryId);
			}
			catch (PortalException pe) {
				if (serviceContext == null) {
					return null;
				}

				if (serviceContext.isFailOnPortalException()) {
					throw pe;
				}

				AssetCategory category =
					assetCategoryPersistence.fetchByPrimaryKey(categoryId);

				if (category == null) {
					category = assetCategoryPersistence.create(categoryId);
				}

				failedCategories.add(category);
			}
		}

		return failedCategories;
	}

	@Override
	public void deleteCategory(long categoryId)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), categoryId, ActionKeys.DELETE);

		assetCategoryLocalService.deleteCategory(categoryId);
	}

	@Override
	public List<AssetCategory> getCategories(String className, long classPK)
		throws PortalException, SystemException {

		return filterCategories(
			assetCategoryLocalService.getCategories(className, classPK));
	}

	@Override
	public AssetCategory getCategory(long categoryId)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), categoryId, ActionKeys.VIEW);

		return assetCategoryLocalService.getCategory(categoryId);
	}

	@Override
	public List<AssetCategory> getChildCategories(long parentCategoryId)
		throws PortalException, SystemException {

		return filterCategories(
			assetCategoryLocalService.getChildCategories(parentCategoryId));
	}

	@Override
	public List<AssetCategory> getChildCategories(
			long parentCategoryId, int start, int end, OrderByComparator obc)
		throws PortalException, SystemException {

		return filterCategories(
			assetCategoryLocalService.getChildCategories(
				parentCategoryId, start, end, obc));
	}

	/**
	 * @deprecated As of 6.2.0, replaced by {@link #search(long[], String,
	 *             long[], int, int)}
	 */
	@Deprecated
	@Override
	public JSONArray getJSONSearch(
			long groupId, String name, long[] vocabularyIds, int start, int end)
		throws PortalException, SystemException {

		return search(new long[] {groupId}, name, vocabularyIds, start, end);
	}

	/**
	 * @deprecated As of 6.2.0, replaced by {@link
	 *             #getVocabularyCategoriesDisplay(long, int, int,
	 *             OrderByComparator)}
	 */
	@Deprecated
	@Override
	public JSONObject getJSONVocabularyCategories(
			long vocabularyId, int start, int end, OrderByComparator obc)
		throws PortalException, SystemException {

		JSONObject jsonObject = JSONFactoryUtil.createJSONObject();

		List<AssetCategory> categories = filterCategories(
			assetCategoryLocalService.getVocabularyCategories(
				vocabularyId, start, end, obc));

		jsonObject.put("categories", toJSONArray(categories));
		jsonObject.put("total", categories.size());

		return jsonObject;
	}

	/**
	 * @deprecated As of 6.2.0, replaced by {@link
	 *             #getVocabularyCategoriesDisplay(long, String, long, int, int,
	 *             OrderByComparator)}
	 */
	@Deprecated
	@Override
	public JSONObject getJSONVocabularyCategories(
			long groupId, String title, long vocabularyId, int start, int end,
			OrderByComparator obc)
		throws PortalException, SystemException {

		JSONObject jsonObject = JSONFactoryUtil.createJSONObject();

		int page = 0;

		if ((end > 0) && (start > 0)) {
			page = end / (end - start);
		}

		jsonObject.put("page", page);

		List<AssetCategory> categories;
		int total = 0;

		if (Validator.isNotNull(title)) {
			categories = searchCategories(
				new long[] {groupId}, title, new long[] {vocabularyId}, start,
				end);
			total = categories.size();
		}
		else {
			categories = getVocabularyCategories(vocabularyId, start, end, obc);
			total = getVocabularyCategoriesCount(groupId, vocabularyId);
		}

		jsonObject.put("categories", toJSONArray(categories));
		jsonObject.put("total", total);

		return jsonObject;
	}

	@Override
	public List<AssetCategory> getVocabularyCategories(
			long vocabularyId, int start, int end, OrderByComparator obc)
		throws PortalException, SystemException {

		return filterCategories(
			assetCategoryLocalService.getVocabularyCategories(
				vocabularyId, start, end, obc));
	}

	@Override
	public List<AssetCategory> getVocabularyCategories(
			long parentCategoryId, long vocabularyId, int start, int end,
			OrderByComparator obc)
		throws PortalException, SystemException {

		return filterCategories(
			assetCategoryLocalService.getVocabularyCategories(
				parentCategoryId, vocabularyId, start, end, obc));
	}

	@Override
	public List<AssetCategory> getVocabularyCategories(
			long groupId, String name, long vocabularyId, int start, int end,
			OrderByComparator obc)
		throws SystemException {

		if (Validator.isNull(name)) {
			return assetCategoryPersistence.filterFindByG_V(
				groupId, vocabularyId, start, end, obc);
		}
		else {
			return assetCategoryPersistence.filterFindByG_LikeN_V(
				groupId, name, vocabularyId, start, end, obc);
		}
	}

	@Override
	public int getVocabularyCategoriesCount(long groupId, long vocabularyId)
		throws SystemException {

		return assetCategoryPersistence.filterCountByG_V(groupId, vocabularyId);
	}

	@Override
	public int getVocabularyCategoriesCount(
			long groupId, String name, long vocabularyId)
		throws SystemException {

		if (Validator.isNull(name)) {
			return assetCategoryPersistence.filterCountByG_V(
				groupId, vocabularyId);
		}
		else {
			return assetCategoryPersistence.filterCountByG_LikeN_V(
				groupId, name, vocabularyId);
		}
	}

	@Override
	public AssetCategoryDisplay getVocabularyCategoriesDisplay(
			long vocabularyId, int start, int end, OrderByComparator obc)
		throws PortalException, SystemException {

		List<AssetCategory> categories = filterCategories(
			assetCategoryLocalService.getVocabularyCategories(
				vocabularyId, start, end, obc));

		return new AssetCategoryDisplay(
			categories, categories.size(), start, end);
	}

	@Override
	public AssetCategoryDisplay getVocabularyCategoriesDisplay(
			long groupId, String name, long vocabularyId, int start, int end,
			OrderByComparator obc)
		throws PortalException, SystemException {

		List<AssetCategory> categories = null;
		int total = 0;

		if (Validator.isNotNull(name)) {
			name = (CustomSQLUtil.keywords(name))[0];

			categories = getVocabularyCategories(
				groupId, name, vocabularyId, start, end, obc);
			total = getVocabularyCategoriesCount(groupId, name, vocabularyId);
		}
		else {
			categories = getVocabularyCategories(vocabularyId, start, end, obc);
			total = getVocabularyCategoriesCount(groupId, vocabularyId);
		}

		return new AssetCategoryDisplay(categories, total, start, end);
	}

	/**
	 * @deprecated As of 6.2.0, replaced by {@link
	 *             #getVocabularyRootCategories(long, long, int, int,
	 *             OrderByComparator)}
	 */
	@Deprecated
	@Override
	public List<AssetCategory> getVocabularyRootCategories(
			long vocabularyId, int start, int end, OrderByComparator obc)
		throws PortalException, SystemException {

		AssetVocabulary vocabulary = assetVocabularyLocalService.getVocabulary(
			vocabularyId);

		return getVocabularyRootCategories(
			vocabulary.getGroupId(), vocabularyId, start, end, obc);
	}

	@Override
	public List<AssetCategory> getVocabularyRootCategories(
			long groupId, long vocabularyId, int start, int end,
			OrderByComparator obc)
		throws SystemException {

		return assetCategoryPersistence.filterFindByG_P_V(
			groupId, AssetCategoryConstants.DEFAULT_PARENT_CATEGORY_ID,
			vocabularyId, start, end, obc);
	}

	@Override
	public int getVocabularyRootCategoriesCount(long groupId, long vocabularyId)
		throws SystemException {

		return assetCategoryPersistence.filterCountByG_P_V(
			groupId, AssetCategoryConstants.DEFAULT_PARENT_CATEGORY_ID,
			vocabularyId);
	}

	@Override
	public AssetCategory moveCategory(
			long categoryId, long parentCategoryId, long vocabularyId,
			ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), categoryId, ActionKeys.UPDATE);

		return assetCategoryLocalService.moveCategory(
			categoryId, parentCategoryId, vocabularyId, serviceContext);
	}

	@Override
	public List<AssetCategory> search(
			long groupId, String keywords, long vocabularyId, int start,
			int end, OrderByComparator obc)
		throws SystemException {

		String name = CustomSQLUtil.keywords(keywords)[0];

		if (Validator.isNull(name)) {
			return assetCategoryPersistence.filterFindByG_V(
				groupId, vocabularyId, start, end, obc);
		}
		else {
			return assetCategoryPersistence.filterFindByG_LikeN_V(
				groupId, name, vocabularyId, start, end, obc);
		}
	}

	@Override
	public JSONArray search(
			long groupId, String name, String[] categoryProperties, int start,
			int end)
		throws PortalException, SystemException {

		List<AssetCategory> categories = assetCategoryLocalService.search(
			groupId, name, categoryProperties, start, end);

		categories = filterCategories(categories);

		return Autocomplete.listToJson(categories, "name", "name");
	}

	@Override
	public JSONArray search(
			long[] groupIds, String title, long[] vocabularyIds, int start,
			int end)
		throws PortalException, SystemException {

		List<AssetCategory> categories = searchCategories(
			groupIds, title, vocabularyIds, start, end);

		return toJSONArray(categories);
	}

	@Override
	public AssetCategory updateCategory(
			long categoryId, long parentCategoryId,
			Map<Locale, String> titleMap, Map<Locale, String> descriptionMap,
			long vocabularyId, String[] categoryProperties,
			ServiceContext serviceContext)
		throws PortalException, SystemException {

		AssetCategoryPermission.check(
			getPermissionChecker(), categoryId, ActionKeys.UPDATE);

		return assetCategoryLocalService.updateCategory(
			getUserId(), categoryId, parentCategoryId, titleMap, descriptionMap,
			vocabularyId, categoryProperties, serviceContext);
	}

	protected SearchContext buildSearchContext(
		long companyId, long[] groupIds, String title, long[] vocabularyIds,
		int start, int end) {

		SearchContext searchContext = new SearchContext();

		Map<String, Serializable> attributes =
			new HashMap<String, Serializable>();

		attributes.put(Field.ASSET_VOCABULARY_IDS, vocabularyIds);
		attributes.put(Field.TITLE, title);

		searchContext.setAttributes(attributes);

		searchContext.setCompanyId(companyId);
		searchContext.setEnd(end);
		searchContext.setGroupIds(groupIds);
		searchContext.setKeywords(title);

		QueryConfig queryConfig = new QueryConfig();

		queryConfig.setHighlightEnabled(false);
		queryConfig.setScoreEnabled(false);

		searchContext.setQueryConfig(queryConfig);

		searchContext.setStart(start);

		return searchContext;
	}

	protected List<AssetCategory> filterCategories(
			List<AssetCategory> categories)
		throws PortalException, SystemException {

		PermissionChecker permissionChecker = getPermissionChecker();

		categories = ListUtil.copy(categories);

		Iterator<AssetCategory> itr = categories.iterator();

		while (itr.hasNext()) {
			AssetCategory category = itr.next();

			if (!AssetCategoryPermission.contains(
					permissionChecker, category, ActionKeys.VIEW)) {

				itr.remove();
			}
		}

		return categories;
	}

	protected List<AssetCategory> searchCategories(
			long[] groupIds, String title, long[] vocabularyIds, int start,
			int end)
		throws PortalException, SystemException {

		User user = getUser();

		SearchContext searchContext = buildSearchContext(
			user.getCompanyId(), groupIds, title, vocabularyIds, start, end);

		Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
			AssetCategory.class);

		for (int i = 0; i < 10; i++) {
			Hits hits = indexer.search(searchContext);

			List<Document> documents = hits.toList();

			List<AssetCategory> categories = new ArrayList<AssetCategory>(
			documents.size());

			for (Document document : documents) {
				long categoryId = GetterUtil.getLong(
					document.get(Field.ASSET_CATEGORY_ID));

				AssetCategory category =
					AssetCategoryLocalServiceUtil.getCategory(categoryId);

				if (category == null) {
					categories = null;

					long companyId = GetterUtil.getLong(
						document.get(Field.COMPANY_ID));

					indexer.delete(companyId, document.getUID());
				}
				else if (categories != null) {
					categories.add(category);
				}
			}

			if (categories != null) {
				return categories;
			}
		}

		throw new SearchException(
			"Unable to fix the search index after 10 attempts");
	}

	protected JSONArray toJSONArray(List<AssetCategory> categories)
		throws PortalException, SystemException {

		JSONArray jsonArray = JSONFactoryUtil.createJSONArray();

		for (AssetCategory category : categories) {
			String categoryJSON = JSONFactoryUtil.looseSerialize(category);

			JSONObject categoryJSONObject = JSONFactoryUtil.createJSONObject(
				categoryJSON);

			List<String> names = new ArrayList<String>();

			AssetCategory curCategory = category;

			while (curCategory.getParentCategoryId() > 0) {
				AssetCategory parentCategory = getCategory(
					curCategory.getParentCategoryId());

				names.add(parentCategory.getName());
				names.add(
					StringPool.SPACE + StringPool.GREATER_THAN +
						StringPool.SPACE);

				curCategory = parentCategory;
			}

			Collections.reverse(names);

			AssetVocabulary vocabulary = assetVocabularyService.getVocabulary(
				category.getVocabularyId());

			StringBundler sb = new StringBundler(1 + names.size());

			sb.append(vocabulary.getTitleCurrentValue());
			sb.append(names.toArray(new String[names.size()]));

			categoryJSONObject.put("path", sb.toString());

			jsonArray.put(categoryJSONObject);
		}

		return jsonArray;
	}

}