/*
 * Copyright 2000-2014 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.vcs.log.impl;

import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsKey;
import com.intellij.openapi.vcs.changes.committed.MockAbstractVcs;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.Semaphore;

import static org.junit.Assert.assertEquals;

public class TestVcsLogProvider implements VcsLogProvider {

  public static final VcsRefType BRANCH_TYPE = new VcsRefType() {
    @Override
    public boolean isBranch() {
      return true;
    }

    @NotNull
    @Override
    public Color getBackgroundColor() {
      return Color.white;
    }
  };
  private static final String SAMPLE_SUBJECT = "Sample subject";
  private static final VcsUser STUB_USER = new VcsUserImpl("John Smith", "John.Smith@mail.com");

  @NotNull private final VirtualFile myRoot;
  @NotNull private final List<TimedVcsCommit> myCommits;
  @NotNull private final Set<VcsRef> myRefs;
  @NotNull private final MockRefManager myRefManager;
  @NotNull private final ReducibleSemaphore myFullLogSemaphore;
  @NotNull private final ReducibleSemaphore myRefreshSemaphore;
  private int myReadFirstBlockCounter;

  private final Function<TimedVcsCommit, VcsCommitMetadata> myCommitToMetadataConvertor =
    new Function<TimedVcsCommit, VcsCommitMetadata>() {
      @Override
      public VcsCommitMetadata fun(TimedVcsCommit commit) {
        return new VcsCommitMetadataImpl(commit.getId(), commit.getParents(), commit.getTimestamp(), myRoot,
                                                               SAMPLE_SUBJECT, STUB_USER, SAMPLE_SUBJECT, STUB_USER, commit.getTimestamp());
      }
    };

  public TestVcsLogProvider(@NotNull VirtualFile root) {
    myRoot = root;
    myCommits = ContainerUtil.newArrayList();
    myRefs = ContainerUtil.newHashSet();
    myRefManager = new MockRefManager();
    myFullLogSemaphore = new ReducibleSemaphore();
    myRefreshSemaphore = new ReducibleSemaphore();
  }

  @NotNull
  @Override
  public DetailedLogData readFirstBlock(@NotNull final VirtualFile root,
                                                   @NotNull Requirements requirements)
    throws VcsException {
    if (requirements instanceof VcsLogProviderRequirementsEx && ((VcsLogProviderRequirementsEx)requirements).isRefresh()) {
      try {
        myRefreshSemaphore.acquire();
      }
      catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
    myReadFirstBlockCounter++;
    assertRoot(root);
    List<VcsCommitMetadata> metadatas = ContainerUtil.map(myCommits.subList(0, requirements.getCommitCount()),
                                                          myCommitToMetadataConvertor);
    return new LogDataImpl(Collections.<VcsRef>emptySet(), metadatas);
  }

  @NotNull
  @Override
  public LogData readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
    try {
      myFullLogSemaphore.acquire();
    }
    catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    assertRoot(root);
    for (TimedVcsCommit commit : myCommits) {
      commitConsumer.consume(commit);
    }
    return new LogDataImpl(myRefs, Collections.<VcsUser>emptySet());
  }

  private void assertRoot(@NotNull VirtualFile root) {
    assertEquals("Requested data for unknown root", myRoot, root);
  }

  @NotNull
  @Override
  public List<? extends VcsShortCommitDetails> readShortDetails(@NotNull VirtualFile root, @NotNull List<String> hashes)
    throws VcsException {
    throw new UnsupportedOperationException();
  }

  @NotNull
  @Override
  public List<? extends VcsFullCommitDetails> readFullDetails(@NotNull VirtualFile root, @NotNull List<String> hashes) throws VcsException {
    throw new UnsupportedOperationException();
  }

  @NotNull
  @Override
  public VcsKey getSupportedVcs() {
    return MockAbstractVcs.getKey();
  }

  @NotNull
  @Override
  public VcsLogRefManager getReferenceManager() {
    return myRefManager;
  }

  @Override
  public void subscribeToRootRefreshEvents(@NotNull Collection<VirtualFile> roots, @NotNull VcsLogRefresher refresher) {
    throw new UnsupportedOperationException();
  }

  @NotNull
  @Override
  public List<TimedVcsCommit> getCommitsMatchingFilter(@NotNull VirtualFile root,
                                                       @NotNull VcsLogFilterCollection filterCollection,
                                                       int maxCount) throws VcsException {
    throw new UnsupportedOperationException();
  }

  @Nullable
  @Override
  public VcsUser getCurrentUser(@NotNull VirtualFile root) throws VcsException {
    return STUB_USER;
  }

  @NotNull
  @Override
  public Collection<String> getContainingBranches(@NotNull VirtualFile root, @NotNull Hash commitHash) throws VcsException {
    throw new UnsupportedOperationException();
  }

  public void appendHistory(@NotNull List<TimedVcsCommit> commits) {
    myCommits.addAll(0, commits);
  }

  public void addRef(@NotNull VcsRef ref) {
    myRefs.add(ref);
  }

  public void blockRefresh() {
    myRefreshSemaphore.block();
  }

  public void unblockRefresh() {
    myRefreshSemaphore.unblock();
  }

  public void blockFullLog() throws InterruptedException {
    myFullLogSemaphore.block();
  }

  public void unblockFullLog() {
    myFullLogSemaphore.unblock();
  }

  public void resetReadFirstBlockCounter() {
    myReadFirstBlockCounter = 0;
  }

  public int getReadFirstBlockCounter() {
    return myReadFirstBlockCounter;
  }

  @Nullable
  @Override
  public <T> T getPropertyValue(VcsLogProperties.VcsLogProperty<T> property) {
    return null;
  }

  private static class MockRefManager implements VcsLogRefManager {

    public static final Comparator<VcsRef> FAKE_COMPARATOR = new Comparator<VcsRef>() {
      @Override
      public int compare(VcsRef o1, VcsRef o2) {
        return 0;
      }
    };

    @NotNull
    @Override
    public Comparator<VcsRef> getLabelsOrderComparator() {
      return FAKE_COMPARATOR;
    }

    @NotNull
    @Override
    public List<RefGroup> group(Collection<VcsRef> refs) {
      return ContainerUtil.map(refs, new Function<VcsRef, RefGroup>() {
        @Override
        public RefGroup fun(VcsRef ref) {
          return new SingletonRefGroup(ref);
        }
      });
    }

    @NotNull
    @Override
    public Comparator<VcsRef> getBranchLayoutComparator() {
      return FAKE_COMPARATOR;
    }
  }

  private static class ReducibleSemaphore extends Semaphore {
    private boolean myBlocked;

    public ReducibleSemaphore() {
      super(1);
    }

    @Override
    public void acquire() throws InterruptedException {
      if (myBlocked) {
        super.acquire();
      }
    }

    public void block() {
      myBlocked = true;
      reducePermits(1);
    }

    public void unblock() {
      myBlocked = false;
      release();
    }

  }
}

@@ -1581,30 +1581,29 @@
 ubject%22;%0A  p
-rivate
+ublic
  static fina
@@ -1612,20 +1612,23 @@
 VcsUser 
-STUB
+DEFAULT
 _USER = 
@@ -2381,39 +2381,47 @@
  myRoot,
-%0A                      
+ SAMPLE_SUBJECT, DEFAULT_USER,%0A
         
@@ -2473,39 +2473,15 @@
 CT, 
-STUB_USER, SAMPLE_SUBJECT, STUB
+DEFAULT
 _USE
@@ -5543,12 +5543,15 @@
 urn 
-STUB
+DEFAULT
 _USE

make DEFAULT_USER public to reuse in other tests