/*******************************************************************************
 * Copyright (c) 2007, 2013 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.internal.core.settings.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.cdtvariables.ICdtVariablesContributor;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvider;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsProvidersKeeper;
import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsManager;
import org.eclipse.cdt.core.language.settings.providers.ScannerDiscoveryLegacySupport;
import org.eclipse.cdt.core.settings.model.CConfigurationStatus;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICBuildSetting;
import org.eclipse.cdt.core.settings.model.ICConfigExtensionReference;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICExternalSetting;
import org.eclipse.cdt.core.settings.model.ICFileDescription;
import org.eclipse.cdt.core.settings.model.ICFolderDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSetting;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.core.settings.model.ICSettingBase;
import org.eclipse.cdt.core.settings.model.ICSettingContainer;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSettingObject;
import org.eclipse.cdt.core.settings.model.ICSourceEntry;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.core.settings.model.ICTargetPlatformSetting;
import org.eclipse.cdt.core.settings.model.IModificationContext;
import org.eclipse.cdt.core.settings.model.WriteAccessException;
import org.eclipse.cdt.core.settings.model.extension.CBuildData;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationDataProvider;
import org.eclipse.cdt.core.settings.model.extension.CFileData;
import org.eclipse.cdt.core.settings.model.extension.CFolderData;
import org.eclipse.cdt.core.settings.model.extension.CLanguageData;
import org.eclipse.cdt.core.settings.model.extension.CResourceData;
import org.eclipse.cdt.core.settings.model.extension.CTargetPlatformData;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultConfigurationData;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.core.settings.model.util.PathSettingsContainer;
import org.eclipse.cdt.internal.core.cdtvariables.CdtVariableManager;
import org.eclipse.cdt.internal.core.cdtvariables.StorableCdtVariables;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;

/**
 * CConfigurationDescriptionCache is a proxy class for serialization of configuration description data.
 *
 * An inspection of the scenario where user changes project properties and saves it yields
 * following sequence of events:
 * <ol>
 * <li> Initialization:
 *   <ul>
 *   <li> After eclipse started a project is being opened. A new CConfigurationDescriptionCache is created
 *        with CConfigurationDescriptionCache(ICStorageElement storage, CProjectDescription parent) constructor.
 *   <li> Any clients needed ICConfigurationDescription get CConfigurationDescription using constructor
 *        CConfigurationDescription(CConfigurationData data, String buildSystemId, ICDataProxyContainer cr)
 *        where the CConfigurationDescriptionCache is passed as data. The reference to cache is kept in field fCfgCache.
 *   <li> fCfgCache is used to getSpecSettings() CConfigurationSpecSettings, after that fCfgCache is set to null.
 *   </ul>
 * <li> User enters project properties/settings:
 *   <ul>
 *   <li> another CConfigurationDescription (settings configuration) created using the same constructor setting fCfgCache
 *        to the CConfigurationDescriptionCache.
 *   </ul>
 * <li> User changes settings (in the settings configuration CConfigurationDescription) and saves it:
 *   <ul>
 *   <li> new CConfigurationDescriptionCache is created from the CConfigurationDescription via constructor
 *        CConfigurationDescriptionCache(ICConfigurationDescription baseDescription, ...) where
 *        baseDescription is saved as fBaseDescription.
 *   <li> CConfigurationDescriptionCache.applyData(...) is used to persist the data. at that point
 *        reference fBaseDescription gets set to null.
 *   </ul>
 * </ol>
 *
 * @see ICConfigurationDescription
 * @see CConfigurationDescription
 * @see CProjectDescriptionEvent
 */
public class CConfigurationDescriptionCache extends CDefaultConfigurationData
		implements ICConfigurationDescription, IInternalCCfgInfo, ILanguageSettingsProvidersKeeper, ICachedData {

	private CProjectDescription fParent;
	private PathSettingsContainer fPathSettingContainer = PathSettingsContainer.createRootContainer();
	private ResourceDescriptionHolder fRcHolder = new ResourceDescriptionHolder(fPathSettingContainer, true);
	private List<ICSettingObject> fChildList = new ArrayList<>();
	private CConfigurationSpecSettings fSpecSettings;
	private CConfigurationData fData;
	private CConfigurationDescriptionCache fBaseCache;
	private ICSourceEntry fProjSourceEntries[];
	private StorableCdtVariables fMacros;
	private boolean fDataLoadded;
	private boolean fInitializing;
	private ICConfigurationDescription fBaseDescription;
	private ICSourceEntry[] fResolvedSourceEntries;

	CConfigurationDescriptionCache(ICStorageElement storage, CProjectDescription parent) throws CoreException {
		super(null);
		fInitializing = true;
		fParent = parent;
		fSpecSettings = new CConfigurationSpecSettings(this, storage);

		fId = fSpecSettings.getId();
		fName = fSpecSettings.getName();

		//		loadData();
	}

	public boolean isInitializing() {
		return fInitializing;
	}

	void loadData() throws CoreException {
		if (fDataLoadded)
			return;

		fDataLoadded = true;

		CConfigurationDataProvider dataProvider = CProjectDescriptionManager.getInstance().getProvider(this);
		fData = dataProvider.loadConfiguration(this, new NullProgressMonitor());

		if (getDefaultLanguageSettingsProvidersIds() == null) {
			// default ids would come from toolchain configuration, ensure sensible defaults
			String[] defaultIds = ScannerDiscoveryLegacySupport.getDefaultProviderIdsLegacy(this);
			setDefaultLanguageSettingsProvidersIds(defaultIds);
		}
		if (!fSpecSettings.isLanguageSettingProvidersLoaded()) {
			// when loading - providers come from xml file, ensure defaults if no xml file present
			String[] defaultIds = getDefaultLanguageSettingsProvidersIds();
			List<ILanguageSettingsProvider> providers = LanguageSettingsManager
					.createLanguageSettingsProviders(defaultIds);
			setLanguageSettingProviders(providers);
		}

		copySettingsFrom(fData, true);

		fSpecSettings.reconcileExtensionSettings(true);
		((CBuildSettingCache) fBuildData).initEnvironmentCache();
		ICdtVariable vars[] = CdtVariableManager.getDefault().getVariables(this);
		fMacros = new StorableCdtVariables(vars, true);
		//		fInitializing = false;
	}

	CConfigurationDescriptionCache(ICConfigurationDescription baseDescription, CConfigurationData base,
			CConfigurationDescriptionCache baseCache, CConfigurationSpecSettings settingsBase,
			CProjectDescription parent, ICStorageElement rootEl) throws CoreException {
		super(base.getId(), base.getName(), null);
		fInitializing = true;
		fParent = parent;
		fSpecSettings = new CConfigurationSpecSettings(this, settingsBase, rootEl);
		fSpecSettings.setModified(settingsBase.isModified());
		fBaseDescription = baseDescription;
		if (base instanceof CConfigurationDescriptionCache) {
			fData = ((CConfigurationDescriptionCache) base).getConfigurationData();
			//			fData = CProjectDescriptionManager.getInstance().applyData(this, baseDescription, fData);
		} else {
			fData = base;
			//			base = CProjectDescriptionManager.getInstance().applyData(this, baseDescription, base);
			//			fData = base;
		}

		fBaseCache = baseCache;
	}

	CConfigurationDescriptionCache getBaseCache() {
		return fBaseCache;
	}

	boolean applyData(SettingsContext context) throws CoreException {
		boolean modified = true;
		if (fBaseDescription != null) {
			context.init(this);
			CConfigurationDataProvider dataProvider = CProjectDescriptionManager.getInstance().getProvider(this);
			fData = dataProvider.applyConfiguration(this, fBaseDescription, fData, context, new NullProgressMonitor());
			fDataLoadded = true;
			fName = fData.getName();
			fId = fData.getId();

			if ((context.getAllConfigurationSettingsFlags() & IModificationContext.CFG_DATA_SETTINGS_UNMODIFIED) == 0
					|| fBaseCache == null) {
				copySettingsFrom(fData, true);
			} else {
				copySettingsFrom(fBaseCache, true);
				modified = fSpecSettings.isModified();
				if (!modified)
					modified = (context.getAllConfigurationSettingsFlags()
							& IModificationContext.CFG_DATA_STORAGE_UNMODIFIED) == 0;
			}

			ICdtVariable vars[] = CdtVariableManager.getDefault().getVariables(this);
			fMacros = new StorableCdtVariables(vars, true);
			fSpecSettings.serialize();
			fSpecSettings.setModified(false);
		}

		fBaseDescription = null;
		fBaseCache = null;

		return modified;
	}

	public StorableCdtVariables getCachedVariables() {
		return fMacros;
	}

	protected void setId(String id) throws CoreException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public CFileData copyFileData(IPath path, CFileData base, boolean clone) {
		return new CFileDescriptionCache(base, this);
	}

	@Override
	protected CFileData copyFileData(IPath path, CFolderData base, CLanguageData langData) {
		//should never be here
		throw new UnsupportedOperationException();
	}

	@Override
	public CFolderData copyFolderData(IPath path, CFolderData base, boolean clone) {
		return new CFolderDescriptionCache(base, this);
	}

	@Override
	protected CBuildData copyBuildData(CBuildData data, boolean clone) {
		return new CBuildSettingCache(data, this);
	}

	void addResourceDescription(ICResourceDescription des) {
		fRcHolder.addResourceDescription(des.getPath(), des);
		fChildList.add(des);
	}

	void addTargetPlatformSetting(ICTargetPlatformSetting tpS) {
		fChildList.add(tpS);
	}

	void addBuildSetting(ICBuildSetting bs) {
		fChildList.add(bs);
		fBuildData = (CBuildData) bs;
	}

	@Override
	public ICProjectDescription getProjectDescription() {
		return fParent;
	}

	@Override
	public ICResourceDescription getResourceDescription(IPath path, boolean exactPath) {
		return fRcHolder.getResourceDescription(path, exactPath);
	}

	public ICResourceDescription[] getResourceDescriptions(int kind) {
		return fRcHolder.getResourceDescriptions(kind);
	}

	@Override
	public ICFolderDescription getRootFolderDescription() {
		return (ICFolderDescription) fRootFolderData;
	}

	@Override
	public boolean isActive() {
		if (isPreferenceConfiguration())
			return false;
		return fParent.getActiveConfiguration() == this;
	}

	@Override
	public void removeResourceDescription(ICResourceDescription des) throws CoreException {
		throw new CoreException(
				new DescriptionStatus(SettingsModelMessages.getString("CConfigurationDescriptionCache.0"))); //$NON-NLS-1$
	}

	@Override
	public CFileData createFileData(IPath path, CFileData base) throws CoreException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public CFileData createFileData(IPath path, CFolderData base, CLanguageData baseLangData) throws CoreException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public CFolderData createFolderData(IPath path, CFolderData base) throws CoreException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public void removeResourceData(CResourceData data) throws CoreException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public void setDescription(String description) throws WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public void setName(String name) throws WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public ICSettingObject[] getChildSettings() {
		return fChildList.toArray(new ICSettingObject[fChildList.size()]);
	}

	@Override
	public ICConfigurationDescription getConfiguration() {
		return this;
	}

	@Override
	public ICSettingContainer getParent() {
		return fParent;
	}

	@Override
	public ICResourceDescription[] getResourceDescriptions() {
		return fRcHolder.getResourceDescriptions();
	}

	@Override
	public ICStorageElement getStorage(String id, boolean create) throws CoreException {
		return getSpecSettings().getStorage(id, create);
	}

	@Override
	public void removeStorage(String id) throws CoreException {
		getSpecSettings().removeStorage(id);
	}

	@Override
	public ICStorageElement importStorage(String id, ICStorageElement el)
			throws UnsupportedOperationException, CoreException {
		return getSpecSettings().importStorage(id, el);
	}

	@Override
	public CConfigurationSpecSettings getSpecSettings() /*throws CoreException*/ {
		return fSpecSettings;
	}

	@Override
	public String getBuildSystemId() {
		return fSpecSettings.getBuildSystemId();
	}

	@Override
	public CConfigurationData getConfigurationData() {
		return fData;
	}

	@Override
	public void setConfigurationData(String bsId, CConfigurationData data) throws WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public boolean isModified() {
		return false;
	}

	@Override
	public CConfigurationData getConfigurationData(boolean write) throws WriteAccessException {
		if (write)
			throw ExceptionFactory.createIsReadOnlyException();

		return this;
	}

	@Override
	public void setActive() throws WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	/*	public CConfigurationData getBaseData(){
			return fData;
		}
	*/
	@Override
	public ICFileDescription createFileDescription(IPath path, ICResourceDescription base)
			throws CoreException, WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public ICFolderDescription createFolderDescription(IPath path, ICFolderDescription base)
			throws CoreException, WriteAccessException {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	ResourceDescriptionHolder createHolderForRc(IPath path) {
		return new ResourceDescriptionHolder(fPathSettingContainer.getChildContainer(path, true, true), false);
	}

	@Override
	public boolean isReadOnly() {
		return !fInitializing;
	}

	@Override
	public void setReadOnly(boolean readOnly, boolean keepModify) {
		if (readOnly)
			throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public ICTargetPlatformSetting getTargetPlatformSetting() {
		return (ICTargetPlatformSetting) getTargetPlatformData();
	}

	@Override
	protected CTargetPlatformData copyTargetPlatformData(CTargetPlatformData base, boolean clone) {
		return new CTargetPlatformSettingCache(base, this);
	}

	@Override
	public ICFileDescription[] getFileDescriptions() {
		return (ICFileDescription[]) fRcHolder.getResourceDescriptions(ICSettingBase.SETTING_FILE);
	}

	@Override
	public ICFolderDescription[] getFolderDescriptions() {
		return (ICFolderDescription[]) fRcHolder.getResourceDescriptions(ICSettingBase.SETTING_FOLDER);
	}

	public void setSourcePaths(IPath[] paths) {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public ICSourceEntry[] getSourceEntries() {
		initSourceEntries();
		return fProjSourceEntries.clone();
	}

	private void initSourceEntries() {
		if (fProjSourceEntries == null) {
			IProject project = getProject();
			fProjSourceEntries = CDataUtil.adjustEntries(fSourceEntries, true, project);
		}
	}

	private IProject getProject() {
		return isPreferenceConfiguration() ? null : getProjectDescription().getProject();
	}

	@Override
	public void setSourceEntries(ICSourceEntry[] entries) {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public Map<String, String> getReferenceInfo() {
		return getSpecSettings().getReferenceInfo();
	}

	@Override
	public void setReferenceInfo(Map<String, String> refs) {
		throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public ICExternalSetting createExternalSetting(String[] languageIDs, String[] contentTypeIds, String[] extensions,
			ICSettingEntry[] entries) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();

		return fSpecSettings.createExternalSetting(languageIDs, contentTypeIds, extensions, entries);
	}

	@Override
	public ICExternalSetting[] getExternalSettings() {
		return fSpecSettings.getExternalSettings();
	}

	@Override
	public void removeExternalSetting(ICExternalSetting setting) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();

		fSpecSettings.removeExternalSetting(setting);
	}

	@Override
	public void removeExternalSettings() {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();

		fSpecSettings.removeExternalSettings();
	}

	@Override
	public ICBuildSetting getBuildSetting() {
		return (ICBuildSetting) getBuildData();
	}

	@Override
	public void setSessionProperty(QualifiedName name, Object value) {
		fSpecSettings.setSettionProperty(name, value);
		//throw ExceptionFactory.createIsReadOnlyException();
	}

	@Override
	public Object getSessionProperty(QualifiedName name) {
		return fSpecSettings.getSettionProperty(name);
	}

	@Override
	public ICdtVariablesContributor getBuildVariablesContributor() {
		return fData.getBuildVariablesContributor();
	}

	@Override
	public ICConfigExtensionReference create(String extensionPoint, String extension) throws CoreException {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		return fSpecSettings.create(extensionPoint, extension);
	}

	@Override
	public ICConfigExtensionReference[] get(String extensionPointID) {
		return fSpecSettings.get(extensionPointID);
	}

	@Override
	public void remove(ICConfigExtensionReference ext) throws CoreException {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.remove(ext);
	}

	@Override
	public void remove(String extensionPoint) throws CoreException {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.remove(extensionPoint);
	}

	@Override
	public boolean isPreferenceConfiguration() {
		return getProjectDescription() == null;
	}

	void doneInitialization() {
		CProjectDescriptionManager.getInstance().notifyCached(this, fData, null);
		fInitializing = false;
		fSpecSettings.doneInitialization();
	}

	@Override
	public ICLanguageSetting getLanguageSettingForFile(IPath path, boolean ignoreExcludeStatus) {
		return CProjectDescriptionManager.getLanguageSettingForFile(this, path, ignoreExcludeStatus);
	}

	@Override
	protected CResourceData[] filterRcDatasToCopy(CConfigurationData base) {
		if (!isPreferenceConfiguration())
			CProjectDescriptionManager.removeNonCustomSettings(getProjectDescription().getProject(), base);
		return super.filterRcDatasToCopy(base);
	}

	boolean isExcluded(IPath path) {
		//		if(path.segmentCount() == 0)
		//			return false;

		initSourceEntries();
		IProject project = getProject();
		if (project != null)
			path = project.getFullPath().append(path);

		return CDataUtil.isExcluded(path, fProjSourceEntries);
	}

	@Override
	public String[] getExternalSettingsProviderIds() {
		return fSpecSettings.getExternalSettingsProviderIds();
	}

	@Override
	public void setExternalSettingsProviderIds(String[] ids) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.setExternalSettingsProviderIds(ids);
	}

	@Override
	public void updateExternalSettingsProviders(String[] ids) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.updateExternalSettingsProviders(ids);
	}

	@Override
	public ICSourceEntry[] getResolvedSourceEntries() {
		if (fResolvedSourceEntries == null) {
			ICSourceEntry[] entries = getSourceEntries();
			fResolvedSourceEntries = CDataUtil.resolveEntries(entries, this);
		}
		return fResolvedSourceEntries;
	}

	@Override
	public CConfigurationStatus getConfigurationStatus() {
		CConfigurationStatus status = getStatus();
		return status != null ? status : CConfigurationStatus.CFG_STATUS_OK;
	}

	@Override
	public void setLanguageSettingProviders(List<? extends ILanguageSettingsProvider> providers) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.setLanguageSettingProviders(providers);
	}

	@Override
	public List<ILanguageSettingsProvider> getLanguageSettingProviders() {
		return fSpecSettings.getLanguageSettingProviders();
	}

	@Override
	public void setDefaultLanguageSettingsProvidersIds(String[] ids) {
		if (!fInitializing)
			throw ExceptionFactory.createIsReadOnlyException();
		fSpecSettings.setDefaultLanguageSettingsProvidersIds(ids);
	}

	@Override
	public String[] getDefaultLanguageSettingsProvidersIds() {
		return fSpecSettings.getDefaultLanguageSettingsProvidersIds();
	}
}
