/*******************************************************************************
 * Copyright (c) 2007 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.settings.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IPathEntry;
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.ICSettingBase;
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.impl.CDataFactory;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultConfigurationData;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultFileData;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultFolderData;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultLanguageData;
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.core.settings.model.util.EntryStore;
import org.eclipse.cdt.core.settings.model.util.PathEntryResolveInfo;
import org.eclipse.cdt.core.settings.model.util.PathEntryTranslator;
import org.eclipse.cdt.core.settings.model.util.PathEntryTranslator.ReferenceSettingsInfo;
import org.eclipse.cdt.internal.core.model.CModelManager;
import org.eclipse.cdt.internal.core.model.PathEntryManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;

public class PathEntryConfigurationDataProvider extends
		CConfigurationDataProvider {
	private static PathEntryDataFactory fFactory;
	
	
	public static CDataFactory getDataFactory(){
		if(fFactory == null){
			fFactory = new PathEntryDataFactory();
		}
		return fFactory;
	}

	public PathEntryConfigurationDataProvider(){
		getDataFactory();
	}

	private static class PathEntryFolderData extends CDefaultFolderData {
		private EntryStore fStore;
		
//		public PathEntryFolderData(CConfigurationData cfg, CDataFacroty factory) {
//			super(cfg, factory);
//		}

//		public PathEntryFolderData(String id, IPath path,
//				CConfigurationData cfg, CDataFacroty factory) {
//			super(id, path, cfg, factory);
//		}

		public PathEntryFolderData(String id, IPath path, PathEntryFolderData base,
				CConfigurationData cfg, CDataFactory factory, boolean clone) {
			super(id, path, cfg, factory);
			
			if(base != null)
				fStore = new EntryStore(base.fStore, true);
			else
				fStore = new EntryStore(true);
			
			copyDataFrom(base, clone);
		}
	}

	private static class PathEntryFileData extends CDefaultFileData {
		private EntryStore fStore;

//		public PathEntryFileData(CConfigurationData cfg, CDataFacroty factory) {
//			super(cfg, factory);
//		}

//		public PathEntryFileData(String id, IPath path, CConfigurationData cfg,
//				CDataFacroty factory) {
//			super(id, path, cfg, factory);
//		}

		public PathEntryFileData(String id, IPath path, PathEntryFileData base,
				CConfigurationData cfg, CDataFactory factory, boolean clone) {
			super(id, path, cfg, factory);

			fStore = new EntryStore(base.fStore, true);
			
			copyDataFrom(base, clone);
		}

		public PathEntryFileData(String id, IPath path, PathEntryFolderData base,
				CLanguageData baseLangData, CConfigurationData cfg,
				CDataFactory factory) {
			super(id, path, cfg, factory);

			fStore = new EntryStore(base.fStore, true);
			
			copyDataFrom(base, baseLangData);
		}
		
	}
	
	private static class PathEntryLanguageData extends CDefaultLanguageData {

		public PathEntryLanguageData(String id, CLanguageData base, EntryStore store) {
			fId = id;
			fStore = store;
			copySettingsFrom(base);
		}

		public PathEntryLanguageData(String id, String name, String languageId, int kinds, 
				String[] ids, boolean isContentTypes, EntryStore store) {
			super(id, languageId, ids, isContentTypes);
			fStore = store;
			fName = name;
			fSupportedKinds = kinds;
		}

		@Override
		protected EntryStore createStore() {
			return fStore;
		}

		@Override
		protected EntryStore createStore(CLanguageData data) {
			return fStore;
		}
		
	}
	
	private static class PathEntryDataFactory extends CDataFactory {

		@Override
		public CConfigurationData createConfigurationdata(String id,
				String name, CConfigurationData base, boolean clone) {
			if(clone){
				id = base.getId();
			} else if(id == null){
				id = CDataUtil.genId(null);
			}
			
			return new CfgData(id, name, base, clone);
		}

		@Override
		public CFileData createFileData(CConfigurationData cfg,
				CResourceData base, CLanguageData base2, 
				String id, boolean clone,
				IPath path) {
			if(id == null)
				id = clone ? base.getId() : CDataUtil.genId(cfg.getId());
			if(base.getType() == ICSettingBase.SETTING_FILE)
				return new PathEntryFileData(id, path, (PathEntryFileData)base, cfg, this, clone);
			return new PathEntryFileData(id, path, (PathEntryFolderData)base,
					base2, cfg,	this);
		}

		@Override
		public CFolderData createFolderData(CConfigurationData cfg,
				CFolderData base, String id, boolean clone, IPath path) {
			if(id == null)
				id = clone ? base.getId() : CDataUtil.genId(cfg.getId());
			return new PathEntryFolderData(id, path, (PathEntryFolderData)base, cfg, this, clone);
		}

		@Override
		public CLanguageData createLanguageData(CConfigurationData cfg,
				CResourceData rcBase, CLanguageData base, String id, boolean clone) {
			if(id == null)
				id = clone ? base.getId() : CDataUtil.genId(rcBase.getId());
			EntryStore store;
			if(rcBase.getType() == ICSettingBase.SETTING_FOLDER)
				store = ((PathEntryFolderData)rcBase).fStore;
			else
				store = ((PathEntryFileData)rcBase).fStore;
			return new PathEntryLanguageData(id, base, store);
		}

		@Override
		public CLanguageData createLanguageData(CConfigurationData cfg,
				CResourceData rcBase, String id, String name, String languageId, int supportedEntryKinds, String[] rcTypes,
				boolean isContentTypes) {
			if(id == null)
				id = CDataUtil.genId(rcBase.getId());
			EntryStore store;
			if(rcBase.getType() == ICSettingBase.SETTING_FOLDER)
				store = ((PathEntryFolderData)rcBase).fStore;
			else
				store = ((PathEntryFileData)rcBase).fStore;
			return new PathEntryLanguageData(id, name, languageId, supportedEntryKinds, rcTypes, isContentTypes, store);
		}
		
	}

	private static class CfgData extends CDefaultConfigurationData {
//		private PathEntryResolveInfo fResolveInfo;

		public CfgData(String id, String name, CConfigurationData base, boolean clone) {
			super(id, name, base, fFactory, clone);
		}

		public CfgData(String id, String name) {
			super(id, name, fFactory);
		}
		
//		public PathEntryResolveInfo getResolveInfo(){
//			return fResolveInfo;
//		}
//		
//		public void setResolveInfo(PathEntryResolveInfo info){
//			fResolveInfo = info;
//		}
	}
	
	public static boolean isPathEntryData(CConfigurationData data){
		return data instanceof CfgData;
	}
	
	@Override
	public CConfigurationData applyConfiguration(
			ICConfigurationDescription des,
			ICConfigurationDescription baseDescription,
			CConfigurationData base,
			IProgressMonitor monitor)
			throws CoreException {
		//TODO: check external/reference info here as well.
		if(!fFactory.isModified(base)){
			return createData(des, base, false, true);
		}
		
		
		IProject project = des.getProjectDescription().getProject();
//		ReferenceSettingsInfo refInfo = new ReferenceSettingsInfo(des);
		IPathEntry entries[] = PathEntryTranslator.getPathEntries(project, baseDescription, PathEntryTranslator.INCLUDE_USER);
		CModelManager manager = CModelManager.getDefault();
		ICProject cproject = manager.create(project);
		IPathEntry[] curRawEntries = PathEntryManager.getDefault().getRawPathEntries(cproject);
		
		List list = new ArrayList();
		list.addAll(Arrays.asList(entries));
		for(int i = 0; i < curRawEntries.length; i++){
			if(curRawEntries[i].getEntryKind() == IPathEntry.CDT_CONTAINER){
				list.add(curRawEntries[i]);
			}
		}
		
		IPathEntry[] newEntries = (IPathEntry[])list.toArray(new IPathEntry[list.size()]);
		PathEntryManager.getDefault().setRawPathEntries(cproject, newEntries, new NullProgressMonitor());
		return createData(des, base, false, false);
	}
	
	private CConfigurationData createData(ICConfigurationDescription des, CConfigurationData fallbackData, boolean modifiedFlag, boolean useCache) throws CoreException {
		CConfigurationData dataToReturn;
		try {
			dataToReturn = createData(des, useCache);
		} catch (Exception e){
			if(fallbackData != null)
				dataToReturn = fallbackData;
			else if (e instanceof CoreException)
				throw (CoreException)e;
			else
				throw ExceptionFactory.createCoreException(e);
		}
		fFactory.setModified(dataToReturn, modifiedFlag);
		return dataToReturn;
		
	}

	@Override
	public CConfigurationData createConfiguration(
			ICConfigurationDescription des, 
			ICConfigurationDescription baseDescription,
			CConfigurationData base,
			boolean clone,
			IProgressMonitor monitor) throws CoreException {
		CfgData copy = new CfgData(des.getId(), des.getName(), base, clone);
		copy.setModified(false);
		return copy;
	}
	
	private CfgData createData(ICConfigurationDescription des, boolean useCache) throws CoreException{
		IProject project = des.getProjectDescription().getProject();
		CModelManager manager = CModelManager.getDefault();
		ICProject cproject = manager.create(project);
		PathEntryResolveInfo rInfo = PathEntryManager.getDefault().getResolveInfo(cproject, useCache);
		
		CfgData data = new CfgData(des.getId(), des.getName());
		data.initEmptyData();
		CDataUtil.adjustConfig(data, getDataFactory());
		//CProjectDescriptionManager.getInstance().adjustDefaultConfig(data);
	
//		data.setResolveInfo(rInfo);
		PathEntryTranslator tr = new PathEntryTranslator(project, data);
		ReferenceSettingsInfo refInfo = tr.applyPathEntries(rInfo, PathEntryTranslator.OP_REPLACE);
		ICExternalSetting extSettings[] = refInfo.getExternalSettings();
		des.removeExternalSettings();
		if(extSettings.length != 0){
			ICExternalSetting setting;
			for(int i = 0; i < extSettings.length; i++){
				setting = extSettings[i];
				des.createExternalSetting(setting.getCompatibleLanguageIds(), 
						setting.getCompatibleContentTypeIds(), 
						setting.getCompatibleExtensions(), 
						setting.getEntries());
			}
		}

//		IPath projPaths[] = refInfo.getReferencedProjectsPaths();
//		if(projPaths.length != 0){
//			Map map = new HashMap(projPaths.length);
//			for(int i = 0; i < projPaths.length; i++){
//				map.put(projPaths[i].segment(0), "");	//$NON-NLS-1$
//			}
//			des.setReferenceInfo(map);
//		}

		cproject.close();
		
		String[] ids = getIds(des.get(CCorePlugin.BINARY_PARSER_UNIQ_ID));
		data.getTargetPlatformData().setBinaryParserIds(ids);
		
		ids = getIds(des.get(CCorePlugin.ERROR_PARSER_UNIQ_ID));
		data.getBuildData().setErrorParserIDs(ids);
		
		data.setModified(false);
		return data;
	}
	
	private String[] getIds(ICConfigExtensionReference refs[]){
		if(refs == null || refs.length == 0)
			return new String[0];
		
		String[] ids = new String[refs.length];
		for(int i = 0; i < refs.length; i++){
			ids[i] = refs[i].getID();
		}
		return ids;
	}

	@Override
	public CConfigurationData loadConfiguration(ICConfigurationDescription des,
			IProgressMonitor monitor)
			throws CoreException {
		return createData(des, null, false, true);
	}

	@Override
	public void removeConfiguration(ICConfigurationDescription des,
			CConfigurationData data,
			IProgressMonitor monitor) {
		//do nothing for now
	}

	@Override
	public void dataCached(ICConfigurationDescription cfgDes,
			CConfigurationData data, IProgressMonitor monitor) {
		fFactory.setModified(data, false);
	}

}
