import 'dart:typed_data';

import '../../image_exception.dart';
import '../../util/input_buffer.dart';

class PsdChannel {
  static const int RED = 0;
  static const int GREEN = 1;
  static const int BLUE = 2;
  static const int BLACK = 3;
  static const int ALPHA = -1;
  static const int MASK = -2;
  static const int REAL_MASK = -3;

  static const int COMPRESS_NONE = 0;
  static const int COMPRESS_RLE = 1;
  static const int COMPRESS_ZIP = 2;
  static const int COMPRESS_ZIP_PREDICTOR = 3;

  int id;
  int dataLength;
  Uint8List data;

  PsdChannel(this.id, this.dataLength);

  PsdChannel.read(InputBuffer input, this.id, int width, int height,
                  int bitDepth, int compression, Uint16List lineLengths,
                  int planeNumber) {
    readPlane(input, width, height, bitDepth, compression, lineLengths,
              planeNumber);
  }

  void readPlane(InputBuffer input, int width, int height, int bitDepth,
                 [int compression, Uint16List lineLengths, int planeNum = 0]) {
    if (compression == null) {
      compression = input.readUint16();
    }

    switch (compression) {
      case COMPRESS_NONE:
        _readPlaneUncompressed(input, width, height, bitDepth);
        break;
      case COMPRESS_RLE:
        if (lineLengths == null) {
          lineLengths = _readLineLengths(input, height);
        }
        _readPlaneRleCompressed(input, width, height, bitDepth, lineLengths,
                                planeNum);
        break;
      default:
        throw new ImageException('Unsupported compression: $compression');
    }
  }

  Uint16List _readLineLengths(InputBuffer input, int height) {
    Uint16List lineLengths = new Uint16List(height);
    for (int i = 0; i < height; ++i) {
      lineLengths[i] = input.readUint16();
    }
    return lineLengths;
  }

  void _readPlaneUncompressed(InputBuffer input, int width, int height,
                              int bitDepth) {
    int len = width * height;
    if (bitDepth == 16) {
      len *= 2;
    }
    if (len > input.length) {
      data = new Uint8List(len);
      data.fillRange(0, len, 255);
      return;
    }

    InputBuffer imgData = input.readBytes(len);
    data = imgData.toUint8List();
  }

  void _readPlaneRleCompressed(InputBuffer input, int width, int height,
                               int bitDepth, Uint16List lineLengths,
                               int planeNum) {
    int len = width * height;
    if (bitDepth == 16) {
      len *= 2;
    }
    data = new Uint8List(len);
    int pos = 0;
    int lineIndex = planeNum * height;
    if (lineIndex >= lineLengths.length) {
      data.fillRange(0, data.length, 255);
      return;
    }

    for (int i = 0; i < height; ++i) {
      int len = lineLengths[lineIndex++];
      InputBuffer s = input.readBytes(len);
      _decodeRLE(s, data, pos);
      pos += width;
    }
  }

  void _decodeRLE(InputBuffer src, Uint8List dst, int dstIndex) {
    while (!src.isEOS) {
      int n = src.readInt8();
      if (n < 0) {
        n = 1 - n;
        int b = src.readByte();
        for (int i = 0; i < n; ++i) {
          dst[dstIndex++] = b;
        }
      } else {
        n++;
        for (int i = 0; i < n; ++i) {
          dst[dstIndex++] = src.readByte();
        }
      }
    }
  }
}
