package exif

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"time"

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

func DumpBytes(data []byte) {
	fmt.Printf("DUMP: ")
	for _, x := range data {
		fmt.Printf("%02x ", x)
	}

	fmt.Printf("\n")
}

func DumpBytesClause(data []byte) {
	fmt.Printf("DUMP: ")

	fmt.Printf("[]byte { ")

	for i, x := range data {
		fmt.Printf("0x%02x", x)

		if i < len(data)-1 {
			fmt.Printf(", ")
		}
	}

	fmt.Printf(" }\n")
}

func DumpBytesToString(data []byte) string {
	b := new(bytes.Buffer)

	for i, x := range data {
		_, err := b.WriteString(fmt.Sprintf("%02x", x))
		log.PanicIf(err)

		if i < len(data)-1 {
			_, err := b.WriteRune(' ')
			log.PanicIf(err)
		}
	}

	return b.String()
}

func DumpBytesClauseToString(data []byte) string {
	b := new(bytes.Buffer)

	for i, x := range data {
		_, err := b.WriteString(fmt.Sprintf("0x%02x", x))
		log.PanicIf(err)

		if i < len(data)-1 {
			_, err := b.WriteString(", ")
			log.PanicIf(err)
		}
	}

	return b.String()
}

// ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC
// `time.Time` struct.
func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	parts := strings.Split(fullTimestampPhrase, " ")
	datestampValue, timestampValue := parts[0], parts[1]

	dateParts := strings.Split(datestampValue, ":")

	year, err := strconv.ParseUint(dateParts[0], 10, 16)
	if err != nil {
		log.Panicf("could not parse year")
	}

	month, err := strconv.ParseUint(dateParts[1], 10, 8)
	if err != nil {
		log.Panicf("could not parse month")
	}

	day, err := strconv.ParseUint(dateParts[2], 10, 8)
	if err != nil {
		log.Panicf("could not parse day")
	}

	timeParts := strings.Split(timestampValue, ":")

	hour, err := strconv.ParseUint(timeParts[0], 10, 8)
	if err != nil {
		log.Panicf("could not parse hour")
	}

	minute, err := strconv.ParseUint(timeParts[1], 10, 8)
	if err != nil {
		log.Panicf("could not parse minute")
	}

	second, err := strconv.ParseUint(timeParts[2], 10, 8)
	if err != nil {
		log.Panicf("could not parse second")
	}

	timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC)
	return timestamp, nil
}

// ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a
// `time.Time` struct. It will attempt to convert to UTC first.
func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) {
	t = t.UTC()

	return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
}

// ExifTag is one simple representation of a tag in a flat list of all of them.
type ExifTag struct {
	IfdPath string `json:"ifd_path"`

	TagId   uint16 `json:"id"`
	TagName string `json:"name"`

	TagTypeId   TagTypePrimitive `json:"type_id"`
	TagTypeName string           `json:"type_name"`
	Value       interface{}      `json:"value"`
	ValueBytes  []byte           `json:"value_bytes"`

	ChildIfdPath string `json:"child_ifd_path"`
}

// String returns a string representation.
func (et ExifTag) String() string {
	return fmt.Sprintf("ExifTag<IFD-PATH=[%s] TAG-ID=(0x%02x) TAG-NAME=[%s] TAG-TYPE=[%s] VALUE=[%v] VALUE-BYTES=(%d) CHILD-IFD-PATH=[%s]", et.IfdPath, et.TagId, et.TagName, et.TagTypeName, et.Value, len(et.ValueBytes), et.ChildIfdPath)
}

// GetFlatExifData returns a simple, flat representation of all tags.
func GetFlatExifData(exifData []byte) (exifTags []ExifTag, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	im := NewIfdMappingWithStandard()
	ti := NewTagIndex()

	_, index, err := Collect(im, ti, exifData)
	log.PanicIf(err)

	q := []*Ifd{index.RootIfd}

	exifTags = make([]ExifTag, 0)

	for len(q) > 0 {
		var ifd *Ifd
		ifd, q = q[0], q[1:]

		ti := NewTagIndex()
		for _, ite := range ifd.Entries {
			tagName := ""

			it, err := ti.Get(ifd.IfdPath, ite.TagId)
			if err != nil {
				// If it's a non-standard tag, just leave the name blank.
				if log.Is(err, ErrTagNotFound) != true {
					log.PanicIf(err)
				}
			} else {
				tagName = it.Name
			}

			value, err := ifd.TagValue(ite)
			if err != nil {
				if err == ErrUnhandledUnknownTypedTag {
					value = UnparseableUnknownTagValuePlaceholder
				} else {
					log.Panic(err)
				}
			}

			valueBytes, err := ifd.TagValueBytes(ite)
			if err != nil && err != ErrUnhandledUnknownTypedTag {
				log.Panic(err)
			}

			et := ExifTag{
				IfdPath:      ifd.IfdPath,
				TagId:        ite.TagId,
				TagName:      tagName,
				TagTypeId:    ite.TagType,
				TagTypeName:  TypeNames[ite.TagType],
				Value:        value,
				ValueBytes:   valueBytes,
				ChildIfdPath: ite.ChildIfdPath,
			}

			exifTags = append(exifTags, et)
		}

		for _, childIfd := range ifd.Children {
			q = append(q, childIfd)
		}

		if ifd.NextIfd != nil {
			q = append(q, ifd.NextIfd)
		}
	}

	return exifTags, nil
}