import 'dart:typed_data';

import '../animation.dart';
import '../image.dart';
import '../hdr/hdr_image.dart';
import '../util/input_buffer.dart';
import 'decoder.dart';
import 'tiff/tiff_image.dart';
import 'tiff/tiff_info.dart';

class TiffDecoder extends Decoder {
  TiffInfo info;

  /**
   * Is the given file a valid TIFF image?
   */
  bool isValidFile(List<int> data) {
    return _readHeader(new InputBuffer(data)) != null;
  }

  /**
   * Validate the file is a Gif image and get information about it.
   * If the file is not a valid Gif image, null is returned.
   */
  TiffInfo startDecode(List<int> bytes) {
    _input = new InputBuffer(new Uint8List.fromList(bytes));
    info = _readHeader(_input);
    return info;
  }

  /**
   * How many frames are available to be decoded.  [startDecode] should have
   * been called first. Non animated image files will have a single frame.
   */
  int numFrames() => info != null ? info.images.length : 0;

  /**
   * Decode a single frame from the data stat was set with [startDecode].
   * If [frame] is out of the range of available frames, null is returned.
   * Non animated image files will only have [frame] 0.  An [AnimationFrame]
   * is returned, which provides the image, and top-left coordinates of the
   * image, as animated frames may only occupy a subset of the canvas.
   */
  Image decodeFrame(int frame) {
    if (info == null) {
      return null;
    }

    return info.images[frame].decode(_input);
  }

  /**
   * Decode the file and extract a single image from it.  If the file is
   * animated, the specified [frame] will be decoded.  If there was a problem
   * decoding the file, null is returned.
   */
  Image decodeImage(List<int> data, {int frame: 0}) {
    InputBuffer ptr = new InputBuffer(new Uint8List.fromList(data));

    TiffInfo info = _readHeader(ptr);
    if (info == null) {
      return null;
    }

    return info.images[frame].decode(ptr);
  }

  HdrImage decodeHdrImage(List<int> data, {int frame: 0}) {
    InputBuffer ptr = new InputBuffer(new Uint8List.fromList(data));

    TiffInfo info = _readHeader(ptr);
    if (info == null) {
      return null;
    }

    return info.images[frame].decodeHdr(ptr);
  }

  /**
   * Decode all of the frames from an animation.  If the file is not an
   * animation, a single frame animation is returned.  If there was a problem
   * decoding the file, null is returned.
   */
  Animation decodeAnimation(List<int> data) {
    if (startDecode(data) == null) {
      return null;
    }

    Animation anim = new Animation();
    anim.width = info.width;
    anim.height = info.height;
    anim.frameType = Animation.PAGE;
    for (int i = 0, len = numFrames(); i < len; ++i) {
      Image image = decodeFrame(i);
      if (i == null) {
        continue;
      }
      anim.addFrame(image);
    }

    return anim;
  }

  /**
   * Read the TIFF header and IFD blocks.
   */
  TiffInfo _readHeader(InputBuffer p) {
    TiffInfo info = new TiffInfo();
    int byteOrder = p.readUint16();
    if (byteOrder != TIFF_LITTLE_ENDIAN &&
        byteOrder != TIFF_BIG_ENDIAN) {
      return null;
    }

    if (byteOrder == TIFF_BIG_ENDIAN) {
      p.bigEndian = true;
      info.bigEndian = true;
    } else {
      p.bigEndian = false;
      info.bigEndian = false;
    }

    info.signature = p.readUint16();
    if (info.signature != TIFF_SIGNATURE) {
      return null;
    }

    int offset = p.readUint32();
    info.ifdOffset = offset;

    InputBuffer p2 = new InputBuffer.from(p);
    p2.offset = offset;

    while (offset != 0) {
      TiffImage img;
      try {
        img = new TiffImage(p2);
        if (!img.isValid) {
          break;
        }
      } catch (error) {
        break;
      }
      info.images.add(img);
      if (info.images.length == 1) {
        info.width = info.images[0].width;
        info.height = info.images[0].height;
      }

      offset = p2.readUint32();
      if (offset != 0) {
        p2.offset = offset;
      }
    }

    return info.images.length > 0 ? info : null;
  }

  InputBuffer _input;

  static const int TIFF_SIGNATURE = 42;
  static const int TIFF_LITTLE_ENDIAN = 0x4949;
  static const int TIFF_BIG_ENDIAN = 0x4d4d;
}
