// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.android.ziputils;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple in-memory test file system.
 */
class FakeFileSystem extends FileSystem  {

  private final Map<String, byte[]> files = new HashMap<>();

  public FakeFileSystem() {
    FileSystem.fileSystem = this;
  }

  public void addFile(String name, byte[] content) {
    files.put(name, content);
  }

  public void addFile(String name, String content) {
    files.put(name, content.getBytes(UTF_8));
  }

  public byte[] toByteArray(String filename) throws IOException {
    byte[] data = files.get(filename);
    if (data == null) {
      throw new FileNotFoundException();
    }
    return data;
  }

  @Override
  public FileChannel getInputChannel(String filename) throws IOException {
    return new FakeReadChannel(filename);
  }

  @Override
  public FileChannel getOutputChannel(String filename, boolean append) throws IOException {
    return new FakeWriteChannel(filename);
  }

  @Override
  public InputStream getInputStream(String filename) throws IOException {
    byte[] data = files.get(filename);
    if (data == null) {
      throw new FileNotFoundException();
    }
    return new ByteArrayInputStream(data);
  }

  class FakeReadChannel extends FileChannel {

    byte[] data;
    int position;

    public FakeReadChannel(String filename) throws IOException {
      this.data = toByteArray(filename);
      this.position = 0;
    }

    @Override
    public int read(ByteBuffer dst) throws IOException {
      if (position >= data.length) {
        return -1;
      }
      int remaining = data.length - position;
      if (dst.remaining() < remaining) {
        remaining = dst.remaining();
      }
      dst.put(data, position, remaining);
      position += remaining;
      return remaining;
    }

    @Override
    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int read(ByteBuffer dst, long position) throws IOException {
      if (position < 0 || position >= data.length) {
        throw new IOException("out of bounds");
      }
      int remaining = data.length - (int) position;
      if (dst.remaining() < remaining) {
        remaining = dst.remaining();
      }
      dst.put(data, (int) position, remaining);
      return remaining;
    }

    @Override
    public int write(ByteBuffer src) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int write(ByteBuffer src, long position) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long position() throws IOException {
      return position;
    }

    @CanIgnoreReturnValue
    @Override
    public FileChannel position(long newPosition) throws IOException {
      position = (int) newPosition;
      return this;
    }

    @Override
    public long size() throws IOException {
      return data.length;
    }

    @Override
    public FileChannel truncate(long size) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void force(boolean metaData) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long transferTo(long position, long count, WritableByteChannel target)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long transferFrom(ReadableByteChannel src, long position, long count)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public FileLock lock(long position, long size, boolean shared) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected void implCloseChannel() throws IOException {
    }
  }

  class FakeWriteChannel extends FileChannel {

    final String name;
    final ByteArrayOutputStream buf;
    int position;

    public FakeWriteChannel(String filename) {
      this.name = filename;
      this.buf = new ByteArrayOutputStream();
      this.position = 0;
    }

    @Override
    public int read(ByteBuffer dst) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int read(ByteBuffer dst, long position) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int write(ByteBuffer src) throws IOException {
      byte[] bytes = new byte[src.remaining()];
      src.get(bytes);
      buf.write(bytes);
      position += bytes.length;
      return bytes.length;
    }

    @Override
    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int write(ByteBuffer src, long position) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long position() throws IOException {
      return position;
    }

    @Override
    public FileChannel position(long newPosition) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long size() throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public FileChannel truncate(long size) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void force(boolean metaData) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long transferTo(long position, long count, WritableByteChannel target)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public long transferFrom(ReadableByteChannel src, long position, long count)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
        throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public FileLock lock(long position, long size, boolean shared) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
      throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected void implCloseChannel() throws IOException {
      files.put(name, buf.toByteArray());
    }
  }
}
