import 'dart:typed_data';

import 'package:exif/src/exifheader.dart';
import 'package:exif/src/field_types.dart';
import 'package:exif/src/reader.dart';

class Thumbnail {
  final Map<String, IfdTagImpl> tags;
  final IfdReader file;

  Thumbnail(this.tags, this.file);

  // Extract uncompressed TIFF thumbnail.
  // Take advantage of the pre-existing layout in the thumbnail IFD as
  // much as possible
  List<int>? extractTiffThumbnail(int thumbIfd) {
    final thumb = tags['Thumbnail Compression'];
    if (thumb == null || thumb.tag.printable != 'Uncompressed TIFF') {
      return null;
    }

    List<int> tiff;
    int stripOff = 0;
    int stripLen = 0;

    final entries = file.readInt(thumbIfd, 2);
    // this is header plus offset to IFD ...
    if (file.endian == Endian.big) {
      tiff = 'MM\x00*\x00\x00\x00\x08'.codeUnits;
    } else {
      tiff = 'II*\x00\x08\x00\x00\x00'.codeUnits;
      // ... plus thumbnail IFD data plus a null "next IFD" pointer
    }

    tiff.addAll(file.readSlice(thumbIfd, entries * 12 + 2));
    tiff.addAll([0, 0, 0, 0]);

    // fix up large value offset pointers into data area
    for (int i = 0; i < entries; i++) {
      final entry = thumbIfd + 2 + 12 * i;
      final tag = file.readInt(entry, 2);
      final fieldType = file.readInt(entry + 2, 2);
      final typeLength = fieldTypes[fieldType].length;
      final count = file.readInt(entry + 4, 4);
      final oldOffset = file.readInt(entry + 8, 4);
      // start of the 4-byte pointer area in entry
      final ptr = i * 12 + 18;
      // remember strip offsets location
      if (tag == 0x0111) {
        stripOff = ptr;
        stripLen = count * typeLength;
        // is it in the data area?
      }
      if (count * typeLength > 4) {
        // update offset pointer (nasty "strings are immutable" crap)
        // should be able to say "tiff[ptr:ptr+4]=newOffset"
        final tiff0 = tiff;
        final newOffset = tiff0.length;
        tiff = tiff0.sublist(0, ptr);
        tiff.addAll(file.offsetToBytes(newOffset, 4));
        tiff.addAll(tiff0.sublist(ptr + 4));
        // remember strip offsets location
        if (tag == 0x0111) {
          stripOff = newOffset;
          stripLen = 4;
        }
        // get original data and store it
        tiff.addAll(file.readSlice(oldOffset, count * typeLength));
      }
    }

    // add pixel strips and update strip offset info
    final oldOffsets = tags['Thumbnail StripOffsets']?.tag.values.toList();
    final oldCounts = tags['Thumbnail StripByteCounts']?.tag.values.toList();
    if (oldOffsets == null || oldCounts == null) {
      return null;
    }

    for (int i = 0; i < oldOffsets.length; i++) {
      // update offset pointer (more nasty "strings are immutable" crap)
      final tiff0 = tiff;
      final offset = file.offsetToBytes(tiff0.length, stripLen);
      tiff = tiff0.sublist(0, stripOff);
      tiff.addAll(offset);
      tiff.addAll(tiff0.sublist(stripOff + stripLen));
      stripOff += stripLen;
      // add pixel strip to end
      tiff.addAll(file.readSlice(oldOffsets[i] as int, oldCounts[i] as int));
    }

    return tiff;
  }

  // Extract JPEG thumbnail.
  // (Thankfully the JPEG data is stored as a unit.)
  List<int>? extractJpegThumbnail() {
    final thumbFmt = tags['Thumbnail JPEGInterchangeFormat'];
    final thumbFmtLen = tags['Thumbnail JPEGInterchangeFormatLength'];
    if (thumbFmt != null && thumbFmtLen != null) {
      final size = thumbFmtLen.tag.values.firstAsInt();
      final values = file.readSlice(thumbFmt.tag.values.firstAsInt(), size);
      return values;
    }

    // Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
    // since it's not allowed in a uncompressed TIFF IFD
    final thumbnail = tags['MakerNote JPEGThumbnail'];
    if (thumbnail != null) {
      final values = file.readSlice(
          thumbnail.tag.values.firstAsInt(), thumbnail.fieldLength);
      return values;
    }

    return null;
  }
}
