// Copyright (C) 2016 The Android Open Source Project
//
// 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.google.gerrit.server.git;

import static java.util.Objects.requireNonNull;

import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PackParser;

public class InMemoryInserter extends ObjectInserter {
  private final ObjectReader reader;
  private final Map<ObjectId, InsertedObject> inserted = new LinkedHashMap<>();
  private final boolean closeReader;

  public InMemoryInserter(ObjectReader reader) {
    this.reader = requireNonNull(reader);
    closeReader = false;
  }

  public InMemoryInserter(Repository repo) {
    this.reader = repo.newObjectReader();
    closeReader = true;
  }

  @Override
  public ObjectId insert(int type, long length, InputStream in) throws IOException {
    return insert(InsertedObject.create(type, in));
  }

  @Override
  public ObjectId insert(int type, byte[] data) {
    return insert(type, data, 0, data.length);
  }

  @Override
  public ObjectId insert(int type, byte[] data, int off, int len) {
    return insert(InsertedObject.create(type, data, off, len));
  }

  public ObjectId insert(InsertedObject obj) {
    inserted.put(obj.id(), obj);
    return obj.id();
  }

  @Override
  public PackParser newPackParser(InputStream in) {
    throw new UnsupportedOperationException();
  }

  @Override
  public ObjectReader newReader() {
    return new Reader();
  }

  @Override
  public void flush() {
    // Do nothing; objects are not written to the repo.
  }

  @Override
  public void close() {
    if (closeReader) {
      reader.close();
    }
  }

  public ImmutableList<InsertedObject> getInsertedObjects() {
    return ImmutableList.copyOf(inserted.values());
  }

  public int getInsertedObjectCount() {
    return inserted.values().size();
  }

  public void clear() {
    inserted.clear();
  }

  private class Reader extends ObjectReader {
    @Override
    public ObjectReader newReader() {
      return new Reader();
    }

    @Override
    public Collection<ObjectId> resolve(AbbreviatedObjectId id) throws IOException {
      Set<ObjectId> result = new HashSet<>();
      for (ObjectId insId : inserted.keySet()) {
        if (id.prefixCompare(insId) == 0) {
          result.add(insId);
        }
      }
      result.addAll(reader.resolve(id));
      return result;
    }

    @Override
    public ObjectLoader open(AnyObjectId objectId, int typeHint) throws IOException {
      InsertedObject obj = inserted.get(objectId);
      if (obj == null) {
        return reader.open(objectId, typeHint);
      }
      if (typeHint != OBJ_ANY && obj.type() != typeHint) {
        throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
      }
      return obj.newLoader();
    }

    @Override
    public Set<ObjectId> getShallowCommits() throws IOException {
      return reader.getShallowCommits();
    }

    @Override
    public void close() {
      // Do nothing; this class owns no open resources.
    }

    @Override
    public ObjectInserter getCreatedFromInserter() {
      return InMemoryInserter.this;
    }
  }
}
