package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.S;

import android.system.ErrnoException;
import com.android.i18n.timezone.internal.BufferIterator;
import com.android.i18n.timezone.internal.MemoryMappedFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import libcore.io.Streams;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;

/** Fork of ShadowMemoryMappedFile to adjust to changed package names of MemoryMappedFile in S. */
@Implements(value = MemoryMappedFile.class, isInAndroidSdk = false, minSdk = S)
public class ShadowMemoryMappedFileS {
  protected byte[] bytes;
  private static final String TZ_DATA_1 = "/misc/zoneinfo/tzdata";
  private static final String TZ_DATA_2 = "/usr/share/zoneinfo/tzdata";
  private static final String TZ_DATA_3 = "/misc/zoneinfo/current/tzdata";
  private static final String TZ_DATA_4 = "/etc/tz/tzdata";

  @Implementation
  public static MemoryMappedFile mmapRO(String path) throws Throwable {
    if (path.endsWith(TZ_DATA_1)
        || path.endsWith(TZ_DATA_2)
        || path.endsWith(TZ_DATA_3)
        || path.endsWith(TZ_DATA_4)) {
      InputStream is = MemoryMappedFile.class.getResourceAsStream(TZ_DATA_2);
      if (is == null) {
        throw new ErrnoException("open", -1);
      }
      try {
        MemoryMappedFile memoryMappedFile = new MemoryMappedFile(0L, 0L);
        ShadowMemoryMappedFileS shadowMemoryMappedFile = Shadow.extract(memoryMappedFile);
        shadowMemoryMappedFile.bytes = Streams.readFully(is);
        return memoryMappedFile;
      } catch (IOException e) {
        throw new ErrnoException("mmap", -1);
      }
    } else {
      throw new IllegalArgumentException("Unknown file for mmap: '" + path);
    }
  }

  @Implementation
  public synchronized void close() throws Exception {
    bytes = null;
  }

  @Implementation
  public BufferIterator bigEndianIterator() {
    return getHeapBufferIterator(ByteOrder.BIG_ENDIAN);
  }

  @Implementation
  public BufferIterator littleEndianIterator() {
    return getHeapBufferIterator(ByteOrder.LITTLE_ENDIAN);
  }

  protected BufferIterator getHeapBufferIterator(ByteOrder endianness) {
    return new RoboBufferIterator(bytes, endianness);
  }

  @Implementation
  @SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
  public int size() {
    return bytes.length;
  }

  protected static class RoboBufferIterator extends BufferIterator {
    protected final ByteBuffer buffer;

    public RoboBufferIterator(byte[] buffer, ByteOrder order) {
      this.buffer = ByteBuffer.wrap(buffer);
    }

    @Override
    public void seek(int offset) {
      ((Buffer) buffer).position(offset);
    }

    @Override
    public void skip(int byteCount) {
      ((Buffer) buffer).position(buffer.position() + byteCount);
    }

    @Override
    public int pos() {
      return 0;
    }

    @Override
    public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
      System.arraycopy(buffer.array(), buffer.position(), dst, dstOffset, byteCount);
      skip(byteCount);
    }

    @Override
    public byte readByte() {
      return buffer.get();
    }

    @Override
    public int readInt() {
      return buffer.getInt();
    }

    @Override
    public void readIntArray(int[] dst, int dstOffset, int intCount) {
      for (int i = 0; i < intCount; i++) {
        dst[dstOffset + i] = buffer.getInt();
      }
    }

    @Override
    public void readLongArray(long[] dst, int dstOffset, int longCount) {
      for (int i = 0; i < longCount; i++) {
        dst[dstOffset + i] = buffer.getLong();
      }
    }

    @Override
    public short readShort() {
      return buffer.getShort();
    }
  }
}
