/*******************************************************************************
 * Copyright (c) 2007, 2011 Intel Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.internal.tcmodification;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IFileInfo;
import org.eclipse.cdt.managedbuilder.core.IFolderInfo;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.internal.core.FolderInfo;
import org.eclipse.cdt.managedbuilder.internal.core.IRealBuildObjectAssociation;
import org.eclipse.cdt.managedbuilder.internal.core.ResourceConfiguration;
import org.eclipse.cdt.managedbuilder.internal.tcmodification.extension.ObjectSetListBasedDefinition;
import org.eclipse.cdt.managedbuilder.internal.tcmodification.extension.RulesManager;
import org.eclipse.cdt.managedbuilder.tcmodification.IFileInfoModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IFolderInfoModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IToolChainModificationManager;
import org.eclipse.core.runtime.IPath;

public class ToolChainModificationManager implements IToolChainModificationManager {
	private static ToolChainModificationManager fInstance;

	private ToolChainModificationManager() {
	}

	public static ToolChainModificationManager getInstance() {
		if (fInstance == null)
			fInstance = getInstanceSynch();
		return fInstance;
	}

	private static synchronized ToolChainModificationManager getInstanceSynch() {
		if (fInstance == null)
			fInstance = new ToolChainModificationManager();
		return fInstance;
	}

	public void start() {
		RulesManager.getInstance().start();
	}

	@Override
	public IFileInfoModification createModification(IFileInfo rcInfo) {
		return new FileInfoModification((ResourceConfiguration) rcInfo);
	}

	@Override
	public IFolderInfoModification createModification(IFolderInfo rcInfo) {
		FolderInfo foInfo = (FolderInfo) rcInfo;
		if (foInfo.isRoot())
			return new ConfigurationModification(foInfo);
		return new FolderInfoModification(foInfo);
	}

	@Override
	public IFolderInfoModification createModification(IConfiguration cfg, IFolderInfoModification base)
			throws IllegalArgumentException {
		IResourceInfo baseRcInfo = base.getResourceInfo();
		IPath path = baseRcInfo.getPath();
		IResourceInfo rcInfo = cfg.getResourceInfo(path, true);
		FolderInfo folderInfo;
		if (rcInfo != null) {
			if (rcInfo instanceof FolderInfo) {
				folderInfo = (FolderInfo) rcInfo;
			} else {
				throw new IllegalArgumentException();
			}
		} else {
			folderInfo = (FolderInfo) cfg.createFolderInfo(path);
		}

		return folderInfo.isRoot() ? new ConfigurationModification(folderInfo, (ConfigurationModification) base)
				: new FolderInfoModification(folderInfo, (FolderInfoModification) base);
	}

	@Override
	public IFileInfoModification createModification(IConfiguration cfg, IFileInfoModification base)
			throws IllegalArgumentException {
		IResourceInfo baseRcInfo = base.getResourceInfo();
		IPath path = baseRcInfo.getPath();
		IResourceInfo rcInfo = cfg.getResourceInfo(path, true);
		ResourceConfiguration fileInfo;
		if (rcInfo != null) {
			if (rcInfo instanceof ResourceConfiguration) {
				fileInfo = (ResourceConfiguration) rcInfo;
			} else {
				throw new IllegalArgumentException();
			}
		} else {
			fileInfo = (ResourceConfiguration) cfg.createFileInfo(path);
		}

		return new FileInfoModification(fileInfo, (FileInfoModification) base);
	}

	public static boolean checkFlags(int flags, int value) {
		return (flags & value) == value;
	}

	public static int addFlags(int flags, int value) {
		return flags |= value;
	}

	public static int clearFlags(int flags, int value) {
		return flags &= (~value);
	}

	private boolean getMatchingObjects(int type, IObjectSet[] oSets, Set<IRealBuildObjectAssociation> skipSet,
			IRealBuildObjectAssociation additionalSkip, Set<IRealBuildObjectAssociation> result) {
		Set<IRealBuildObjectAssociation> tmp = null;
		boolean added = false;
		for (IObjectSet os : oSets) {
			int setType = os.getObjectType();
			if (setType != type)
				continue;

			if (tmp == null)
				tmp = new HashSet<>();
			else
				tmp.clear();

			os.getRealBuildObjects(tmp);

			if (skipSet != null)
				tmp.removeAll(skipSet);

			if (additionalSkip != null)
				tmp.remove(additionalSkip);

			if (result.addAll(tmp)) {
				added = true;
			}
		}
		return added;
	}

	public ConflictMatchSet getConflictInfo(int objType,
			PerTypeMapStorage<IRealBuildObjectAssociation, Set<IPath>> parent) {
		//parent should be passed - it is constant no need to recalculate every time
		//PerTypeMapStorage parent = TcModificationUtil.createParentObjectsRealToolToPathSet(foInfo);

		ConflictMatchSet conflicts = getConflictMatches(objType, parent, null);

		return conflicts;
	}

	public static class ConflictMatch {
		final int fMatchType;
		final Map<IRealBuildObjectAssociation, Set<IPath>> fRObjToPathMap;
		final int fConflictType;
		final Set<IRealBuildObjectAssociation> fConflicts;

		ConflictMatch(int matchType, Map<IRealBuildObjectAssociation, Set<IPath>> rtToPathMap, int conflictType,
				Set<IRealBuildObjectAssociation> conflicts) {
			fMatchType = matchType;
			fRObjToPathMap = Collections.unmodifiableMap(rtToPathMap);
			fConflictType = conflictType;
			fConflicts = Collections.unmodifiableSet(conflicts);
		}
	}

	public static class ConflictMatchSet {
		ConflictMatch[] fConflicts;
		Map<? extends IRealBuildObjectAssociation, List<ConflictMatch>> fObjToConflictListMap;

		ConflictMatchSet(ConflictMatch[] coflicts,
				Map<? extends IRealBuildObjectAssociation, List<ConflictMatch>> objToConflictMap) {
			fConflicts = coflicts;
			fObjToConflictListMap = objToConflictMap;
		}

	}

	private ConflictMatchSet getConflictMatches(int type,
			PerTypeMapStorage<IRealBuildObjectAssociation, Set<IPath>> rtToPath,
			PerTypeSetStorage<IRealBuildObjectAssociation> skip) {

		//conversion:
		//1.first filter applicable to not-this
		//2. get variants for applicable ones

		//1.first filter applicable to not-this
		List<ConflictMatch> conflictList = new ArrayList<>();
		Map<IRealBuildObjectAssociation, List<ConflictMatch>> objToConflictMatchMap = new HashMap<>();

		ObjectSetListBasedDefinition[] defs = RulesManager.getInstance()
				.getRules(ObjectSetListBasedDefinition.CONFLICT);
		for (int i = 0; i < defs.length; i++) {
			ObjectSetListBasedDefinition def = defs[i];
			ObjectSetList sl = def.getObjectSetList();
			IObjectSet oss[] = sl.getObjectSets();
			for (int k = 0; k < oss.length; k++) {
				IObjectSet os = oss[k];
				int objType = os.getObjectType();
				Map<IRealBuildObjectAssociation, Set<IPath>> rtToPathMap = rtToPath.getMap(objType, false);
				if (rtToPathMap == null)
					continue;

				@SuppressWarnings("unchecked")
				Map<IRealBuildObjectAssociation, Set<IPath>> clone = (Map<IRealBuildObjectAssociation, Set<IPath>>) ((HashMap<IRealBuildObjectAssociation, Set<IPath>>) rtToPathMap)
						.clone();
				rtToPathMap = clone;
				Set<IRealBuildObjectAssociation> skipSet = skip != null
						? (Set<IRealBuildObjectAssociation>) skip.getSet(objType, false)
						: null;
				Set<IRealBuildObjectAssociation> objSet = rtToPathMap.keySet();

				if (skipSet != null)
					objSet.removeAll(skipSet);

				os.retainMatches(objSet);

				if (objSet.size() != 0) {
					List<IObjectSet> remainingList = new ArrayList<>(Arrays.asList(oss));
					remainingList.remove(os);

					IObjectSet[] remaining = remainingList.toArray(new IObjectSet[remainingList.size()]);
					//get objects matching remaining
					Set<IRealBuildObjectAssociation> skipSet2 = skip != null
							? (Set<IRealBuildObjectAssociation>) skip.getSet(type, false)
							: null;
					Set<IRealBuildObjectAssociation> matchingObjects = new HashSet<>();
					getMatchingObjects(type, remaining, skipSet2, null, matchingObjects);
					if (matchingObjects.size() != 0) {
						ConflictMatch conflict = new ConflictMatch(objType, rtToPathMap, type, matchingObjects);

						for (IRealBuildObjectAssociation bo : matchingObjects) {
							List<ConflictMatch> list = TcModificationUtil.getArrayList(objToConflictMatchMap, bo);
							list.add(conflict);
						}

						conflictList.add(conflict);
					}
					//					break;
				}
			}
		}

		return new ConflictMatchSet(conflictList.toArray(new ConflictMatch[conflictList.size()]),
				objToConflictMatchMap);
	}
}
