package collabode.collab;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.text.edits.ReplaceEdit;

import collabode.*;

/**
 * Commits error-free changes in Java files.
 */
public class JavaCommitter extends WorkingCopyOwner implements CollabListener, Runnable, IProblemRequestor {
    
    private final BlockingQueue<CollabDocument> queue = new LinkedBlockingQueue<CollabDocument>();
    private final BlockingQueue<IProblem[]> reported = new LinkedBlockingQueue<IProblem[]>(1);
    
    public JavaCommitter(Collab collab) {
        new Thread(this, getClass().getSimpleName() + " " + collab.id).start();
    }
    
    public void updated(PadDocument doc) {
        queue.add(doc.collab);
    }
    
    public void committed(CollabDocument doc) { }
    
    public void run() {
        while (true) {
            try {
                CollabDocument doc = queue.take();
                queue.remove(doc);
                handleUpdated(doc);
            } catch (BadLocationException ble) {
                ble.printStackTrace(); // XXX
            } catch (JavaModelException jme) {
                jme.printStackTrace(); // XXX
            } catch (InterruptedException ie) {
                ie.printStackTrace(); // XXX
            }
        }
    }
    
    private void handleUpdated(CollabDocument doc) throws BadLocationException, JavaModelException, InterruptedException {
        List<ReplaceEdit> edits = new ArrayList<ReplaceEdit>();
        
        List<IRegion> regions = doc.unionOnlyRegionsOfDisk();
        for (IRegion region : regions) {
            int diskOffset = doc.unionToLocalOffsetOfDisk(region.getOffset());
            edits.add(new ReplaceEdit(diskOffset, 0, doc.union.get(region.getOffset(), region.getLength())));
        }
        
        regions = doc.localOnlyRegionsOfDisk();
        for (IRegion region : regions) {
            int diskOffset = doc.unionToLocalOffsetOfDisk(region.getOffset()) - region.getLength();
            edits.add(new ReplaceEdit(diskOffset, region.getLength(), ""));
        }
        
        handleEdits(doc, edits);
    }
    
    private void handleEdits(CollabDocument doc, List<ReplaceEdit> options) throws BadLocationException, JavaModelException, InterruptedException {
        Collections.sort(options, new Comparator<ReplaceEdit>() {
            public int compare(ReplaceEdit e1, ReplaceEdit e2) {
                return e2.getOffset() - e1.getOffset();
            }
        });
        
        if ( ! JavaCore.isJavaLikeFileName(doc.file.getName())) { // XXX duplicated from PadDocumentOwner, fishy
            doc.commitDiskCoordinateEdits(options); // XXX is this right?
            return;
        }
        
        ICompilationUnit wc = JavaCore.createCompilationUnitFrom(doc.file).getWorkingCopy(this, null);
        final String disk = wc.getBuffer().getContents();
        reported.take(); // XXX problems generated by working copy creation
        
        Collection<ReplaceEdit> accepted = bestEdits(wc, disk, options);
        wc.discardWorkingCopy();
        
        if ( ! accepted.isEmpty()) {
            doc.commitDiskCoordinateEdits(accepted);
        }
    }
    
    private Collection<ReplaceEdit> bestEdits(ICompilationUnit wc, String disk, List<ReplaceEdit> options) throws JavaModelException, InterruptedException {
        if (options.isEmpty()) { return options; }
        
        Collection<ReplaceEdit> best = Collections.emptyList();
        
        for (Collection<ReplaceEdit> subset : new PowerSet<ReplaceEdit>(options)) {
            if (subset.size() <= best.size()) { continue; }
            
            wc.getBuffer().setContents(disk);
            for (ReplaceEdit edit : subset) {
                wc.getBuffer().replace(edit.getOffset(), edit.getLength(), edit.getText());
            }
            wc.reconcile(ICompilationUnit.NO_AST, true, this, null);
            IProblem[] problems = reported.take();
            
            if (problems.length == 0) { best = subset; }
        }
        
        return best;
    }
    
    // Extending WorkingCopyOwner
    
    @Override public IProblemRequestor getProblemRequestor(ICompilationUnit workingCopy) {
        return this;
    }
    
    // Implementing IProblemRequestor
    
    private final List<IProblem> problems = new LinkedList<IProblem>();
    
    public void beginReporting() {
        problems.clear();
    }
    
    public void acceptProblem(IProblem problem) {
        if (problem.isError()) { problems.add(problem); }
    }
    
    public void endReporting() {
        reported.add(problems.toArray(new IProblem[0]));
    }
    
    public boolean isActive() {
        return true;
    }
}

/**
 * Power set.
 * XXX Would be nice to enumerate subsets from largest to smallest.
 * 
 * @see http://code.google.com/p/guava-libraries/source/browse/trunk/guava/src/com/google/common/collect/Sets.java
 * @see http://www.pingel.org/xref/org/pingel/util/PowerSet.html
 */
class PowerSet<E> extends AbstractCollection<Collection<E>> {
    
    private final List<E> elements;
    
    PowerSet(Collection<E> elements) {
        this.elements = new ArrayList<E>(elements);
    }
    
    public int size() {
        return 1 << elements.size();
    }
    
    public Iterator<Collection<E>> iterator() {
        return new Iterator<Collection<E>>() { // iterator over power set sets
            int next = 1 << elements.size(); // start with the full set...
            
            public void remove() {
                throw new UnsupportedOperationException();
            }
            
            public boolean hasNext() {
                return next > 0; // ... and end with the empty set
            }
            
            public Collection<E> next() {
                final int next = --this.next;
                return new AbstractCollection<E>() {
                    public int size() {
                        return Integer.bitCount(next);
                    }
                    
                    public Iterator<E> iterator() { // iterator over power set set elements
                        return new Iterator<E>() {
                            int mask = next; // bitmask of elements to include
                            
                            public void remove() {
                                throw new UnsupportedOperationException();
                            }
                            
                            public boolean hasNext() {
                                return mask > 0;
                            }
                            
                            public E next() {
                                int idx = Integer.numberOfTrailingZeros(mask); // position of first 1 bit
                                mask &= ~(1 << idx); // knock out that bit
                                return elements.get(idx);
                            }
                        };
                    }
                };
            }
        };
    }
}

@@ -2639,22 +2639,28 @@
         
-return
+int offset =
  e2.getO
@@ -2681,24 +2681,151 @@
 etOffset();%0A
+                if (offset != 0) %7B return offset; %7D%0A                return e2.getLength() - e1.getLength(); // deletions first%0A
             

Fix edit sort so deletion occurs before insertion at the same index