import 'dart:typed_data';
import 'archive_exception.dart';
import 'byte_order.dart';

/**
 * A helper class to work with List and TypedData in a way similar to pointers
 * in C.
 */
class MemPtr {
  var buffer;
  int offset;
  int _length;
  int byteOrder;

  MemPtr(List<int> other, [this.offset = 0, this._length = -1,
      this.byteOrder = LITTLE_ENDIAN]) {
    buffer = other;
    if (_length < 0 || _length > buffer.length) {
      _length = buffer.length;
    }
  }

  MemPtr.from(MemPtr other, [this.offset = 0, this._length = -1]) {
    buffer = other.buffer;
    offset += other.offset;
    byteOrder = other.byteOrder;
    if (_length < 0) {
      _length = other.length;
    }
    if (_length > buffer.length) {
      _length = buffer.length;
    }
  }

  /**
   * Are we at the end of the buffer?
   */
  bool get isEOS => offset >= _length;

  /**
   * Get a byte in the buffer relative to the current read position.
   */
  int operator[](int index) => buffer[offset + index];

  /**
   * Set a byte in the buffer relative to the current read position.
   */
  operator[]=(int index, int value) => buffer[offset + index] = value;

  /**
   * The number of bytes remaining in the buffer.
   */
  int get length => _length - offset;

  /**
   * Copy data from [other] to this buffer, at [start] offset from the
   * current read position, and [length] number of bytes.  [offset] is
   * the offset in [other] to start reading.
   */
  void memcpy(int start, int length, other, [int offset = 0]) {
    if (other is MemPtr) {
      buffer.setRange(this.offset + start, this.offset + start + length,
                      other.buffer, other.offset + offset);
    } else {
      buffer.setRange(this.offset + start, this.offset + start + length,
                      other, offset);
    }
  }

  /**
   * Set a range of bytes in this buffer to [value], at [start] offset from the
   * current read position, and [length] number of bytes.
   */
  void memset(int start, int length, int value) {
    buffer.fillRange(offset + start, offset + start + length, value);
  }

  /**
   * Read a single byte.
   */
  int readByte() {
    return buffer[offset++];
  }

  /**
   * Read [count] bytes from the buffer.
   */
  List<int> readBytes(int count) {
    if (buffer is Uint8List) {
      Uint8List bytes = new Uint8List.view(buffer.buffer,
                                           buffer.offsetInBytes + offset,
                                           count);
      offset += bytes.length;
      return bytes;
    }

    List<int> bytes = (buffer as List<int>).sublist(offset, offset + count);
    offset += bytes.length;
    return bytes;
  }

  /**
   * Read a null-terminated string, or if [len] is provided, that number of
   * bytes returned as a string.
   */
  String readString([int len]) {
    if (len == null) {
      List<int> codes = [];
      while (!isEOS) {
        int c = readByte();
        if (c == 0) {
          return new String.fromCharCodes(codes);
        }
        codes.add(c);
      }
      throw new ArchiveException('EOF reached without finding string terminator');
    }

    return new String.fromCharCodes(readBytes(len));
  }

  /**
   * Read a 16-bit word from the stream.
   */
  int readUint16() {
    int b1 = buffer[offset++] & 0xff;
    int b2 = buffer[offset++] & 0xff;
    if (byteOrder == BIG_ENDIAN) {
      return (b1 << 8) | b2;
    }
    return (b2 << 8) | b1;
  }

  /**
   * Read a 24-bit word from the stream.
   */
  int readUint24() {
    int b1 = buffer[offset++] & 0xff;
    int b2 = buffer[offset++] & 0xff;
    int b3 = buffer[offset++] & 0xff;
    if (byteOrder == BIG_ENDIAN) {
      return b3 | (b2 << 8) | (b1 << 16);
    }
    return b1 | (b2 << 8) | (b3 << 16);
  }

  /**
   * Read a 32-bit word from the stream.
   */
  int readUint32() {
    int b1 = buffer[offset++] & 0xff;
    int b2 = buffer[offset++] & 0xff;
    int b3 = buffer[offset++] & 0xff;
    int b4 = buffer[offset++] & 0xff;
    if (byteOrder == BIG_ENDIAN) {
      return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
    }
    return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
  }

  /**
   * This assumes buffer is a Typed
   */
  Uint8List toUint8List([int offset = 0]) {
    return new Uint8List.view(buffer.buffer,
        buffer.offsetInBytes + this.offset + offset);
  }

  /**
   * This assumes buffer is a Typed
   */
  Uint32List toUint32List([int offset = 0]) {
    return new Uint32List.view(buffer.buffer,
        buffer.offsetInBytes + this.offset + offset);
  }
}
