/*
 * Copyright 2000-2009 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 org.jetbrains.idea.svn.history;

import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
import com.intellij.util.containers.ContainerUtil;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class BunchFactory {
  private final LiveProvider myLiveProvider;
  private final List<Fragment> myResult;
  private final Iterator<BunchProvider> myProviderIterator;
  private BunchProvider myCurrentProvider;

  // they will move
  private int myBunchSize;
  private long myYoungest;

  public BunchFactory(final CachedProvider internallyCached, final CachedProvider visuallyCached, final LiveProvider liveProvider) {
    myLiveProvider = liveProvider;

    final List<BunchProvider> providers = new ArrayList<>();
    if (internallyCached != null) {
      providers.add(internallyCached);
    }
    if (visuallyCached != null) {
      providers.add(visuallyCached);
    }
    providers.add(myLiveProvider);

    myResult = new ArrayList<>();
    myProviderIterator = providers.iterator();
    while (myProviderIterator.hasNext()) {
      myCurrentProvider = myProviderIterator.next();
      if (! myCurrentProvider.isEmpty()) {
        break;
      }
    }
    myYoungest = -1;
  }

  public List<Fragment> goBack(final int bunchSize, final Ref<Boolean> myYoungestRead) throws VcsException {
    execute(bunchSize);
    myYoungestRead.set(myLiveProvider.isEarliestRevisionWasAccessed());
    return new ArrayList<>(myResult);
  }

  private void addToResult(final Fragment fragment) {
    if ((myBunchSize == 0) || (fragment.getList().isEmpty())) {
      return;
    }

    final List<CommittedChangeList> list = fragment.getList();

    final List<CommittedChangeList> subList = ContainerUtil.getFirstItems(list, myBunchSize);
    myResult.add(new Fragment(fragment.getOrigin(), subList, fragment.isConsistentWithOlder(), fragment.isConsistentWithYounger(),
                     fragment.getOriginBunch()));
    myBunchSize -= subList.size();
    myBunchSize = Math.max(myBunchSize, 0);
    myYoungest = subList.get(subList.size() - 1).getNumber();
  }

  private void execute(final int bunchSize) throws VcsException {
    myBunchSize = bunchSize;
    myResult.clear();

    int defender = 1000;
    while (true) {
      while (true) {
        -- defender;
        if (defender == 0) {
          return;
        }
        ProgressManager.checkCanceled();
        final Fragment fragment = myCurrentProvider.getEarliestBunchInInterval(myYoungest, 0,
                                  (myYoungest == -1) ? myBunchSize : (myBunchSize + 1), (myYoungest == -1), true);
        if ((fragment == null) || (fragment.getList().isEmpty())) {
          // switch to next provider
          break;
        }

        ProgressManager.checkCanceled();
        final List<CommittedChangeList> bunchLists = fragment.getList();
        if (! fragment.isConsistentWithYounger()) {
          final long endRevision = bunchLists.get(0).getNumber();
          if ((endRevision < myYoungest) || (myYoungest == -1)) {
            final Fragment liveFragment = myLiveProvider.getEarliestBunchInInterval(myYoungest, endRevision,
                                  (myYoungest == -1) ? (myBunchSize + 1) : (myBunchSize + 2), (myYoungest == -1), false);
            if (liveFragment != null) {
              addToResult(liveFragment);
              if (myBunchSize == 0) {
                return;
              }
            }
          }
        }
        addToResult(fragment);
        if (myBunchSize == 0) {
          return;
        }
      }
      if (myProviderIterator.hasNext()) {
        myCurrentProvider = myProviderIterator.next();
      } else {
        break;
      }
    }
  }
}
