/*******************************************************************************
 * 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.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.ICExtensionReference;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IPathEntry;
import org.eclipse.cdt.core.resources.IPathEntryStore;
import org.eclipse.cdt.core.resources.IPathEntryStoreListener;
import org.eclipse.cdt.core.resources.PathEntryStoreChangedEvent;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICExternalSetting;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.util.PathEntryTranslator;
import org.eclipse.cdt.core.settings.model.util.PathEntryTranslator.PathEntryCollector;
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.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;

public class ConfigBasedPathEntryStore implements IPathEntryStore, ICProjectDescriptionListener {
	private List fListeners;
	private IProject fProject;
	static final QualifiedName PATH_ENTRY_COLLECTOR_PROPERTY_NAME = new QualifiedName(CCorePlugin.PLUGIN_ID, "PathEntryStoreCollector");	//$NON-NLS-1$
	
	/**
	 * 
	 */
	public ConfigBasedPathEntryStore(IProject project) {
		fProject = project;
		fListeners = Collections.synchronizedList(new ArrayList());
		
		CProjectDescriptionManager.getInstance().addCProjectDescriptionListener(this, CProjectDescriptionEvent.APPLIED | CProjectDescriptionEvent.LOADDED);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.core.resources.IPathEntryStore#addPathEntryStoreListener(org.eclipse.cdt.core.resources.IPathEntryStoreListener)
	 */
	public void addPathEntryStoreListener(IPathEntryStoreListener listener) {		
		fListeners.add(listener);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.core.resources.IPathEntryStore#removePathEntryStoreListener(org.eclipse.cdt.core.resources.IPathEntryStoreListener)
	 */
	public void removePathEntryStoreListener(IPathEntryStoreListener listener) {
		fListeners.remove(listener);
	}

	private void fireContentChangedEvent(IProject project) {
		PathEntryStoreChangedEvent evt = new PathEntryStoreChangedEvent(this, project, PathEntryStoreChangedEvent.CONTENT_CHANGED);
		IPathEntryStoreListener[] observers = new IPathEntryStoreListener[fListeners.size()];
		fListeners.toArray(observers);
		for (int i = 0; i < observers.length; i++) {
			observers[i].pathEntryStoreChanged(evt);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.core.resources.IPathEntryStore#fireClosedChangedEvent(IProject)
	 */
	public void close() {
		PathEntryStoreChangedEvent evt = new PathEntryStoreChangedEvent(this, fProject, PathEntryStoreChangedEvent.STORE_CLOSED);
		IPathEntryStoreListener[] observers = new IPathEntryStoreListener[fListeners.size()];
		fListeners.toArray(observers);
		for (int i = 0; i < observers.length; i++) {
			observers[i].pathEntryStoreChanged(evt);
		}
		CProjectDescriptionManager.getInstance().removeCProjectDescriptionListener(this);
	}

	public IProject getProject() {
		return fProject;
	}

	public ICExtensionReference getExtensionReference() {
		return null;
	}

	public IPathEntry[] getRawPathEntries() throws CoreException {
		ICConfigurationDescription cfg = getIndexCfg(fProject);
		List[] es = getEntries(fProject, cfg);
		if(es != null){
			List list = new ArrayList(es[0].size() + 1);
			list.addAll(es[0]);
			list.add(CoreModel.newContainerEntry(ConfigBasedPathEntryContainer.CONTAINER_PATH));
			return (IPathEntry[])list.toArray(new IPathEntry[list.size()]);
		}
		return new IPathEntry[0];
	}

	public void setRawPathEntries(IPathEntry[] entries) throws CoreException {
		ICConfigurationDescription cfg = getIndexCfg(fProject);
		List es[] = getEntries(fProject, cfg);
		if(es != null){
			List sysList = es[1];
			List usrList = es[0];
			List newUsrList = new ArrayList(entries.length);
			for(int i = 0; i < entries.length; i++){
				if(entries[i].getEntryKind() != IPathEntry.CDT_CONTAINER)
					newUsrList.add(entries[i]);
			}
			
			if(!newUsrList.equals(usrList)){
				usrList = newUsrList;
				ICProjectDescription des = CoreModel.getDefault().getProjectDescription(fProject, true);
				ICConfigurationDescription cfgDes = des.getDefaultSettingConfiguration();
				CConfigurationData data = cfgDes.getConfigurationData();
				PathEntryTranslator tr = new PathEntryTranslator(fProject, data);
				IPathEntry[] usrEntries = (IPathEntry[])usrList.toArray(new IPathEntry[usrList.size()]);
				IPathEntry[] sysEntries = (IPathEntry[])sysList.toArray(new IPathEntry[sysList.size()]);
				ReferenceSettingsInfo rInfo = tr.applyPathEntries(usrEntries, sysEntries, PathEntryTranslator.OP_REPLACE);
				cfgDes.removeExternalSettings();
				ICExternalSetting extSettings[] = rInfo.getExternalSettings();
				for(int i = 0; i < extSettings.length; i++){
					ICExternalSetting setting = extSettings[i];
					cfgDes.createExternalSetting(setting.getCompatibleLanguageIds(), 
							setting.getCompatibleContentTypeIds(),
							setting.getCompatibleExtensions(), 
							setting.getEntries());
				}
				Map refMap = rInfo.getRefProjectsMap();
				cfgDes.setReferenceInfo(refMap);
				
				CoreModel.getDefault().setProjectDescription(fProject, des);
			}
		}
	}
	
	private static void clearCachedEntries(ICProjectDescription des){
		ICConfigurationDescription[] cfgDess = des.getConfigurations();
		for(int i = 0; i < cfgDess.length; i++){
			setCachedEntries(cfgDess[i], null);
		}
	}

	public void handleEvent(CProjectDescriptionEvent event) {
		IProject project = event.getProject();
		if(!fProject.equals(project))
			return;
		
		switch(event.getEventType()){
			case CProjectDescriptionEvent.APPLIED:{
				ICProjectDescription des = event.getNewCProjectDescription();
				ICProjectDescription oldDes = event.getOldCProjectDescription();
				List oldCrEntries = null;
				if(oldDes != null){
					ICConfigurationDescription oldIndexCfg = oldDes.getDefaultSettingConfiguration();
					List[] oldEs = getCachedEntries(oldIndexCfg);
					if(oldEs != null)
						oldCrEntries = oldEs[1];
					
					clearCachedEntries(oldDes);
				}
				if(des != null){
					//TODO: smart delta handling
					clearCachedEntries(des);
					
					if(oldCrEntries != null){
						ICConfigurationDescription newIndexCfg = des.getDefaultSettingConfiguration();
						List[] newEs = getEntries(fProject, newIndexCfg);
						List newCrEntries = newEs[1];
						if(!Arrays.equals(oldCrEntries.toArray(), newCrEntries.toArray())){
							CModelManager manager = CModelManager.getDefault();
							ICProject cproject = manager.create(project);
							

				//			ConfigBasedPathEntryContainer newContainer = createContainer(des);
							try {
								PathEntryManager.getDefault().clearPathEntryContainer(new ICProject[]{cproject}, ConfigBasedPathEntryContainer.CONTAINER_PATH, new NullProgressMonitor());
							} catch (CModelException e) {
								CCorePlugin.log(e);
							}
						}
					}
				}
				fireContentChangedEvent(fProject);
				break;
			}
		}
	}
	
//	private static PathEntryCollector getCollector(ICProjectDescription des){
//		ICConfigurationDescription cfgDes = ((CProjectDescription)des).getIndexConfiguration();
//		if(cfgDes != null){
//			PathEntryCollector cr = getCachedCollector(cfgDes);
//			if(cr == null){
//				cr = PathEntryTranslator.collectEntries(des.getProject(), cfgDes);
//				setCachedCollector(cfgDes, cr);
//			}
//			return cr;
//		}
//		return null;
//	}

	private static List[] getEntries(IProject project, ICConfigurationDescription cfgDes){
		if(cfgDes != null){
			List[] es = getCachedEntries(cfgDes);
			if(es == null){
				PathEntryCollector cr = PathEntryTranslator.collectEntries(project, cfgDes);
				es = createEntriesList(cfgDes, cr);
				setCachedEntries(cfgDes, es);
			}
			return es;
		}
		return null;
	}
	
	private static List[] createEntriesList(ICConfigurationDescription cfgDes, PathEntryCollector cr){
		ArrayList[] es = new ArrayList[2];
		es[0] = new ArrayList(); 
		cr.getEntries(es[0], PathEntryTranslator.INCLUDE_USER, cfgDes);
		es[0].trimToSize();
		es[1] = new ArrayList(); 
		cr.getEntries(es[1], PathEntryTranslator.INCLUDE_BUILT_INS, cfgDes);
		es[1].trimToSize();
		
		return es;
	}

	private static List[] getCachedEntries(ICConfigurationDescription cfgDes){
		return (List[])cfgDes.getSessionProperty(PATH_ENTRY_COLLECTOR_PROPERTY_NAME);
	}

	private static void setCachedEntries(ICConfigurationDescription cfgDes, List[] es){
		cfgDes.setSessionProperty(PATH_ENTRY_COLLECTOR_PROPERTY_NAME, es);
	}

//	public static PathEntryCollector getCollector(IProject project){
//		ICProjectDescription des = CCorePlugin.getDefault().getProjectDescription(project, false);
//		if(des != null)
//			return getCollector(des);
//		return null;
//	}
	
	private static ICConfigurationDescription getIndexCfg(IProject project){
		ICProjectDescription des = CCorePlugin.getDefault().getProjectDescription(project, false);
		return des != null ? des.getDefaultSettingConfiguration() : null;
	}
	
	private static List getContainerEntries(IProject project){
		ICProjectDescription des = CCorePlugin.getDefault().getProjectDescription(project, false);
		if(des != null)
			return getContainerEntries(des);
		return new ArrayList(0);
	}

	private static List getContainerEntries(ICProjectDescription des){
		ICConfigurationDescription cfg = des.getDefaultSettingConfiguration();
		List es[] = getEntries(des.getProject(), cfg);
		if(es != null)
			return es[1];
		return new ArrayList(0);
	}

	public static ConfigBasedPathEntryContainer createContainer(IProject project){
		List list = getContainerEntries(project);
		return new ConfigBasedPathEntryContainer(list);
	}

	public static ConfigBasedPathEntryContainer createContainer(ICProjectDescription des){
		List list = getContainerEntries(des);
		return new ConfigBasedPathEntryContainer(list);
	}
}
