/*
 * Created on 2005-04-27
 *
 * Part of the Symbian development plug-in for Eclipse
 * Copyright (C) Mikołaj Zalewski, 2005
 */
package pl.zalewski.mikolaj.symbdev.core.internal.mmpmodel;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import pl.zalewski.mikolaj.symbdev.SymbdevPlugin;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPSourceFileReference;

/**
 * Watches for *.cpp files in a selected directory and adds them to
 * the MMP.
 * 
 * @author mikolaj
 */
public class SourceDirectoryWatcher implements IResourceChangeListener {

    public static final String CPP_EXT="cpp";    
    
    public SourceDirectoryWatcher(IProject project, IPath directory,
            FileMMPModel model, IPath mmpCurrentDir, IAutoEntriesMapper mapper)
    {
        fDirectoryPath=directory;
        fWorkspaceDirectoryPath=project.getFullPath().append(fDirectoryPath);
        fMMPCurrentDir=mmpCurrentDir;
        fModel=model;
        fProject=project;
        fMapper=mapper;
        fContainer=project.getFolder(directory);
        
        try {
	        IPath dir=fMapper.getDirectoryForAutoDir(fDirectoryPath);
	        if (dir!=null)
	            fModel.removeAutoDirectory(dir, fDirectoryPath);
	        if (fMapper.isDontAdd(fDirectoryPath))
	        	fModel.removeSourceDontAdd(fDirectoryPath);
        }
        catch (CoreException e) {
            SymbdevPlugin.log(e);
        }
    }
    
    public boolean isContainerValid() {
        return fContainer!=null;
    }
    
    public String normalizePath(IPath path) {
        return path.toString().toLowerCase();        
    }
    
    protected void findFilesToAdd(IContainer cont, ModelData md,
            HashSet unusedFiles, HashSet unusedDirs, LinkedList updates) {
        IResource[] members;
        try {
            members=cont.members();
        } catch (CoreException e) {
            SymbdevPlugin.log(e);
            return;
        }
        
        for (int i=0; i<members.length; i++) {
            IPath path=members[i].getProjectRelativePath();
            if (fMapper.isDontAdd(path))
                continue;
            
            if (members[i] instanceof IContainer) {
                if (fMapper.isSelectedDir(path))
                    continue;
                
                visitDirectory(md, members[i], updates);
                unusedDirs.remove(path);
                findFilesToAdd((IContainer)members[i], md, unusedFiles, unusedDirs, updates);
            } else if (members[i] instanceof IFile) {
                visitFile(md, members[i], updates);
                unusedFiles.remove(path);
            }
        }
    }
    
    protected void visitFile(ModelData md, IResource file, LinkedList updates) {
        final IPath path=file.getProjectRelativePath();
        if (file.getFileExtension().equalsIgnoreCase(CPP_EXT)) {
            final IPath dir=fMapper.getDirectoryForAutoFile(path);
            if (fDirectoryPath.equals(dir))
                return;
            
            if (dir!=null){
                updates.add(new IModelUpdater() {
                    public void run() throws CoreException {
                        fModel.changeAutoFileOwner(fDirectoryPath, dir, path);                        
                    }
                });
                return;
            }
            
            String pathstr=normalizePath(file.getLocation());
            if (md.fFiles.contains(pathstr))
                return;
            
            final IPath filepath=file.getProjectRelativePath();
            updates.add(new IModelUpdater() {
                public void run() throws CoreException {
                    fModel.addAutoSourceFile(fDirectoryPath, filepath);                    
                }
            });
            md.fFiles.add(pathstr);
        }        
    }
    
    protected void visitDirectory(ModelData md, IResource file, LinkedList updates) {
        final IPath path=file.getProjectRelativePath();
        final IPath dir=fMapper.getDirectoryForAutoDir(path);
        if (fDirectoryPath.equals(dir))
            return;
        
        if (dir==null)
            updates.add(new IModelUpdater() {
                		public void run() throws CoreException {
                            fModel.addAutoDirectory(fDirectoryPath, path);                    		    
                		}
                    });
        	else updates.add(new IModelUpdater() {
        	    	public void run() throws CoreException {
        	    	    fModel.changeAutoDirectoryOwner(fDirectoryPath, dir, path);
        	    	}
        		});
    }
    
    protected void addedResource(ModelData md, IResource resource, LinkedList updates) {
        if (resource instanceof IContainer) {
            visitDirectory(md, resource, updates);
        } else if (resource instanceof IFile) {
            visitFile(md, resource, updates);
        }
    }
    
    IModelUpdater createRemoveFileUpdate(IPath file) {
        final IPath path=file;
        return new IModelUpdater() {
	    	public void run() throws CoreException {
                fModel.removeAutoSourceFile(fDirectoryPath, path);	            	    	    
	    	}
		};
    }
    
    IModelUpdater createRemoveDirectoryUpdater(IPath dir) {
        final IPath path=dir;
        return new IModelUpdater() {
    	    public void run() throws CoreException {
                fModel.removeAutoDirectory(fDirectoryPath, path);	            	        
    	    }
    	};
    }
    
    protected void removedResource(ModelData md, IResource resource, LinkedList updates) {
        IPath path=resource.getProjectRelativePath();
        if (resource instanceof IContainer) {
            fDirectoryPath.equals(fMapper.getDirectoryForAutoDir(path));
            	updates.add(createRemoveDirectoryUpdater(path));
        } else if (resource instanceof IFile) {
            fDirectoryPath.equals(fMapper.getDirectoryForAutoFile(path));
            	updates.add(createRemoveFileUpdate(path));
        }
    }
    
    public ModelData loadModelData() {
        ModelData md=new ModelData();
        MMPSourceFileReference[] refs=fModel.getSourceFiles();
        for (int i=0; i<refs.length; i++) {
            IPath path=refs[i].getCanonicalPathIfRelative(fMMPCurrentDir);
            if (path!=null)
                md.fFiles.add(normalizePath(path));
        }
        
        return md;
    }
    
    void executeUpdates(LinkedList updates) {
        fModel.freezeFireChanges();
        try {
            Iterator it=updates.iterator();
            while (it.hasNext()) {
                IModelUpdater update=(IModelUpdater)it.next();
                try {
                    update.run();
                }
                catch (CoreException e) {
                    SymbdevPlugin.log(e);
                }
            }
        }
        finally {
            fModel.unfreezeFireChanges();
        }
    }
    
    public void addFiles() {
        if (fContainer==null)
            fContainer=fProject.getFolder(fDirectoryPath);
        if (fContainer==null)
            return;
        
        ModelData md=loadModelData();
        List list;
        list=Arrays.asList(fMapper.getAutoFilesForDirectory(fDirectoryPath));
        HashSet unusedFiles=new HashSet(list);
        
        list=Arrays.asList(fMapper.getAutoDirsForDirectory(fDirectoryPath));
        HashSet unusedDirs=new HashSet(list);
        LinkedList updates=new LinkedList();
        
        findFilesToAdd(fContainer, md, unusedFiles, unusedDirs, updates);
        
        Iterator it=unusedFiles.iterator();
        while (it.hasNext()) {
            IPath path=(IPath)it.next();
        	updates.add(createRemoveFileUpdate(path));
        }
        
        it=unusedDirs.iterator();
        while (it.hasNext()) {
            IPath path=(IPath)it.next();
            updates.add(createRemoveDirectoryUpdater(path));
        }
        
        executeUpdates(updates);
    }
    
    public void startWatch() {
        addFiles();
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
                IResourceChangeEvent.POST_CHANGE);
    }
    
    public void stopWatch() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }
    
    public void resourceChanged(IResourceChangeEvent event) {
        if (event.getType()!=IResourceChangeEvent.POST_CHANGE)
            return;
        IResourceDelta td=event.getDelta().findMember(fWorkspaceDirectoryPath);
        if (td==null)
            return;
        if (fContainer==null)
            fContainer=fProject.getFolder(fDirectoryPath);
        if (fContainer==null)
            return;
        
        final LinkedList updates=new LinkedList();
        IResourceDeltaVisitor visitor=new IResourceDeltaVisitor() {
            public boolean visit(IResourceDelta delta) throws CoreException {
                IResource res=delta.getResource();
                if (fMapper.isDontAdd(res.getProjectRelativePath()))
                    return false;
                
                switch (delta.getKind()) {
                	case IResourceDelta.ADDED:
                	    if (md==null)
                	        md=loadModelData();
                	    addedResource(md, delta.getResource(), updates);
                		break;
                	case IResourceDelta.REMOVED:
                	    if (md==null)
                	        md=loadModelData();
                	    removedResource(md, delta.getResource(), updates);
                		break;
                }
                return true;
            }
            
            ModelData md;
        };
        try {
            td.accept(visitor);
            SymbdevPlugin.getStandardDisplay().asyncExec(
                    new Runnable() {
                        public void run() {
                            executeUpdates(updates);
                        }
                    });
        }
        catch (CoreException e) {
            SymbdevPlugin.log(e);
        }
    }
    
    static class ModelData {
        HashSet fFiles=new HashSet();
    }

    interface IModelUpdater {
        void run() throws CoreException;
    }
    
    IContainer fContainer;
    /**
     * A project root-relative path to the watched directory
     */
    IPath fDirectoryPath;
    /**
     * A workspace root-relative path to the watched directory
     */
    IPath fWorkspaceDirectoryPath;
    IPath fMMPCurrentDir;
    IProject fProject;
    IAutoEntriesMapper fMapper;
    FileMMPModel fModel;
}
