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

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.util.Assert;
import org.eclipse.ui.part.FileEditorInput;

import pl.zalewski.mikolaj.symbdev.SymbdevPlugin;
import pl.zalewski.mikolaj.symbdev.core.internal.mmpparser.MMPParser;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.BldInfModel;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPBitmapReference;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPCommands;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPFileReference;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPModelException;
import pl.zalewski.mikolaj.symbdev.core.mmpmodel.MMPSourceFileReference;
import pl.zalewski.mikolaj.symbdev.utils.PathUtils;

/**
 * A model of the MMP file that is kept in sync with the file - changes
 * in the file forces a reparsing and changes to the model modifies
 * the given file.
 * 
 * @author mikolaj
 */
public class FileMMPModel extends MMPModel {

    /**
     * The deleay (in ms) between a change in the document and the
     * reparsing of it.
     */
    public static final int REPARSE_DELAY=100;    
    
    /**
     * @param doc
     * @param file
     */
    public FileMMPModel(IDocument doc, IResource file) {
        super();
        fDoc=doc;
        
        fCurrentDir=file.getParent().getLocation();
        fProject=file.getProject();
        
        fParser=new MMPParser(doc, this, file);
        MMPModel updatedModel=fParser.parseAndReturnModel();
        loadModel(updatedModel);
        fParser.applyMarkers();
        
        fParserJob=new Job("MMP Parsing"){
            protected IStatus run(IProgressMonitor monitor) {
                fParser.parse();
                return Status.OK_STATUS;
            }
        };
        fParserJob.setSystem(true);
        fParserJob.setPriority(Job.DECORATE);
        
        fDoc.addDocumentListener(new IDocumentListener()
                {
                    public void documentAboutToBeChanged(DocumentEvent event) {
                    }

                    public void documentChanged(DocumentEvent event) {
                        fParser.fClean=false;
                        fParserJob.schedule(REPARSE_DELAY);
                    }
                });
        try {
            Assert.isTrue(file instanceof IFile);
            fFile=(IFile)file;
            fSupplement=new MMPSupplementSupport(fFile);
	        IPath[] dirs=fSupplement.getSupplementSourceDirectories();
	        for (int i=0; i<dirs.length; i++)
	            createWatcher(dirs[i]);
        }
        catch (CoreException e) {
            SymbdevPlugin.log(e);
            fSupplementException=e;
        }
    }
    
    protected void checkSupplement() throws CoreException {
        if (fSupplementException!=null)
            throw new CoreException(
                    SymbdevPlugin.createErrorStatus(SymbdevPlugin.ERROR_ACCESS_SYMBBUILD,
                            "Error accessing .symbbuild file", fSupplementException));
        if (fSupplement==null)
            throw new CoreException(
                    SymbdevPlugin.createErrorStatus(SymbdevPlugin.ERROR_ACCESS_SYMBBUILD,
                            "Couldn't access .symbbuild file"));
    }
    
    public void setupBooleanSwitch(String cmd, boolean value)
    	throws CoreException
    {
        if (value) fParser.addCommand(cmd, true);
        	else fParser.removeCommand(cmd);
    }
    
    public void setupOptString(String cmd, String value, boolean chopBackslash)
    	throws CoreException
    {
        if (chopBackslash)
        {
            if (value.endsWith("\\"))
                value=value.substring(0, value.length()-1);
        }
        
        if (value.length()>0)
            fParser.addCommand(cmd, new String[]{value}, true);
        	else fParser.removeCommand(cmd);
    }
    
    public void setupOptString(String cmd, String value)
	throws CoreException
	{
        setupOptString(cmd, value, false);
	}
    
    
    public void setSrcDbg(boolean srcDbg)
    	throws CoreException
    {
        if (srcDbg!=fSrcDbg)
        {
            super.setSrcDbg(srcDbg);
            setupBooleanSwitch("SRCDBG", srcDbg);
        }
    }
    
    
    public void addCompilerOptions(String id, String options)
            throws CoreException {
        super.addCompilerOptions(id, options);
        String[] opts=options.split("\\s+");
        String[] cmdopts=new String[opts.length+1];
        cmdopts[0]=id;
        System.arraycopy(opts, 0, cmdopts, 1, opts.length);
        fParser.addCommand(MMPCommands.OPTION, cmdopts, false);
    }
    
    public void setCompilerOptions(int pos, String id, String options)
            throws CoreException {
        super.setCompilerOptions(pos, id, options);
        String[] opts=options.split("\\s+");
        String[] cmdopts=new String[opts.length+1];
        cmdopts[0]=id;
        System.arraycopy(opts, 0, cmdopts, 1, opts.length);
        fParser.replaceCommand(MMPCommands.OPTION, cmdopts, pos);
    }
    
    public void removeCompilerOptions(int pos) throws CoreException {
        super.removeCompilerOptions(pos);
        fParser.removeCommand(MMPCommands.OPTION, pos);
    }
    
    void setupUIDs()
    	throws CoreException
    {
        if (fUID2!=0 || fUID3!=0)
        {
            fParser.addCommand("UID",
                    new String[]{"0x"+Integer.toHexString(fUID2),
                    			 "0x"+Integer.toHexString(fUID3)},
                    true);
        } else fParser.removeCommand("UID");
            
    }
    
    public void setUID2(int uid) throws CoreException {
        if (uid!=fUID2)
        {
            super.setUID2(uid);
            setupUIDs();
        }
    }
    
    public void setUID3(int uid) throws CoreException {
        if (uid!=fUID3)
        {
            super.setUID3(uid);
            setupUIDs();
        }
    }
    
    public void setupHeap()
    	throws CoreException
    {
        if (fMaxHeap!=0 && fMinHeap!=0)
        {
            fParser.addCommand("EPOCHEAPSIZE",
                    new String[]{""+fMinHeap, ""+fMaxHeap}, true);
        } else fParser.removeCommand("EPOCHEAPSIZE");
    }
    
    public void setHeapMax(int heapMax) throws CoreException {
        if (heapMax!=fMaxHeap)
        {
            super.setHeapMax(heapMax);
        	setupHeap();
        }
    }
    
    public void setHeapMin(int heapMin) throws CoreException {
        if (heapMin!=fMinHeap)
        {
            super.setHeapMin(heapMin);
        	setupHeap();
        }
    }

    public void setLinkAs(String linkAs) throws CoreException {
        if (!fLinkAs.equals(linkAs))
        {
            super.setLinkAs(linkAs);
        	setupOptString("LINKAS", fLinkAs);
        }
    }
    
    public void setNoStrictDef(boolean noStrictDef) throws CoreException {
        if (noStrictDef!=fNoStrictDef)
        {
            super.setNoStrictDef(noStrictDef);
            setupBooleanSwitch("NOSTRICTDEF", noStrictDef);
        }
    }
    
    public void setPriority(String priority) throws CoreException {
        if (!fPriority.equals(priority))
        {
            super.setPriority(priority);
        	setupOptString("EPOCPROCESSPRIORITY", priority);
        }
    }
    
    public void setStackSize(int stackSize) throws CoreException {
        if (stackSize!=fStackSize)
        {
	        super.setStackSize(stackSize);
	        if (stackSize!=0)
	            fParser.addCommand("EPOCSTACKSIZE", new String[]{""+stackSize}, true);
	        	else fParser.removeCommand("EPOCSTACKSIZE");
        }
    }
    
    public void setStrictDepend(boolean strictDepend) throws CoreException {
        if (strictDepend!=fStrictDepend)
        {
            super.setStrictDepend(strictDepend);
            setupBooleanSwitch("STRICTDEPEND", strictDepend);
        }
    }
    
    public void setTargetFilename(String name) throws CoreException {
        if (!fTargetFilename.equals(name))
        {
            super.setTargetFilename(name);
        	setupOptString("TARGET", fTargetFilename);
        }
    }

    public void setTargetPath(String path) throws CoreException {
        if (!fTargetPath.equals(path))
        {
	        super.setTargetPath(path);
	        setupOptString("TARGETPATH", fTargetPath, true);
        }
    }
    
    public void setTargetType(String type) throws CoreException {
        if (!fTargetType.equals(type))
        {
	        super.setTargetType(type);
	        setupOptString("TARGETTYPE", fTargetType);
        }
    }
    
    public void addMacro(String macro) throws CoreException {
        super.addMacro(macro);
        fParser.appendToken(MMPCommands.MACRO, macro);
    }    
    
    public void setMacro(int pos, String macro) throws CoreException {
        super.setMacro(pos, macro);
        fParser.replaceCmdToken(MMPCommands.MACRO, pos, macro);
    }
    
    public void removeMacro(int pos) throws CoreException {
        super.removeMacro(pos);
        fParser.removeCmdToken(MMPCommands.MACRO, pos);
    }
    
    public void addLanguage(Language l) throws CoreException {
        super.addLanguage(l);
        fParser.appendToken("LANG", l.getCode());
    }
    
    public void removeLanguage(int pos) throws CoreException {
        super.removeLanguage(pos);
        fParser.removeCmdToken("LANG", pos);
    }
    
    public void addDebugLibrary(String name) throws CoreException {
        super.addDebugLibrary(name);
        fParser.addCommand("DEBUGLIBRARY", new String[]{name}, false);
    }

    public void addLibrary(String name) throws CoreException {
        super.addLibrary(name);
        fParser.addCommand("LIBRARY", new String[]{name}, false);
    }
    
    public void removeDebugLibrary(int pos) throws CoreException {
        super.removeDebugLibrary(pos);
        fParser.removeCmdToken("DEBUGLIBRARY", pos);
    }
    
    public void removeLibrary(int pos) throws CoreException {
        super.removeLibrary(pos);
        fParser.removeCmdToken("LIBRARY", pos);
    }
    
    public void addSystemInclude(String name) throws CoreException {
        super.addSystemInclude(name);
        fParser.addCommand(MMPCommands.SYSTEMINCLUDE, new String[]{name}, false);
    }
    
    public void addUserInclude(String name) throws CoreException {
        super.addUserInclude(name);
        fParser.addCommand(MMPCommands.USERINCLUDE, new String[]{name}, false);
    }
    
    public void removeSystemInclude(int pos) throws CoreException {
        super.removeSystemInclude(pos);
        fParser.removeCmdToken(MMPCommands.SYSTEMINCLUDE, pos);
    }
    
    public void removeUserInclude(int pos) throws CoreException {
        super.removeUserInclude(pos);
        fParser.removeCmdToken(MMPCommands.USERINCLUDE, pos);
    }
    
    public void setSystemInclude(int pos, String name) throws CoreException {
        super.setSystemInclude(pos, name);
        fParser.replaceCommand(MMPCommands.SYSTEMINCLUDE, new String[]{name}, pos);
    }
    
    public void setUserInclude(int pos, String name) throws CoreException {
        super.setUserInclude(pos, name);
        fParser.replaceCommand(MMPCommands.USERINCLUDE, new String[]{name}, pos);
    }

    /**
     * This function is overwritten in FileMMPModel and allways throws
     * an IllegalStateException
     */
    public void addResource(MMPFileReference file) throws CoreException {
        throw new IllegalStateException();
    }    
    
    public void addResource(IPath file) throws CoreException {
        MMPFileReference ref=fParser.addFileCommand(MMPCommands.RESOURCE,
                file);
        super.addResource(ref);
    }
    
    public void removeResource(int pos) throws CoreException {
        super.removeResource(pos);
        fParser.removeCmdToken(MMPCommands.RESOURCE, pos);
    }
    
    public void setResource(int pos, IPath file) throws CoreException {
        removeResource(pos);
        addResource(file);
    }

    /**
     * This function is overwritten in FileMMPModel and allways
     * throws IllegalStateException
     */
    public void setResource(int pos, MMPFileReference file)
            throws CoreException {
        throw new IllegalStateException();
    }
    
    public void addSystemResource(IPath file) throws CoreException {
        MMPFileReference ref=fParser.addFileCommand(MMPCommands.SYSTEMRESOURCE, file);
        super.addSystemResource(ref);
    }
    /**
     * This function is overwritten in FileMMPModel and allways throws
     * an IllegalStateException
     */
    public void addSystemResource(MMPFileReference file) throws CoreException {
        throw new IllegalStateException();
    }
    
    public void removeSystemResource(int pos) throws CoreException {
        super.removeSystemResource(pos);
        fParser.removeCmdToken(MMPCommands.SYSTEMRESOURCE, pos);
    }
    
    public void setSystemResource(int pos, IPath file) throws CoreException {
        removeSystemResource(pos);
        addSystemResource(file);
    }
    
    /**
     * This function is overwritten in FileMMPModel and allways throws
     * an IllegalStateException
     */
    public void setSystemResource(int pos, MMPFileReference file)
            throws CoreException {
        throw new IllegalStateException();
    }

    //==================
    //   SOURCE FILES
    //==================
    public MMPSourceFileReference[] getSourceFiles() {
        MMPSourceFileReference[] ret=super.getSourceFiles();
        if (fSupplement==null)
            return ret;
        
        IPath[] dirs=fSupplement.getSupplementSourceDirectories();
        HashSet autoFiles=new HashSet();
        for (int i=0; i<dirs.length; i++) {
	        IPath[] paths=fSupplement.getAutoFilesForDirectory(dirs[i]);
	        for (int j=0; j<paths.length; j++)
	            autoFiles.add(paths[j]);
        }
        
        for (int i=0; i<ret.length; i++) {
            IPath path=ret[i].getCanonicalPathIfRelative(fCurrentDir);
            path=PathUtils.getProjectPathForLocation(fProject, path);
            if (path!=null && autoFiles.contains(path))
                ret[i].fType=MMPSourceFileReference.ITEM_SOURCE_AUTO;
        }
        return ret;
    }
    public IPath[] getSourceDirs() throws CoreException {
        checkSupplement();
        IPath[] tab=fSupplement.getSupplementSourceDirectories();
        IPath[] ret=new IPath[tab.length];
        for (int i=0; i<tab.length; i++)
            ret[i]=PathUtils.getLocationForProjectMember(fProject, tab[i]);
        return ret;
    }
    
    public IPath[] getAllSourceAutoDirs() throws CoreException {
        IPath[] dirs=fSupplement.getSupplementSourceDirectories();
        IPath[][] autodirs=new IPath[dirs.length][];
        int count=0;
        for (int i=0; i<dirs.length; i++) {
	        autodirs[i]=fSupplement.getAutoDirsForDirectory(dirs[i]);
	        count+=autodirs[i].length;
        }
        
        IPath[] ret=new IPath[count];
        int destpos=0;
        for (int i=0; i<dirs.length; i++) {
            for (int j=0; j<autodirs[i].length; j++)
                ret[destpos++]=PathUtils.getLocationForProjectMember(fProject, autodirs[i][j]);
        }
        return ret;
    }
    
    /**
     * @return The files/directories that should not be added by
     * directories. All the paths are relative to the project root
     * @throws CoreException If something goes wrong
     */
    public IPath[] getSourceAutoDontAdd() throws CoreException {
        checkSupplement();
        return fSupplement.getSupplementDontAdd();
    }
    
    public void removeSourceDir(IPath path) throws CoreException {
        checkSupplement();
        freezeFireChanges();
        try {
	        IPath localPath=PathUtils.getProjectPathForLocation(fProject, path);
	        if (localPath==null)
	            throw new MMPModelException(MMPModelException.PATH_NOT_IN_PROJECT,
	                    "Path doesn't exist or is not relative to project");
	        SourceDirectoryWatcher watcher=(SourceDirectoryWatcher)fWatchers.get(localPath);
	        if (watcher==null)
	            throw new MMPModelException(MMPModelException.ITEM_NOT_FOUND,
	                    "Source directory not found");
	        watcher.stopWatch();
	        fWatchers.remove(localPath);
	        
	        IPath[] addedFiles=fSupplement.getAutoFilesForDirectory(localPath);
	        for (int i=0; i<addedFiles.length; i++) {
	            IPath file=PathUtils.getLocationForProjectMember(fProject, addedFiles[i]);
	            int item=findSourceFilePos(file, null);
	            if (item!=-1)
	                removeSourceFileFromMMP(item);
	        }
	        fSupplement.removeSupplementSourceDirectory(localPath);
	        fSupplement.addSupplementDontAdd(localPath);
	        fireChange(CHANGE_SOURCE_DIR);
        }
        finally {
            unfreezeFireChanges();
        }
    }
    
    protected void createWatcher(IPath path) {
        SourceDirectoryWatcher watcher=new SourceDirectoryWatcher(fProject, path, this, fCurrentDir, fSupplement);
        watcher.startWatch();
        fWatchers.put(path, watcher);
    }
    
    /**
     * This function is overwritten in FileMMPModel and adds the file
     */
    public void addSourceDir(IPath dir) throws CoreException {
        checkSupplement();
        IPath path=PathUtils.getProjectPathForLocation(fProject, dir);
        if (path==null) {
            throw new MMPModelException(MMPModelException.PATH_NOT_IN_PROJECT,
                    "Path doesn't exist or is not relative to the current project");
        }
        fSupplement.addSupplementSourceDirectory(path);
        createWatcher(path);
        fireChange(CHANGE_SOURCE_DIR);
    }
    
    public void changeAutoDirectoryOwner(IPath newDir, IPath oldDir, IPath entry) {
        fSupplement.removeSupplementDirectoryAutoFile(oldDir, entry);
        fSupplement.addSupplementDirectoryAutoFile(newDir, entry);
    }
    
    public void changeAutoFileOwner(IPath newDir, IPath oldDir, IPath entry) {
        fSupplement.removeSupplementDirectoryAutoFile(oldDir, entry);
        fSupplement.addSupplementDirectoryAutoFile(newDir, entry);
    }
    
    /**
     * This function is overwritten in FileMMPModel and adds the file
     */
    public void addSourceFile(IPath file) throws CoreException {
        MMPFileReference ref=fParser.addFileCommand(MMPCommands.SOURCE, file);
        super.addSourceMMPItem(ref);        
    }
    
    /**
     * Adds a file which have to be added because it is in a selected
     * directory. Note that the path is relative to the project 
     * @param directory Path to the directory it is in
     * @param file Path to the file to add, relative to the project root
     * @throws CoreException If something goes wrong
     */
    public void addAutoSourceFile(IPath directory, IPath file)
    	throws CoreException
    {
        checkSupplement();
        fSupplement.addSupplementDirectoryAutoFile(directory, file);
        addSourceFile(PathUtils.getLocationForProjectMember(fProject, file));
    }
    
    /**
     * Removes a file which have to be added because it is in a selected
     * directory. Note that the path is relative to the project 
     * @param directory Path to the directory it is in
     * @param file Path to the file to add, relative to the project root
     * @throws CoreException If something goes wrong
     */
    public void removeAutoSourceFile(IPath directory, IPath file)
	throws CoreException
    {
        checkSupplement();
        IPath location=PathUtils.getLocationForProjectMember(fProject, file);
        int item=findSourceFilePos(location, null);
        if (item!=-1)
            removeSourceFileFromMMP(item);
        fSupplement.removeSupplementDirectoryAutoFile(directory, file);
    }
    
    /**
     * Adds a subdirectory which have to be added because it is in
     * a selected directory
     * @param directory Path to the directory it is in
     * @param subdriectory Path to the directory, relative to the current project
     * @throws CoreException If something goes wrong
     */
    public void addAutoDirectory(IPath directory, IPath subdirectory)
    	throws CoreException
    {
        checkSupplement();
        fSupplement.addSupplementDirectoryAutoDir(directory, subdirectory);
        fireChange(CHANGE_SOURCE_DIR);
    }
    
    /**
     * Removes a subdirectory which have to be added because it is in
     * a selected directory
     * @param directory Path to the directory it is in
     * @param subdriectory Path to the directory, relative to the current project
     * @throws CoreException If something goes wrong
     */
    public void removeAutoDirectory(IPath directory, IPath subdirectory)
    	throws CoreException
    {
        checkSupplement();
        fSupplement.removeSupplementDirectoryAutoDir(directory, subdirectory);
        fSupplement.addSupplementDontAdd(subdirectory);
        fireChange(CHANGE_SOURCE_DIR);
    }
    
    public void removeSourceDontAdd(IPath path)
    	throws CoreException
    {
        checkSupplement();
        fSupplement.removeSupplementDontAdd(path);
    }
    
    /**
     * Find the position of the given source file
     * @param file The filesystem path of the file to find
     * @param rootPath The path to the SDK root. May be
     * <code>null</code> - then the absulute paths will be ignored.
     * @return The position of -1 if not found
     */
    protected int findSourceFilePos(IPath file, IPath rootPath)
	{
	    String path;
	    try {
	        path=file.toFile().getCanonicalPath();
	    }
	    catch (IOException e) {
	        SymbdevPlugin.logWarning(SymbdevPlugin.WARNING_IO_ACCESS,
	                "I/O exception while querying canoncal path", e);
	        path=file.toOSString();
	    }
	    path=path.intern();
	    Iterator it=fSourceMMPItems.iterator();
	    int item=0;
	    while (it.hasNext())
	    {
	        MMPFileReference ref=(MMPFileReference)it.next();
	        String fileref;
	        if (rootPath!=null)
	            fileref=ref.getCanonicalPath(fCurrentDir, rootPath).toOSString();
	        	else fileref=ref.getCanonicalPathIfRelative(fCurrentDir).toOSString();
	        if (fileref!=null && fileref.intern()==path)
	            return item;
	        item++;
	    }
	    return -1;
	}

    protected void removeSourceFileFromMMP(int pos)
    	throws CoreException
    {
        super.removeSourceFile(pos);
        fParser.removeCommand(MMPCommands.SOURCE, pos);        
    }
    
    /**
     * This function is overwritten in FileMMPModel and removes the file
     */
    public void removeSourceFile(IPath file, IPath rootPath) throws CoreException {
        int item=findSourceFilePos(file, rootPath);
        if (item==-1)
    	    throw new MMPModelException(MMPModelException.ITEM_NOT_FOUND, "Source file reference not found in MMP fiel");        
        removeSourceFile(item);
    }
        
    public void removeSourceFile(int pos) throws CoreException {
        MMPFileReference ref=(MMPFileReference)fSourceMMPItems.get(pos);
        IPath path=ref.getPathIfRelative(fCurrentDir);
        path=PathUtils.getProjectPathForLocation(fProject, path);
        if (path!=null) {
            checkSupplement();
            IPath dir=fSupplement.getDirectoryForAutoFile(path);
            if (dir!=null) {
                fSupplement.addSupplementDontAdd(path);
                fSupplement.removeSupplementDirectoryAutoFile(dir, path);
            }
        }
        removeSourceFileFromMMP(pos);
    }
    
    protected void storeAIFDeclaration() throws CoreException {
        MMPFileReference ref=fAIFDeclaration.fSourceFile;
        LinkedList bitmaps=fAIFDeclaration.fBitmaps;
        int argsCount=3;
        if (bitmaps.size()>0)
            argsCount+=bitmaps.size()+1;
        String[] args=new String[argsCount];
        args[0]=fAIFDeclaration.fTargetFilename;
        args[1]=ref.fSourcePathString;
        args[2]=ref.fString;
        if (bitmaps.size()>0) {
            // find the shortest sequence of color depths such that the whole
            // sequence is generated by repeating it. We build the prefix
            // table like in the KMP algorithm
            int[] kmp=new int[bitmaps.size()];
            int[] hashcodes=new int[bitmaps.size()];
            MMPBitmapReference[] bittab=fAIFDeclaration.getBitmaps();
            for (int i=0; i<bitmaps.size(); i++)
                hashcodes[i]=bittab[i].fDepth.hashCode();
            int k=0;
            kmp[0]=0;
            for (int q=1; q<bitmaps.size(); q++) {
                while (k>0) {
                    if (hashcodes[k]==hashcodes[q])                        
                        if (bittab[q].fDepth.equals(bittab[k].fDepth))
                            break;
                    k=kmp[k-1];
                }
                if (hashcodes[q]==hashcodes[k] && bittab[q].fDepth.equals(bittab[k].fDepth))
                    k++;
                kmp[q]=k;
            }
            
            int depths=bitmaps.size()-kmp[bitmaps.size()-1];
            StringBuffer buf=new StringBuffer();
            for (int i=0; i<depths; i++) {
                if (i!=0)
                    buf.append(',');
                buf.append(((MMPBitmapReference)bitmaps.get(i)).fDepth);
            }
            args[3]=buf.toString();
            for (int i=0; i<bitmaps.size(); i++) {
                args[i+4]=((MMPBitmapReference)bitmaps.get(i)).fPath.fString;
            }
        }
        argsCount=0;
        fParser.addCommand(MMPCommands.AIF, args, true);
    }
    
    protected void linkAIFDeclaration() {
        super.linkAIFDeclaration();
        fAIFDeclaration.setCurrentDir(fCurrentDir);
    }
    
    public void createAIFDeclaration(IPath file, String targetname) throws CoreException {
        IPath dirpath=file.removeLastSegments(1);
        MMPFileReference ref=new MMPFileReference(
                file.lastSegment(),
                PathUtils.makeRelativePath(dirpath, fCurrentDir));
        createAIFDeclaration(ref, targetname);
    }
    
    public void createAIFDeclaration(MMPFileReference ref, String targetname) throws CoreException {
        super.createAIFDeclaration(ref, targetname);
        storeAIFDeclaration();
    }
    
    public void removeAIFDeclaration() throws CoreException {
        super.removeAIFDeclaration();
        fParser.removeCommand(MMPCommands.AIF);
    }
    
    public void AIFDeclarationChanged() {
        if (fAIFDeclaration!=null && fAIFDeclaration.fDirty) {
            try {
                storeAIFDeclaration();
            } catch (CoreException e) {
                // TODO: send to the user
                SymbdevPlugin.log(e);
            }
            fAIFDeclaration.fDirty=false;
        }
        super.AIFDeclarationChanged();
    }
    
    /**
     * Ignores all the changes made while the document was opened
     * in an editor. Then runs the SourceDirectoryWatchers to
     * re-make the automatic changes, if any.
     * @throws CoreException Is something goes wrong
     */
    public void resetDocument() throws CoreException {
        BldInfModel.getDocumentProvider().resetDocument(
                new FileEditorInput(fFile));
        Iterator it=fWatchers.values().iterator();
        while (it.hasNext()) {
            SourceDirectoryWatcher w=(SourceDirectoryWatcher)it.next();
            w.addFiles();
        }
    }
    
    public IFile getFile() {
        return fFile;
    }
    
    public IDocument getDocument() {
        return fDoc;
    }
    
    protected MMPSupplementSupport fSupplement;
    /**
     * A IPath -> SourceDirectoryWatcher mapping
     */
    protected HashMap fWatchers=new HashMap(); 
    protected CoreException fSupplementException;
    protected IDocument fDoc;
    protected MMPParser fParser;
    protected Job fParserJob;
    protected IProject fProject;
    protected IPath fCurrentDir;
    protected IFile fFile;
}
