package exif

import (
    "bytes"
    "fmt"
    "strings"
    "errors"
    "reflect"

    "encoding/binary"

    "github.com/dsoprea/go-logging"
)

var (
    ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")

    ErrNoThumbnail = errors.New("no thumbnail")
)


// IfdTagEnumerator knows how to decode an IFD and all of the tags it
// describes.
//
// The IFDs and the actual values can float throughout the EXIF block, but the
// IFD itself is just a minor header followed by a set of repeating,
// statically-sized records. So, the tags (though notnecessarily their values)
// are fairly simple to enumerate.
type IfdTagEnumerator struct {
    byteOrder binary.ByteOrder
    addressableData []byte
    ifdOffset uint32
    buffer *bytes.Buffer
}

func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (ite *IfdTagEnumerator) {
    ite = &IfdTagEnumerator{
        addressableData: addressableData,
        byteOrder: byteOrder,
        buffer: bytes.NewBuffer(addressableData[ifdOffset:]),
    }

    return ite
}

// getUint16 reads a uint16 and advances both our current and our current
// accumulator (which allows us to know how far to seek to the beginning of the
// next IFD when it's time to jump).
func (ife *IfdTagEnumerator) getUint16() (value uint16, raw []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    raw = make([]byte, 2)

    _, err = ife.buffer.Read(raw)
    log.PanicIf(err)

    if ife.byteOrder == binary.BigEndian {
        value = binary.BigEndian.Uint16(raw)
    } else {
        value = binary.LittleEndian.Uint16(raw)
    }

    return value, raw, nil
}

// getUint32 reads a uint32 and advances both our current and our current
// accumulator (which allows us to know how far to seek to the beginning of the
// next IFD when it's time to jump).
func (ife *IfdTagEnumerator) getUint32() (value uint32, raw []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    raw = make([]byte, 4)

    _, err = ife.buffer.Read(raw)
    log.PanicIf(err)

    if ife.byteOrder == binary.BigEndian {
        value = binary.BigEndian.Uint32(raw)
    } else {
        value = binary.LittleEndian.Uint32(raw)
    }

    return value, raw, nil
}


type IfdEnumerate struct {
    exifData []byte
    buffer *bytes.Buffer
    byteOrder binary.ByteOrder
    currentOffset uint32
}

func NewIfdEnumerate(exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
    return &IfdEnumerate{
        exifData: exifData,
        buffer: bytes.NewBuffer(exifData),
        byteOrder: byteOrder,
    }
}

// ValueContext describes all of the parameters required to find and extract
// the actual tag value.
type ValueContext struct {
    UnitCount uint32
    ValueOffset uint32
    RawValueOffset []byte
    AddressableData []byte
}

func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) {
    ite = NewIfdTagEnumerator(
            ie.exifData[ExifAddressableAreaStart:],
            ie.byteOrder,
            ifdOffset)

    return ite
}

func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumerator) (tag *IfdTagEntry, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    tagId, _, err := ite.getUint16()
    log.PanicIf(err)

    tagType, _, err := ite.getUint16()
    log.PanicIf(err)

    unitCount, _, err := ite.getUint32()
    log.PanicIf(err)

    valueOffset, rawValueOffset, err := ite.getUint32()
    log.PanicIf(err)

    tag = &IfdTagEntry{
        Ii: ii,
        TagId: tagId,
        TagIndex: tagIndex,
        TagType: tagType,
        UnitCount: unitCount,
        ValueOffset: valueOffset,
        RawValueOffset: rawValueOffset,
    }

    value, isUnhandledUnknown, err := ie.resolveTagValue(tag)
    log.PanicIf(err)

    tag.value = value
    tag.isUnhandledUnknown = isUnhandledUnknown

    // If it's an IFD but not a standard one, it'll just be seen as a LONG
    // (the standard IFD tag type), later, unless we skip it because it's
    // [likely] not even in the standard list of known tags.
    childIfdName, isIfd := IfdTagNameWithId(ii.IfdName, tagId)
    if isIfd == true {
        tag.ChildIfdName = childIfdName
    }

    return tag, nil
}

func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, isUnhandledUnknown bool, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    addressableData := ie.exifData[ExifAddressableAreaStart:]

    // Return the exact bytes of the unknown-type value. Returning a string
    // (`ValueString`) is easy because we can just pass everything to
    // `Sprintf()`. Returning the raw, typed value (`Value`) is easy
    // (obviously). However, here, in order to produce the list of bytes, we
    // need to coerce whatever `UndefinedValue()` returns.
    if ite.TagType == TypeUndefined {
        valueContext := ValueContext{
            UnitCount: ite.UnitCount,
            ValueOffset: ite.ValueOffset,
            RawValueOffset: ite.RawValueOffset,
            AddressableData: addressableData,
        }

        value, err := UndefinedValue(ite.Ii, ite.TagId, valueContext, ie.byteOrder)
        if err != nil {
            if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
                valueBytes = []byte(UnparseableUnknownTagValuePlaceholder)
                return valueBytes, true, nil
            } else {
                log.Panic(err)
            }
        } else {
            switch value.(type) {
            case []byte:
                return value.([]byte), false, nil
            case string:
                return []byte(value.(string)), false, nil
            case UnknownTagValue:
                valueBytes, err := value.(UnknownTagValue).ValueBytes()
                log.PanicIf(err)

                return valueBytes, false, nil
            default:
// TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?)
                log.Panicf("can not produce bytes for unknown-type tag (0x%04x): [%s]", ite.TagId, reflect.TypeOf(value))
            }
        }
    } else {
        originalType := NewTagType(ite.TagType, ie.byteOrder)
        byteCount := uint32(originalType.Size()) * ite.UnitCount

        tt := NewTagType(TypeByte, ie.byteOrder)

        if tt.ValueIsEmbedded(byteCount) == true {
            iteLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).")

            // In this case, the bytes normally used for the offset are actually
            // data.
            valueBytes, err = tt.ParseBytes(ite.RawValueOffset, byteCount)
            log.PanicIf(err)
        } else {
            iteLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).")

            valueBytes, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
            log.PanicIf(err)
        }
    }

    return valueBytes, false, nil
}

// TagVisitor is an optional callback that can get hit for every tag we parse
// through. `addressableData` is the byte array startign after the EXIF header
// (where the offsets of all IFDs and values are calculated from).
type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)

// ParseIfd decodes the IFD block that we're currently sitting on the first
// byte of.
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    tagCount, _, err := ite.getUint16()
    log.PanicIf(err)

    ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)

    entries = make([]*IfdTagEntry, 0)

    var iteThumbnailOffset *IfdTagEntry
    var iteThumbnailSize *IfdTagEntry

    for i := 0; i < int(tagCount); i++ {
        tag, err := ie.parseTag(ii, i, ite)
        log.PanicIf(err)

        if tag.TagId == ThumbnailOffsetTagId {
            iteThumbnailOffset = tag

            continue
        } else if tag.TagId == ThumbnailSizeTagId {
            iteThumbnailSize = tag
            continue
        }

        if visitor != nil {
            tt := NewTagType(tag.TagType, ie.byteOrder)

            vc := ValueContext{
                UnitCount: tag.UnitCount,
                ValueOffset: tag.ValueOffset,
                RawValueOffset: tag.RawValueOffset,
                AddressableData: ie.exifData[ExifAddressableAreaStart:],
            }

            err := visitor(ii, ifdIndex, tag.TagId, tt, vc)
            log.PanicIf(err)
        }

        // If it's an IFD but not a standard one, it'll just be seen as a LONG
        // (the standard IFD tag type), later, unless we skip it because it's
        // [likely] not even in the standard list of known tags.
        if tag.ChildIfdName != "" && doDescend == true {
            ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", tag.ChildIfdName)

            childIi, _ := IfdIdOrFail(ii.IfdName, tag.ChildIfdName)

            err := ie.scan(childIi, tag.ValueOffset, visitor)
            log.PanicIf(err)
        }

        entries = append(entries, tag)
    }

    if iteThumbnailOffset != nil && iteThumbnailSize != nil {
        thumbnailData, err = ie.parseThumbnail(iteThumbnailOffset, iteThumbnailSize)
        log.PanicIf(err)
    }

    nextIfdOffset, _, err = ite.getUint32()
    log.PanicIf(err)

    ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)

    return nextIfdOffset, entries, thumbnailData, nil
}

func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumbnailData []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    addressableData := ie.exifData[ExifAddressableAreaStart:]


    vRaw, err := lengthIte.Value(addressableData, ie.byteOrder)
    log.PanicIf(err)

    vList := vRaw.([]uint32)
    if len(vList) != 1 {
        log.Panicf("not exactly one long: (%d)", len(vList))
    }

    length := vList[0]

    // The tag is official a LONG type, but it's actually an offset to a blob of bytes.
    offsetIte.TagType = TypeByte
    offsetIte.UnitCount = length

    thumbnailData, err = offsetIte.ValueBytes(addressableData, ie.byteOrder)
    log.PanicIf(err)

    return thumbnailData, nil
}

// Scan enumerates the different EXIF's IFD blocks.
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor) (err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    for ifdIndex := 0;; ifdIndex++ {
        ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, ifdIndex, ifdOffset)
        ite := ie.getTagEnumerator(ifdOffset)

        nextIfdOffset, _, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
        log.PanicIf(err)

        if nextIfdOffset == 0 {
            break
        }

        ifdOffset = nextIfdOffset
    }

    return nil
}

// Scan enumerates the different EXIF blocks (called IFDs).
func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    ii, _ := IfdIdOrFail("", IfdStandard)

    err = ie.scan(ii, ifdOffset, visitor)
    log.PanicIf(err)

    return nil
}


// Ifd represents a single parsed IFD.
type Ifd struct {
    // This is just for convenience, just so that we can easily get the values
    // and not involve other projects in semantics that they won't otherwise
    // need to know.
    addressableData []byte

    ByteOrder binary.ByteOrder

    Ii IfdIdentity
    TagId uint16

    Id int

    ParentIfd *Ifd

    // ParentTagIndex is our tag position in the parent IFD, if we had a parent
    // (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
    // instead of as a child).
    ParentTagIndex int

    Name string
    Index int
    Offset uint32

    Entries []*IfdTagEntry
    EntriesByTagId map[uint16][]*IfdTagEntry

    Children []*Ifd
    NextIfdOffset uint32
    NextIfd *Ifd

    thumbnailData []byte
}

func (ifd *Ifd) ChildWithIfdIdentity(ii IfdIdentity) (childIfd *Ifd, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    for _, childIfd := range ifd.Children {
        if childIfd.Ii == ii {
            return childIfd, nil
        }
    }

    log.Panic(ErrTagNotFound)
    return nil, nil
}

func (ifd *Ifd) ChildWithName(ifdName string) (childIfd *Ifd, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    for _, childIfd := range ifd.Children {
        if childIfd.Ii.IfdName == ifdName {
            return childIfd, nil
        }
    }

    log.Panic(ErrTagNotFound)
    return nil, nil
}

func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    value, err = ite.Value(ifd.addressableData, ifd.ByteOrder)
    log.PanicIf(err)

    return value, nil
}

func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    value, err = ite.ValueBytes(ifd.addressableData, ifd.ByteOrder)
    log.PanicIf(err)

    return value, nil
}

// FindTagWithId returns a list of tags (usually just zero or one) that match
// the given tag ID. This is efficient.
func (ifd *Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    results, found := ifd.EntriesByTagId[tagId]
    if found != true {
        log.Panic(ErrTagNotFound)
    }

    return results, nil
}

// FindTagWithName returns a list of tags (usually just zero or one) that match
// the given tag name. This is not efficient (though the labor is trivial).
func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    ii := ifd.Identity()
    it, err := tagIndex.GetWithName(ii, tagName)
    if log.Is(err, ErrTagNotFound) == true {
        log.Panic(ErrTagNotStandard)
    } else if err != nil {
        log.Panic(err)
    }

    results = make([]*IfdTagEntry, 0)
    for _, ite := range ifd.Entries {
        if ite.TagId == it.Id {
            results = append(results, ite)
        }
    }

    if len(results) == 0 {
        log.Panic(ErrTagNotFound)
    }

    return results, nil
}

func (ifd Ifd) String() string {
    parentOffset := uint32(0)
    if ifd.ParentIfd != nil {
        parentOffset = ifd.ParentIfd.Offset
    }

    return fmt.Sprintf("Ifd<ID=(%d) PARENT-IFD=[%s] IFD=[%s] INDEX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)>", ifd.Id, ifd.Ii.ParentIfdName, ifd.Ii.IfdName, ifd.Index, len(ifd.Entries), ifd.Offset, len(ifd.Children), parentOffset, ifd.NextIfdOffset)
}

func (ifd *Ifd) Thumbnail() (data []byte, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    if ifd.thumbnailData == nil {
        log.Panic(ErrNoThumbnail)
    }

    return ifd.thumbnailData, nil
}

func (ifd *Ifd) Identity() IfdIdentity {
    return ifd.Ii
}

func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
    if tags == nil {
        tags = make([]*IfdTagEntry, 0)
    }

    // Quickly create an index of the child-IFDs.

    childIfdIndex := make(map[string]*Ifd)
    for _, childIfd := range ifd.Children {
        childIfdIndex[childIfd.Ii.IfdName] = childIfd
    }

    // Now, print the tags while also descending to child-IFDS as we encounter them.

    ifdsFoundCount := 0

    for _, tag := range ifd.Entries {
        tags = append(tags, tag)

        if tag.ChildIfdName != "" {
            ifdsFoundCount++

            childIfd, found := childIfdIndex[tag.ChildIfdName]
            if found != true {
                log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
            }

            tags = childIfd.dumpTags(tags)
        }
    }

    if len(ifd.Children) != ifdsFoundCount {
        log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
    }

    if ifd.NextIfd != nil {
        tags = ifd.NextIfd.dumpTags(tags)
    }

    return tags
}

// DumpTags prints the IFD hierarchy.
func (ifd *Ifd) DumpTags() []*IfdTagEntry {
    return ifd.dumpTags(nil)
}

func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink bool) {
    indent := strings.Repeat(" ", level * 2)

    prefix := " "
    if nextLink {
        prefix = ">"
    }

    fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)

    // Quickly create an index of the child-IFDs.

    childIfdIndex := make(map[string]*Ifd)
    for _, childIfd := range ifd.Children {
        childIfdIndex[childIfd.Ii.IfdName] = childIfd
    }

    // Now, print the tags while also descending to child-IFDS as we encounter them.

    ifdsFoundCount := 0

    for _, tag := range ifd.Entries {
        if tag.ChildIfdName != "" {
            fmt.Printf("%s - TAG: %s\n", indent, tag)
        } else {
            it, err := tagIndex.Get(ifd.Identity(), tag.TagId)

            tagName := ""
            if err == nil {
                tagName = it.Name
            }

            var value interface{}
            if populateValues == true {
                var err error

                value, err = ifd.TagValue(tag)
                if err != nil {
                    if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
                        value = UnparseableUnknownTagValuePlaceholder
                    } else {
                        log.Panic(err)
                    }
                }
            }

            fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, tag, tagName, value)
        }

        if tag.ChildIfdName != "" {
            ifdsFoundCount++

            childIfd, found := childIfdIndex[tag.ChildIfdName]
            if found != true {
                log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
            }

            childIfd.printTagTree(populateValues, 0, level + 1, false)
        }
    }

    if len(ifd.Children) != ifdsFoundCount {
        log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
    }

    if ifd.NextIfd != nil {
        ifd.NextIfd.printTagTree(populateValues, index + 1, level, true)
    }
}

// PrintTagTree prints the IFD hierarchy.
func (ifd *Ifd) PrintTagTree(populateValues bool) {
    ifd.printTagTree(populateValues, 0, 0, false)
}

func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
    indent := strings.Repeat(" ", level * 2)

    prefix := " "
    if nextLink {
        prefix = ">"
    }

    fmt.Printf("%s%s%s\n", indent, prefix, ifd)

    // Quickly create an index of the child-IFDs.

    childIfdIndex := make(map[string]*Ifd)
    for _, childIfd := range ifd.Children {
        childIfdIndex[childIfd.Ii.IfdName] = childIfd
    }

    // Now, print the tags while also descending to child-IFDS as we encounter them.

    ifdsFoundCount := 0

    for _, tag := range ifd.Entries {
        if tag.ChildIfdName != "" {
            ifdsFoundCount++

            childIfd, found := childIfdIndex[tag.ChildIfdName]
            if found != true {
                log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
            }

            childIfd.printIfdTree(level + 1, false)
        }
    }

    if len(ifd.Children) != ifdsFoundCount {
        log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
    }

    if ifd.NextIfd != nil {
        ifd.NextIfd.printIfdTree(level, true)
    }
}

// PrintIfdTree prints the IFD hierarchy.
func (ifd *Ifd) PrintIfdTree() {
    ifd.printIfdTree(0, false)
}

func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
    if tagsDump == nil {
        tagsDump = make([]string, 0)
    }

    indent := strings.Repeat(" ", level * 2)

    // Quickly create an index of the child-IFDs.

    childIfdIndex := make(map[string]*Ifd)
    for _, childIfd := range ifd.Children {
        childIfdIndex[childIfd.Ii.IfdName] = childIfd
    }

    var ifdPhrase string
    if ifd.ParentIfd != nil {
        ifdPhrase = fmt.Sprintf("[%s]->[%s]:(%d)", ifd.ParentIfd.Ii.IfdName, ifd.Ii.IfdName, ifd.Index)
    } else {
        ifdPhrase = fmt.Sprintf("[ROOT]->[%s]:(%d)", ifd.Ii.IfdName, ifd.Index)
    }

    startBlurb := fmt.Sprintf("%s> IFD %s TOP", indent, ifdPhrase)
    tagsDump = append(tagsDump, startBlurb)

    ifdsFoundCount := 0
    for _, tag := range ifd.Entries {
        tagsDump = append(tagsDump, fmt.Sprintf("%s  - (0x%04x)", indent, tag.TagId))

        if tag.ChildIfdName != "" {
            ifdsFoundCount++

            childIfd, found := childIfdIndex[tag.ChildIfdName]
            if found != true {
                log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
            }

            tagsDump = childIfd.dumpTree(tagsDump, level + 1)
        }
    }

    if len(ifd.Children) != ifdsFoundCount {
        log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
    }

    finishBlurb := fmt.Sprintf("%s< IFD %s BOTTOM", indent, ifdPhrase)
    tagsDump = append(tagsDump, finishBlurb)

    if ifd.NextIfd != nil {
        siblingBlurb := fmt.Sprintf("%s* LINKING TO SIBLING IFD [%s]:(%d)", indent, ifd.NextIfd.Ii.IfdName, ifd.NextIfd.Index)
        tagsDump = append(tagsDump, siblingBlurb)

        tagsDump = ifd.NextIfd.dumpTree(tagsDump, level)
    }

    return tagsDump
}

// DumpTree returns a list of strings describing the IFD hierarchy.
func (ifd *Ifd) DumpTree() []string {
    return ifd.dumpTree(nil, 0)
}

type QueuedIfd struct {
    Ii IfdIdentity
    TagId uint16

    Index int
    Offset uint32
    Parent *Ifd

    // ParentTagIndex is our tag position in the parent IFD, if we had a parent
    // (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
    // instead of as a child).
    ParentTagIndex int
}


type IfdIndex struct {
    RootIfd *Ifd
    Ifds []*Ifd
    Tree map[int]*Ifd
    Lookup map[IfdIdentity][]*Ifd
}


// Scan enumerates the different EXIF blocks (called IFDs).
func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    tree := make(map[int]*Ifd)
    ifds := make([]*Ifd, 0)
    lookup := make(map[IfdIdentity][]*Ifd)

    queue := []QueuedIfd{
        {
            Ii: RootIi,
            TagId: 0xffff,

            Index: 0,
            Offset: rootIfdOffset,
        },
    }

    edges := make(map[uint32]*Ifd)

    for {
        if len(queue) == 0 {
            break
        }

        qi := queue[0]

        ii := qi.Ii
        name := ii.IfdName

        index := qi.Index
        offset := qi.Offset
        parentIfd := qi.Parent

        queue = queue[1:]

        ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, index, offset)
        ite := ie.getTagEnumerator(offset)

        nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(ii, index, ite, nil, false)
        log.PanicIf(err)

        id := len(ifds)

        entriesByTagId := make(map[uint16][]*IfdTagEntry)
        for _, tag := range entries {
            tags, found := entriesByTagId[tag.TagId]
            if found == false {
                tags = make([]*IfdTagEntry, 0)
            }

            entriesByTagId[tag.TagId] = append(tags, tag)
        }

        ifd := &Ifd{
            addressableData: ie.exifData[ExifAddressableAreaStart:],

            ByteOrder: ie.byteOrder,

            Ii: ii,
            TagId: qi.TagId,

            Id: id,

            ParentIfd: parentIfd,
            ParentTagIndex: qi.ParentTagIndex,

            Name: name,
            Index: index,
            Offset: offset,
            Entries: entries,
            EntriesByTagId: entriesByTagId,

            // This is populated as each child is processed.
            Children: make([]*Ifd, 0),

            NextIfdOffset: nextIfdOffset,
            thumbnailData: thumbnailData,
        }

        // Add ourselves to a big list of IFDs.
        ifds = append(ifds, ifd)

        // Install ourselves into a by-id lookup table (keys are unique).
        tree[id] = ifd

        // Install into by-name buckets.

        if list_, found := lookup[ii]; found == true {
            lookup[ii] = append(list_, ifd)
        } else {
            list_ = make([]*Ifd, 1)
            list_[0] = ifd

            lookup[ii] = list_
        }

        // Add a link from the previous IFD in the chain to us.
        if previousIfd, found := edges[offset]; found == true {
            previousIfd.NextIfd = ifd
        }

        // Attach as a child to our parent (where we appeared as a tag in
        // that IFD).
        if parentIfd != nil {
            parentIfd.Children = append(parentIfd.Children, ifd)
        }

        // Determine if any of our entries is a child IFD and queue it.
        for i, entry := range entries {
            if entry.ChildIfdName == "" {
                continue
            }

            childIi := IfdIdentity{
                ParentIfdName: name,
                IfdName: entry.ChildIfdName,
            }

            qi := QueuedIfd{
                Ii: childIi,
                TagId: entry.TagId,

                Index: 0,
                Offset: entry.ValueOffset,
                Parent: ifd,
                ParentTagIndex: i,
            }

            queue = append(queue, qi)
        }

        // If there's another IFD in the chain.
        if nextIfdOffset != 0 {
            // Allow the next link to know what the previous link was.
            edges[nextIfdOffset] = ifd

            qi := QueuedIfd{
                Ii: ii,
                TagId: 0xffff,
                Index: index + 1,
                Offset: nextIfdOffset,
            }

            queue = append(queue, qi)
        }
    }

    index.RootIfd = tree[0]
    index.Ifds = ifds
    index.Tree = tree
    index.Lookup = lookup

    return index, nil
}

// ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for
// testing.
func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitor) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    ie := &IfdEnumerate{
        byteOrder: byteOrder,
    }

    ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)

    nextIfdOffset, entries, _, err = ie.ParseIfd(ii, 0, ite, visitor, true)
    log.PanicIf(err)

    return nextIfdOffset, entries, nil
}

// ParseOneTag is a hack to use an IE to parse a raw tag block.
func ParseOneTag(ii IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (tag *IfdTagEntry, err error) {
    defer func() {
        if state := recover(); state != nil {
            err = log.Wrap(state.(error))
        }
    }()

    ie := &IfdEnumerate{
        byteOrder: byteOrder,
    }

    ite := NewIfdTagEnumerator(tagBlock, byteOrder, 0)

    tag, err = ie.parseTag(ii, 0, ite)
    log.PanicIf(err)

    return tag, nil
}