package PDF::Builder::Resource::XObject::Image::TIFF::File;

use strict;
use warnings;

our $VERSION = '3.019'; # VERSION
my $LAST_UPDATE = '3.001'; # manually update whenever code is changed

use IO::File;

=head1 NAME

PDF::Builder::Resource::XObject::Image::TIFF::File - support routines for TIFF image library

=cut

sub new {
    my ($class, $file) = @_;

    my $self = {};
    bless ($self, $class);
    if (ref($file)) {
        $self->{'fh'} = $file;
        seek($self->{'fh'}, 0, 0);
    } else {
        $self->{'fh'} = IO::File->new();
        open($self->{'fh'}, '<', $file) or die "$!: $file";
    }
    binmode($self->{'fh'}, ':raw');
    my $fh = $self->{'fh'};

    $self->{'offset'} = 0;
    $fh->seek($self->{'offset'}, 0);

    # checking byte order of data
    $fh->read($self->{'byteOrder'}, 2);
    $self->{'byte'} = 'C';
    $self->{'short'} = (($self->{'byteOrder'} eq 'MM')? 'n': 'v');
    $self->{'long'} = (($self->{'byteOrder'} eq 'MM')? 'N': 'V');
    $self->{'rational'} = (($self->{'byteOrder'} eq 'MM')? 'NN': 'VV');

    # get/check version id
    $fh->read($self->{'version'}, 2);
    $self->{'version'} = unpack($self->{'short'}, $self->{'version'});
    die "Wrong TIFF Id '" . $self->{'version'} . "' (should be 42)." if $self->{'version'} != 42;

    # get the offset to the first tag directory.
    $fh->read($self->{'ifdOffset'}, 4);
    $self->{'ifdOffset'} = unpack($self->{'long'}, $self->{'ifdOffset'});

    $self->readTags();

    return $self;
}

sub readTag {
    my $self = shift;

    my $fh = $self->{'fh'};
    my $buf;
    $fh->read($buf, 12);
    my $tag = unpack($self->{'short'}, substr($buf, 0, 2));
    my $type = unpack($self->{'short'}, substr($buf, 2, 2));
    my $count = unpack($self->{'long'}, substr($buf, 4, 4));
    my $len = 0;

    $len = ($type == 1? $count  : # byte
            $type == 2? $count  : # char2
            $type == 3? $count*2: # int16
            $type == 4? $count*4: # int32
            $type == 5? $count*8: # rational: 2 * int32
            $count);

    my $off = substr($buf, 8, 4);

    if ($len > 4) {
        $off = unpack($self->{'long'}, $off);
    } else {
        $off = ($type == 1? unpack($self->{'byte'},  $off):
                $type == 2? unpack($self->{'long'},  $off):
                $type == 3? unpack($self->{'short'}, $off):
                $type == 4? unpack($self->{'long'},  $off):
                unpack($self->{'short'}, $off) );
    }

    return ($tag, $type, $count, $len, $off);
}

sub close { ## no critic
    my $self = shift;

    return $self->{'fh'}->close();
}

sub readTags {
    my $self = shift;

    my $fh = $self->{'fh'};
    $self->{'fillOrder'} = 1;
    $self->{'ifd'} = $self->{'ifdOffset'};

    while ($self->{'ifd'} > 0) {
        $fh->seek($self->{'ifd'}, 0);
        $fh->read($self->{'ifdNum'}, 2);
        $self->{'ifdNum'} = unpack($self->{'short'}, $self->{'ifdNum'});
        $self->{'bitsPerSample'} = 1;
        foreach (1 .. $self->{'ifdNum'}) {
            my ($valTag, $valType, $valCount, $valLen, $valOffset) = $self->readTag();
            # print "tag=$valTag type=$valType count=$valCount len=$valLen off=$valOffset\n";
            if      ($valTag == 0) {
	        # no-op
            } elsif ($valTag == 256) {
                $self->{'imageWidth'} = $valOffset;
            } elsif ($valTag == 257) {
                $self->{'imageHeight'} = $valOffset;
            } elsif ($valTag == 258) {
                # bits per sample
                if ($valCount > 1) {
                    my $here = $fh->tell();
                    my $val;
                    $fh->seek($valOffset, 0);
                    $fh->read($val, 2);
                    $self->{'bitsPerSample'} = unpack($self->{'short'}, $val);
                    $fh->seek($here, 0);
                } else {
                    $self->{'bitsPerSample'} = $valOffset;
                }
            } elsif ($valTag == 259) {
                # compression
                $self->{'filter'} = $valOffset;
                if      ($valOffset == 1) {
                    delete $self->{'filter'};
                } elsif ($valOffset == 3 || $valOffset == 4) {
                    $self->{'filter'} = 'CCITTFaxDecode';
                    $self->{'ccitt'} = $valOffset;
                } elsif ($valOffset == 5) {
                    $self->{'filter'} = 'LZWDecode';
                } elsif ($valOffset == 6 || $valOffset == 7) {
                    $self->{'filter'} = 'DCTDecode';
                } elsif ($valOffset == 8 || $valOffset == 0x80b2) {
                    $self->{'filter'} = 'FlateDecode';
                } elsif ($valOffset == 32773) {
                    $self->{'filter'} = 'RunLengthDecode';
                } else {
                    die "unknown/unsupported TIFF compression method with id '" . $self->{'filter'} . "'.";
                }
            } elsif ($valTag == 262) {
                # photometric interpretation
                $self->{'colorSpace'} = $valOffset;
                if ($valOffset == 0) {
                    $self->{'colorSpace'} = 'DeviceGray';
                    $self->{'whiteIsZero'} = 1;
                } elsif ($valOffset == 1) {
                    $self->{'colorSpace'} = 'DeviceGray';
                    $self->{'blackIsZero'} = 1;
                } elsif ($valOffset == 2) {
                    $self->{'colorSpace'} = 'DeviceRGB';
                } elsif ($valOffset == 3) {
                    $self->{'colorSpace'} = 'Indexed';
		# } elsif ($valOffset == 4) {
                #     $self->{'colorSpace'} = 'TransMask';
                } elsif ($valOffset == 5) {
                    $self->{'colorSpace'} = 'DeviceCMYK';
                } elsif ($valOffset == 6) {
                    $self->{'colorSpace'} = 'DeviceRGB';
                } elsif ($valOffset == 8) {
                    $self->{'colorSpace'} = 'Lab';
                } else {
                    die "unknown/unsupported TIFF photometric interpretation with id '" . $self->{'colorSpace'} . "'.";
                }
            } elsif ($valTag == 266) {
                $self->{'fillOrder'} = $valOffset;
            } elsif ($valTag == 270) {
                # ImageDescription
                my $here = $fh->tell();
                $fh->seek($valOffset, 0);
                $fh->read($self->{'imageDescription'}, $valLen);
                $fh->seek($here, 0);
            } elsif ($valTag == 282) {
                # xRes
                my $here = $fh->tell();
                $fh->seek($valOffset, 0);
                $fh->read($self->{'xRes'}, $valLen);
                $fh->seek($here, 0);
                $self->{'xRes'} = [unpack($self->{'rational'}, $self->{'xRes'})];
                $self->{'xRes'} = ($self->{'xRes'}->[0] / $self->{'xRes'}->[1]);
            } elsif ($valTag == 283) {
                # yRes
                my $here = $fh->tell();
                $fh->seek($valOffset, 0);
                $fh->read($self->{'yRes'}, $valLen);
                $fh->seek($here, 0);
                $self->{'yRes'} = [unpack($self->{'rational'}, $self->{'yRes'})];
                $self->{'yRes'} = ($self->{'yRes'}->[0] / $self->{'yRes'}->[1]);
            } elsif ($valTag == 296) {
                # resolution Unit
                $self->{'resUnit'} = $valOffset;
            } elsif ($valTag == 273) {
                # image data offset/strip offsets
                if ($valCount == 1) {
                    $self->{'imageOffset'} = $valOffset;
                } else {
                    my $here = $fh->tell();
                    my $val;
                    $fh->seek($valOffset, 0);
                    $fh->read($val, $valLen);
                    $fh->seek($here, 0);
                    $self->{'imageOffset'} = [unpack($self->{'long'} . '*', $val)];
                }
            } elsif ($valTag == 277) {
                $self->{'samplesPerPixel'} = $valOffset;
            } elsif ($valTag == 278) {
                $self->{'RowsPerStrip'} = $valOffset;
            } elsif ($valTag == 279) {
                # image data length/strip lengths
                if ($valCount == 1) {
                    $self->{'imageLength'} = $valOffset;
                } else {
                    my $here = $fh->tell();
                    my $val;
                    $fh->seek($valOffset, 0);
                    $fh->read($val, $valLen);
                    $fh->seek($here, 0);
                    $self->{'imageLength'} = [unpack($self->{'long'} . '*', $val)];
                }
            } elsif ($valTag == 292) {
                $self->{'g3Options'} = $valOffset;
            } elsif ($valTag == 293) {
                $self->{'g4Options'} = $valOffset;
            } elsif ($valTag == 320) {
                # color map
                $self->{'colorMapOffset'} = $valOffset;
                $self->{'colorMapSamples'} = $valCount;
                $self->{'colorMapLength'} = $valCount*2; # shorts!
            } elsif ($valTag == 317) {
                $self->{'lzwPredictor'} = $valOffset;
            } elsif ($valTag == 0x800d) {
                # imageID
                my $here = $fh->tell();
                $fh->seek($valOffset, 0);
                $fh->read($self->{'imageId'}, $valLen);
                $fh->seek($here, 0);
            # } else {
            #     print "tag=$valTag, type=$valType, len=$valLen\n";
            }
        } # end foreach loop
        
	$fh->read($self->{'ifd'}, 4);
        $self->{'ifd'} = unpack($self->{'long'}, $self->{'ifd'});
    } # end while loop

    return $self;
}

1;
