/*******************************************************************************
 * 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
 * IBM Corporation
 *******************************************************************************/
package org.eclipse.cdt.build.internal.core.scannerconfig;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.cdt.build.core.scannerconfig.CfgInfoContext;
import org.eclipse.cdt.build.core.scannerconfig.ICfgScannerConfigBuilderInfo2Set;
import org.eclipse.cdt.build.core.scannerconfig.ScannerConfigBuilder;
import org.eclipse.cdt.build.internal.core.scannerconfig.PerFileSettingsCalculator.ILangSettingInfo;
import org.eclipse.cdt.build.internal.core.scannerconfig.PerFileSettingsCalculator.IRcSettingInfo;
import org.eclipse.cdt.build.internal.core.scannerconfig2.CfgScannerConfigProfileManager;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICSettingBase;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.extension.CFolderData;
import org.eclipse.cdt.core.settings.model.extension.CResourceData;
import org.eclipse.cdt.make.core.MakeCorePlugin;
import org.eclipse.cdt.make.core.scannerconfig.IDiscoveredPathManager;
import org.eclipse.cdt.make.core.scannerconfig.IScannerConfigBuilderInfo2;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector2;
import org.eclipse.cdt.make.core.scannerconfig.PathInfo;
import org.eclipse.cdt.make.internal.core.scannerconfig.DiscoveredScannerInfoStore;
import org.eclipse.cdt.make.internal.core.scannerconfig2.PerFileSICollector;
import org.eclipse.cdt.make.internal.core.scannerconfig2.SCProfileInstance;
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.IInputType;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.cdt.managedbuilder.internal.core.Configuration;
import org.eclipse.cdt.managedbuilder.internal.core.FolderInfo;
import org.eclipse.cdt.managedbuilder.internal.core.Tool;
import org.eclipse.cdt.managedbuilder.internal.dataprovider.BuildFileData;
import org.eclipse.cdt.managedbuilder.internal.dataprovider.BuildFolderData;
import org.eclipse.cdt.managedbuilder.internal.dataprovider.BuildLanguageData;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;

public class CfgDiscoveredPathManager implements IResourceChangeListener {

	public static CfgDiscoveredPathManager fInstance;

	private IDiscoveredPathManager fBaseMngr;

	/** Deadlock-safe mutex lock */
	private ILock lock = Job.getJobManager().newLock();

	private static class ContextInfo {

		public ContextInfo() {
		}

		CfgInfoContext fInitialContext;
		CfgInfoContext fCacheContext;
		CfgInfoContext fLoadContext;
		ICfgScannerConfigBuilderInfo2Set fCfgInfo;
		IScannerConfigBuilderInfo2 fInfo;
		boolean fIsPerFileCache;
	}

	public static class PathInfoCache {
		private PathInfo fPathInfo;
		private String fProfileId;

		public PathInfo getPathInfo() {
			return fPathInfo;
		}

		private PathInfoCache(String profileId, PathInfo pathInfo) {
			this.fProfileId = profileId;
			this.fPathInfo = pathInfo;
		}
	}

	private CfgDiscoveredPathManager() {
		fBaseMngr = MakeCorePlugin.getDefault().getDiscoveryManager();
	}

	public static CfgDiscoveredPathManager getInstance() {
		if (fInstance == null) {
			fInstance = new CfgDiscoveredPathManager();
			fInstance.startup();
		}
		return fInstance;
	}

	public static void stop() {
		if (fInstance != null)
			fInstance.shutdown();
	}

	public void startup() {
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}

	public void shutdown() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
	 */
	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		if (event.getSource() instanceof IWorkspace) {
			IResource resource = event.getResource();

			switch (event.getType()) {
			case IResourceChangeEvent.POST_CHANGE:
				//                    DiscoveredScannerInfoStore.getInstance().updateScannerConfigStore(event.getDelta());
				break;
			case IResourceChangeEvent.PRE_DELETE:
			case IResourceChangeEvent.PRE_CLOSE:
				if (resource.getType() == IResource.PROJECT) {
					//						fDiscoveredMap.remove(resource);
				}
				break;
			}
		}
	}

	public void updateCoreSettings(final IProject project, final IConfiguration cfgs[]) {
		try {
			IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
				@Override
				public void run(IProgressMonitor monitor) throws CoreException {
					ManagedBuildManager.updateCoreSettings(project, cfgs, true);
				}
			};
			CoreModel.run(runnable, null);
		} catch (CoreException e) {
			ManagedBuilderCorePlugin.log(e);
		}
	}

	public PathInfo getDiscoveredInfo(IProject project, CfgInfoContext context) throws CoreException {

		ContextInfo cInfo = getContextInfo(context);

		PathInfo info = getCachedPathInfo(cInfo);
		if (info == null) {
			try {
				lock.acquire();
				info = getCachedPathInfo(cInfo);

				if (info == null) {
					IDiscoveredPathManager.IDiscoveredPathInfo baseInfo = loadPathInfo(project,
							context.getConfiguration(), cInfo);
					info = resolveCacheBaseDiscoveredInfo(cInfo, baseInfo);
				}
			} finally {
				lock.release();
			}
		}
		return info;
	}

	//	private void adjustPerRcContextInfo(ContextInfo cInfo){
	//		cInfo.fIsFerFileCache = true;
	//		cInfo.fCacheContext = cInfo.fInitialContext;
	//		cInfo.fLoadContext = new CfgInfoContext(cInfo.fInitialContext.getConfiguration());
	//	}

	private PathInfo resolveCacheBaseDiscoveredInfo(ContextInfo cInfo,
			IDiscoveredPathManager.IDiscoveredPathInfo baseInfo) {
		if (cInfo.fIsPerFileCache) {
			if (baseInfo instanceof IDiscoveredPathManager.IPerFileDiscoveredPathInfo2) {
				resolveCachePerFileInfo(cInfo, (IDiscoveredPathManager.IPerFileDiscoveredPathInfo2) baseInfo);
			}
			return getCachedPathInfo(cInfo);
		}

		((FolderInfo) cInfo.fLoadContext.getConfiguration().getRootFolderInfo()).setContainsDiscoveredScannerInfo(true);
		Map<String, String> map = baseInfo.getSymbols();
		IPath paths[] = baseInfo.getIncludePaths();

		PathInfo info = new PathInfo(paths, null, map, null, null);
		setCachedPathInfo(cInfo, info);
		return info;
	}

	private void resolveCachePerFileInfo(ContextInfo cInfo, IDiscoveredPathManager.IPerFileDiscoveredPathInfo2 info) {
		CConfigurationData data = cInfo.fLoadContext.getConfiguration().getConfigurationData();
		if (data == null)
			return;

		PerFileSettingsCalculator calculator = new PerFileSettingsCalculator();
		IRcSettingInfo[] rcInfos = calculator
				.getSettingInfos(cInfo.fLoadContext.getConfiguration().getOwner().getProject(), data, info, true);

		CResourceData rcDatas[] = data.getResourceDatas();
		Map<IPath, CResourceData> rcDataMap = new HashMap<>();
		CResourceData rcData;
		for (int i = 0; i < rcDatas.length; i++) {
			rcData = rcDatas[i];
			rcDataMap.put(rcData.getPath(), rcData);
		}

		IRcSettingInfo rcInfo;
		IPath path;
		boolean rootSettingFound = false;
		boolean fileSettingFound = false;
		for (int i = 0; i < rcInfos.length; i++) {
			rcInfo = rcInfos[i];
			rcData = rcInfo.getResourceData();
			path = rcData.getPath();
			if (path.segmentCount() != 0)
				fileSettingFound = true;
			else
				rootSettingFound = true;

			rcDataMap.remove(path);
			cache(cInfo, rcInfo);
		}

		if (rootSettingFound && fileSettingFound)
			((BuildFolderData) data.getRootFolderData()).setContainsDiscoveredScannerInfo(false);

		if (!rcDataMap.isEmpty()) {
			CResourceData tmpRcData;
			for (Iterator<CResourceData> iter = rcDataMap.values().iterator(); iter.hasNext();) {
				tmpRcData = iter.next();
				if (tmpRcData.getPath().segmentCount() == 0 && tmpRcData.getType() == ICSettingBase.SETTING_FOLDER) {
					cache(cInfo, PerFileSettingsCalculator.createEmptyRcSettingInfo((CFolderData) tmpRcData));
				} else {
					clearCache(tmpRcData);
				}
			}
		}
	}

	private void cache(ContextInfo cInfo, IRcSettingInfo rcSetting) {
		CResourceData rcData = rcSetting.getResourceData();
		clearCache(rcData);
		ILangSettingInfo lInfos[] = rcSetting.getLangInfos();
		for (int i = 0; i < lInfos.length; i++) {
			cache(cInfo, lInfos[i]);
		}
	}

	private void cache(ContextInfo cInfo, ILangSettingInfo lInfo) {
		BuildLanguageData bld = (BuildLanguageData) lInfo.getLanguageData();
		setCachedPathInfo(cInfo, (Configuration) bld.getConfiguration(), (Tool) bld.getTool(), bld.getInputType(),
				lInfo.getFilePathInfo());
	}

	private void clearCache(CResourceData rcData) {
		if (rcData.getType() == ICSettingBase.SETTING_FILE) {
			IFileInfo fiInfo = ((BuildFileData) rcData).getFileInfo();
			ITool tools[] = fiInfo.getTools();
			clearCache(tools);
		} else {
			IFolderInfo foInfo = ((BuildFolderData) rcData).getFolderInfo();
			ITool[] tools = foInfo.getTools();
			clearCache(tools);
		}
	}

	private void clearCache(ITool[] tools) {
		for (int i = 0; i < tools.length; i++) {
			((Tool) tools[i]).clearAllDiscoveredInfo();
		}
	}

	private IDiscoveredPathManager.IDiscoveredPathInfo loadPathInfo(IProject project, IConfiguration cfg,
			ContextInfo cInfo) throws CoreException {
		IDiscoveredPathManager.IDiscoveredPathInfo info = fBaseMngr.getDiscoveredInfo(cfg.getOwner().getProject(),
				cInfo.fLoadContext.toInfoContext(), false);
		if (!DiscoveredScannerInfoStore.getInstance().hasInfo(project, cInfo.fLoadContext.toInfoContext(),
				info.getSerializable())) {
			//			setCachedPathInfo(context, info);
			ICfgScannerConfigBuilderInfo2Set container = cInfo.fCfgInfo;
			IScannerConfigBuilderInfo2 buildInfo = container.getInfo(cInfo.fLoadContext);
			if (buildInfo != null) {
				SCProfileInstance instance = ScannerConfigBuilder.build(cInfo.fLoadContext, buildInfo, 0, null,
						new NullProgressMonitor());
				if (instance != null) {

					IScannerInfoCollector newC = instance.getScannerInfoCollector();
					if (newC instanceof IScannerInfoCollector2 && !(newC instanceof PerFileSICollector)) {
						info = ((IScannerInfoCollector2) newC).createPathInfoObject();
						//        				setCachedPathInfo(context, info);
					}
				}
			}
		}
		return info;
	}

	private PathInfo getCachedPathInfo(ContextInfo cInfo) {
		PathInfo info = getCachedPathInfo(cInfo, true, true, false);
		return info;
	}

	private void removeCachedPathInfo(ContextInfo cInfo) {
		//      ICfgScannerConfigBuilderInfo2Set cfgInfo = cInfo.fCfgInfo;
		if (cInfo.fIsPerFileCache) {
			Configuration cfg = (Configuration) cInfo.fInitialContext.getConfiguration();
			cfg.clearDiscoveredPathInfo();

			IResourceInfo[] infos = cfg.getResourceInfos();
			for (int i = 0; i < infos.length; i++) {
				IResourceInfo rcInfo = infos[i];
				ITool[] tools = rcInfo.getTools();
				for (int k = 0; k < tools.length; k++) {
					Tool tool = (Tool) tools[k];
					tool.clearAllDiscoveredPathInfo();
				}
			}
		} else {
			setCachedPathInfo(cInfo, null);
		}

		//      PathInfo info = getCachedPathInfo(cInfo, true, true, true);
	}

	private PathInfo getCachedPathInfo(ContextInfo cInfo, boolean queryParent, boolean clearIfInvalid, boolean clear) {
		return getCachedPathInfo(cInfo, (Configuration) cInfo.fCacheContext.getConfiguration(),
				(Tool) cInfo.fCacheContext.getTool(), cInfo.fCacheContext.getInputType(), queryParent, clearIfInvalid,
				clear);
	}

	private PathInfo getCachedPathInfo(ContextInfo cInfo, Configuration cfg, Tool tool, IInputType inType,
			boolean queryParent, boolean clearIfInvalid, boolean clear) {
		PathInfoCache infoCache = getPathInfoCache(cInfo, cfg, tool, inType, queryParent, clearIfInvalid, clear);
		if (infoCache != null && isCacheValid(cInfo, infoCache))
			return infoCache.fPathInfo;
		return null;
	}

	private PathInfoCache getPathInfoCache(ContextInfo cInfo, Configuration cfg, Tool tool, IInputType inType,
			boolean queryParent, boolean clearIfInvalid, boolean clear) {
		PathInfoCache info = null;
		//		boolean queryCfg = false;
		if (tool != null) {
			info = tool.getDiscoveredPathInfo(inType);
			if (info != null) {
				if (clear || (clearIfInvalid && !isCacheValid(cInfo, info))) {
					tool.clearDiscoveredPathInfo(inType);
					//					fBaseMngr.removeDiscoveredInfo(cfg.getOwner().getProject(), cInfo.fLoadContext.toInfoContext());
				}
			} else if (queryParent) {
				//				IResourceInfo rcInfo = tool.getParentResourceInfo();
				ITool superTool = tool.getSuperClass();
				if (superTool != null && !superTool.isExtensionElement()) {
					if (inType != null) {
						IInputType superInType = null;
						String exts[] = inType.getSourceExtensions(tool);
						for (int i = 0; i < exts.length; i++) {
							superInType = superTool.getInputType(exts[i]);
							if (superInType != null)
								break;
						}
						if (superInType != null) {
							info = getPathInfoCache(cInfo, cfg, (Tool) superTool, superInType, true, clearIfInvalid,
									clear);
						}
					} else {
						info = getPathInfoCache(cInfo, cfg, (Tool) superTool, null, true, clearIfInvalid, clear);
					}
				} else {
					info = getPathInfoCache(cInfo, cfg, null, null, true, clearIfInvalid, clear);
				}
			}
		} else {
			info = cfg.getDiscoveredPathInfo();
			if (clear || (clearIfInvalid && !isCacheValid(cInfo, info))) {
				cfg.clearDiscoveredPathInfo();
				//				fBaseMngr.removeDiscoveredInfo(cfg.getOwner().getProject(), cInfo.fLoadContext.toInfoContext());
			}
		}

		return info;
	}

	private boolean isCacheValid(ContextInfo cInfo, PathInfoCache cache) {
		if (cache == null)
			return true;

		if (cInfo.fInfo != null) {
			String id = cInfo.fInfo.getSelectedProfileId();
			return id.equals(cache.fProfileId);
		}
		return false;
	}

	private ContextInfo getContextInfo(CfgInfoContext context) {
		return getContextInfo(context, null);
	}

	private ContextInfo getContextInfo(CfgInfoContext context, ICfgScannerConfigBuilderInfo2Set cfgInfo) {
		if (cfgInfo == null)
			cfgInfo = CfgScannerConfigProfileManager.getCfgScannerConfigBuildInfo(context.getConfiguration());

		boolean isPerRcType = cfgInfo.isPerRcTypeDiscovery();
		ContextInfo contextInfo = new ContextInfo();
		contextInfo.fInitialContext = context;
		contextInfo.fCfgInfo = cfgInfo;
		if (isPerRcType) {
			contextInfo.fLoadContext = CfgScannerConfigUtil.adjustPerRcTypeContext(contextInfo.fInitialContext);
			contextInfo.fCacheContext = contextInfo.fLoadContext;
			contextInfo.fIsPerFileCache = false;
			contextInfo.fInfo = cfgInfo.getInfo(contextInfo.fLoadContext);
		} else {
			contextInfo.fLoadContext = new CfgInfoContext(context.getConfiguration());
			contextInfo.fInfo = cfgInfo.getInfo(contextInfo.fLoadContext);
			contextInfo.fIsPerFileCache = CfgScannerConfigProfileManager
					.isPerFileProfile(contextInfo.fInfo.getSelectedProfileId());
			contextInfo.fCacheContext = contextInfo.fIsPerFileCache ? contextInfo.fInitialContext
					: contextInfo.fLoadContext;
		}

		return contextInfo;
	}

	private PathInfo setCachedPathInfo(ContextInfo cInfo, PathInfo info) {
		CfgInfoContext cacheContext = cInfo.fCacheContext;
		return setCachedPathInfo(cInfo, (Configuration) cacheContext.getConfiguration(), (Tool) cacheContext.getTool(),
				cacheContext.getInputType(), info);
	}

	private PathInfo setCachedPathInfo(ContextInfo cInfo, Configuration cfg, Tool tool, IInputType inType,
			PathInfo info) {
		PathInfoCache oldInfo;
		PathInfoCache cache;
		if (info != null) {
			String id = cInfo.fInfo != null ? cInfo.fInfo.getSelectedProfileId() : null;
			cache = new PathInfoCache(id, info);
		} else {
			cache = null;
		}

		if (tool != null) {
			if (info != null)
				oldInfo = tool.setDiscoveredPathInfo(inType, cache);
			else
				oldInfo = tool.clearDiscoveredPathInfo(inType);
		} else {
			if (info != null)
				oldInfo = cfg.setDiscoveredPathInfo(cache);
			else
				oldInfo = cfg.clearDiscoveredPathInfo();
		}
		return oldInfo != null ? oldInfo.fPathInfo : null;
	}

	public void removeDiscoveredInfo(IProject project, CfgInfoContext context) {
		removeDiscoveredInfo(project, context, true);
	}

	public void removeDiscoveredInfo(IProject project, CfgInfoContext context, boolean removeBaseCache) {
		//        if(context == null)
		//        	context = ScannerConfigUtil.createContextForProject(project);

		ContextInfo cInfo = getContextInfo(context);

		removeCachedPathInfo(cInfo);

		if (removeBaseCache)
			fBaseMngr.removeDiscoveredInfo(project, cInfo.fLoadContext.toInfoContext());
	}
}
