/*
 * Copyright 2000-2010 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.diff.impl.patch;

import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsOutgoingChangesProvider;
import com.intellij.openapi.vcs.VcsRoot;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.util.BeforeAfter;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

public class IdeaTextPatchBuilder {
  private IdeaTextPatchBuilder() {
  }

  public static List<BeforeAfter<AirContentRevision>> revisionsConvertor(final Project project, final List<Change> changes) throws VcsException {
    final List<BeforeAfter<AirContentRevision>> result = new ArrayList<BeforeAfter<AirContentRevision>>(changes.size());

    final Convertor<Change, FilePath> beforePrefferingConvertor = new Convertor<Change, FilePath>() {
      public FilePath convert(Change o) {
        final FilePath before = ChangesUtil.getBeforePath(o);
        return before == null ? ChangesUtil.getAfterPath(o) : before;
      }
    };
    final MultiMap<VcsRoot,Change> byRoots = new SortByVcsRoots<Change>(project, beforePrefferingConvertor).sort(changes);

    for (VcsRoot root : byRoots.keySet()) {
      final VcsOutgoingChangesProvider<?> provider = root.vcs.getOutgoingChangesProvider();
      final Collection<Change> rootChanges = byRoots.get(root);
      if (provider == null) {
        addConvertChanges(rootChanges, result);
        continue;
      }
      final Collection<Change> outGoingOnly = provider.whichAreOutgoingChanges(rootChanges, new Convertor<Change, VcsRevisionNumber>() {
        public VcsRevisionNumber convert(Change ch) {
          return (ch.getBeforeRevision() == null ? ch.getAfterRevision() : ch.getBeforeRevision()).getRevisionNumber();
        }
      }, new Convertor<Change, FilePath>() {
        public FilePath convert(Change ch) {
          return (ch.getBeforeRevision() == null ? ch.getAfterRevision() : ch.getBeforeRevision()).getFile();
        }
      }, root.path);

      changes.removeAll(outGoingOnly);
      addConvertChanges(changes, result);

      for (Change change : outGoingOnly) {
        result.add(new BeforeAfter<AirContentRevision>(convertRevision(change.getBeforeRevision(), provider),
                                                       convertRevision(change.getAfterRevision(), provider)));
      }
    }
    return result;
  }

  private static void addConvertChanges(final Collection<Change> changes, final List<BeforeAfter<AirContentRevision>> result) {
    for (Change change : changes) {
      result.add(new BeforeAfter<AirContentRevision>(convertRevisionToAir(change.getBeforeRevision()), convertRevisionToAir(change.getAfterRevision())));
    }
  }

  public static List<FilePatch> buildPatch(final Project project, final Collection<Change> changes, final String basePath, final boolean reversePatch) throws VcsException {
    final Collection<BeforeAfter<AirContentRevision>> revisions;
    if (project != null) {
      revisions = revisionsConvertor(project, new ArrayList<Change>(changes));
    } else {
      revisions = new ArrayList<BeforeAfter<AirContentRevision>>(changes.size());
      for (Change change : changes) {
        revisions.add(new BeforeAfter<AirContentRevision>(convertRevisionToAir(change.getBeforeRevision()), convertRevisionToAir(change.getAfterRevision())));
      }
    }
    return TextPatchBuilder.buildPatch(revisions, basePath, reversePatch, SystemInfo.isFileSystemCaseSensitive, new Runnable() {
      public void run() {
        ProgressManager.checkCanceled();
      }
    });
  }

  @Nullable
  private static AirContentRevision convertRevisionToAir(final ContentRevision cr) {
    return convertRevisionToAir(cr, null);
  }

  @Nullable
  private static AirContentRevision convertRevisionToAir(final ContentRevision cr, final Long ts) {
    if (cr == null) return null;
    final FilePath fp = cr.getFile();
    final StaticPathDescription description = new StaticPathDescription(fp.isDirectory(),
                                                                        ts == null ? fp.getIOFile().lastModified() : ts, fp.getPath());
    if (cr instanceof BinaryContentRevision) {
      return new AirContentRevision() {
        public boolean isBinary() {
          return true;
        }
        public String getContentAsString() {
          throw new IllegalStateException();
        }
        public byte[] getContentAsBytes() throws VcsException {
          return ((BinaryContentRevision) cr).getBinaryContent();
        }
        public String getRevisionNumber() {
          return ts != null ? null : cr.getRevisionNumber().asString();
        }
        @NotNull
        public PathDescription getPath() {
          return description;
        }
      };
    } else {
      return new AirContentRevision() {
        public boolean isBinary() {
          return false;
        }
        public String getContentAsString() throws VcsException {
          return cr.getContent();
        }
        public byte[] getContentAsBytes() throws VcsException {
          throw new IllegalStateException();
        }
        public String getRevisionNumber() {
          return ts != null ? null : cr.getRevisionNumber().asString();
        }
        @NotNull
        public PathDescription getPath() {
          return description;
        }
      };
    }
  }

  @Nullable
  private static AirContentRevision convertRevision(@Nullable final ContentRevision cr, final VcsOutgoingChangesProvider provider) {
    if (cr == null) return null;
    final Date date = provider.getRevisionDate(cr.getRevisionNumber());
    final Long ts = date == null ? null : date.getTime();
    return convertRevisionToAir(cr, ts);
  }
}

@@ -2204,100 +2204,8 @@
 ) %7B%0A
-      final VcsOutgoingChangesProvider%3C?%3E provider = root.vcs.getOutgoingChangesProvider();%0A
     
@@ -2274,24 +2274,73 @@
     if (
-p
+root.vcs == null %7C%7C root.vcs.getOutgoingChangesP
 rovider
+()
  == null
@@ -2413,24 +2413,116 @@
 ue;%0A      %7D%0A
+      final VcsOutgoingChangesProvider%3C?%3E provider = root.vcs.getOutgoingChangesProvider();%0A
       final 

fix tests (text patch builder), sorry