package exifcommon

import (
	"fmt"
	"reflect"
	"sort"
	"testing"

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

func TestIfdMapping_Add(t *testing.T) {
	im := NewIfdMapping()

	err := im.Add([]uint16{}, 0x1111, "ifd0")
	log.PanicIf(err)

	err = im.Add([]uint16{0x1111}, 0x4444, "ifd00")
	log.PanicIf(err)

	err = im.Add([]uint16{0x1111, 0x4444}, 0x5555, "ifd000")
	log.PanicIf(err)

	err = im.Add([]uint16{}, 0x2222, "ifd1")
	log.PanicIf(err)

	err = im.Add([]uint16{}, 0x3333, "ifd2")
	log.PanicIf(err)

	lineages, err := im.DumpLineages()
	log.PanicIf(err)

	sort.Strings(lineages)

	expected := []string{
		"ifd0",
		"ifd0/ifd00",
		"ifd0/ifd00/ifd000",
		"ifd1",
		"ifd2",
	}

	if reflect.DeepEqual(lineages, expected) != true {
		fmt.Printf("Actual:\n")
		fmt.Printf("\n")

		for i, line := range lineages {
			fmt.Printf("(%d) %s\n", i, line)
		}

		fmt.Printf("\n")

		fmt.Printf("Expected:\n")
		fmt.Printf("\n")

		for i, line := range expected {
			fmt.Printf("(%d) %s\n", i, line)
		}

		t.Fatalf("IFD-mapping dump not correct.")
	}
}

func TestIfdMapping_LoadStandardIfds(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	lineages, err := im.DumpLineages()
	log.PanicIf(err)

	sort.Strings(lineages)

	expected := []string{
		"IFD",
		"IFD/Exif",
		"IFD/Exif/Iop",
		"IFD/GPSInfo",
	}

	if reflect.DeepEqual(lineages, expected) != true {
		fmt.Printf("Actual:\n")
		fmt.Printf("\n")

		for i, line := range lineages {
			fmt.Printf("(%d) %s\n", i, line)
		}

		fmt.Printf("\n")

		fmt.Printf("Expected:\n")
		fmt.Printf("\n")

		for i, line := range expected {
			fmt.Printf("(%d) %s\n", i, line)
		}

		t.Fatalf("IFD-mapping dump not correct.")
	}
}

func TestIfdMapping_Get(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	mi, err := im.Get([]uint16{
		IfdStandardIfdIdentity.TagId(),
		IfdExifStandardIfdIdentity.TagId(),
		IfdExifIopStandardIfdIdentity.TagId(),
	})

	log.PanicIf(err)

	if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
		t.Fatalf("Parent tag-ID not correct")
	} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
		t.Fatalf("Tag-ID not correct")
	} else if mi.Name != "Iop" {
		t.Fatalf("name not correct")
	} else if mi.PathPhrase() != "IFD/Exif/Iop" {
		t.Fatalf("path not correct")
	}
}

func TestIfdMapping_GetWithPath(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	mi, err := im.GetWithPath("IFD/Exif/Iop")
	log.PanicIf(err)

	if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
		t.Fatalf("Parent tag-ID not correct")
	} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
		t.Fatalf("Tag-ID not correct")
	} else if mi.Name != "Iop" {
		t.Fatalf("name not correct")
	} else if mi.PathPhrase() != "IFD/Exif/Iop" {
		t.Fatalf("path not correct")
	}
}

func TestIfdMapping_ResolvePath__Regular(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	lineage, err := im.ResolvePath("IFD/Exif/Iop")
	log.PanicIf(err)

	expected := []IfdTagIdAndIndex{
		{Name: "IFD", TagId: 0, Index: 0},
		{Name: "Exif", TagId: 0x8769, Index: 0},
		{Name: "Iop", TagId: 0xa005, Index: 0},
	}

	if reflect.DeepEqual(lineage, expected) != true {
		t.Fatalf("Lineage not correct.")
	}
}

func TestIfdMapping_ResolvePath__WithIndices(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	lineage, err := im.ResolvePath("IFD/Exif1/Iop")
	log.PanicIf(err)

	expected := []IfdTagIdAndIndex{
		{Name: "IFD", TagId: 0, Index: 0},
		{Name: "Exif", TagId: 0x8769, Index: 1},
		{Name: "Iop", TagId: 0xa005, Index: 0},
	}

	if reflect.DeepEqual(lineage, expected) != true {
		t.Fatalf("Lineage not correct.")
	}
}

func TestIfdMapping_ResolvePath__Miss(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	_, err = im.ResolvePath("IFD/Exif/Invalid")
	if err == nil {
		t.Fatalf("Expected failure for invalid IFD path.")
	} else if err.Error() != "ifd child with name [Invalid] not registered: [IFD/Exif/Invalid]" {
		log.Panic(err)
	}
}

func TestIfdMapping_FqPathPhraseFromLineage(t *testing.T) {
	lineage := []IfdTagIdAndIndex{
		{Name: "IFD", Index: 0},
		{Name: "Exif", Index: 1},
		{Name: "Iop", Index: 0},
	}

	im := NewIfdMapping()

	fqPathPhrase := im.FqPathPhraseFromLineage(lineage)
	if fqPathPhrase != "IFD/Exif1/Iop" {
		t.Fatalf("path-phrase not correct: [%s]", fqPathPhrase)
	}
}

func TestIfdMapping_PathPhraseFromLineage(t *testing.T) {
	lineage := []IfdTagIdAndIndex{
		{Name: "IFD", Index: 0},
		{Name: "Exif", Index: 1},
		{Name: "Iop", Index: 0},
	}

	im := NewIfdMapping()

	fqPathPhrase := im.PathPhraseFromLineage(lineage)
	if fqPathPhrase != "IFD/Exif/Iop" {
		t.Fatalf("path-phrase not correct: [%s]", fqPathPhrase)
	}
}

func TestIfdMapping_NewIfdMappingWithStandard(t *testing.T) {
	imWith, err := NewIfdMappingWithStandard()
	log.PanicIf(err)

	imWithout := NewIfdMapping()

	err = LoadStandardIfds(imWithout)
	log.PanicIf(err)

	outputWith, err := imWith.DumpLineages()
	log.PanicIf(err)

	sort.Strings(outputWith)

	outputWithout, err := imWithout.DumpLineages()
	log.PanicIf(err)

	sort.Strings(outputWithout)

	if reflect.DeepEqual(outputWith, outputWithout) != true {
		fmt.Printf("WITH:\n")
		fmt.Printf("\n")

		for _, line := range outputWith {
			fmt.Printf("%s\n", line)
		}

		fmt.Printf("\n")

		fmt.Printf("WITHOUT:\n")
		fmt.Printf("\n")

		for _, line := range outputWithout {
			fmt.Printf("%s\n", line)
		}

		fmt.Printf("\n")

		t.Fatalf("Standard IFDs not loaded correctly.")
	}
}

func TestNewIfdIdentityFromString_Valid_WithoutIndexes(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	fqIfdPath := "IFD/Exif"

	ii, err := NewIfdIdentityFromString(im, fqIfdPath)
	log.PanicIf(err)

	if ii.String() != fqIfdPath {
		t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
	}
}

func TestNewIfdIdentityFromString_Valid_WithIndexes(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	fqIfdPath := "IFD2/Exif4"

	ii, err := NewIfdIdentityFromString(im, fqIfdPath)
	log.PanicIf(err)

	if ii.String() != fqIfdPath {
		t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
	}
}

func TestNewIfdIdentityFromString_Invalid_IfdPathJustRoot(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	fqIfdPath := "XYZ"

	_, err = NewIfdIdentityFromString(im, fqIfdPath)
	if err == nil {
		t.Fatalf("Expected error from invalid path.")
	} else if err.Error() != "ifd child with name [XYZ] not registered: [XYZ]" {
		log.Panic(err)
	}
}

func TestNewIfdIdentityFromString_Invalid_IfdPathWithSubdirectory(t *testing.T) {
	im := NewIfdMapping()

	err := LoadStandardIfds(im)
	log.PanicIf(err)

	fqIfdPath := "IFD/XYZ"

	_, err = NewIfdIdentityFromString(im, fqIfdPath)
	if err == nil {
		t.Fatalf("Expected error from invalid path.")
	} else if err.Error() != "ifd child with name [XYZ] not registered: [IFD/XYZ]" {
		log.Panic(err)
	}
}
