package humanize_test

import (
	"math"
	"testing"
	"time"

	"github.com/git-lfs/git-lfs/tools/humanize"
	"github.com/stretchr/testify/assert"
)

type ParseBytesTestCase struct {
	Given    string
	Expected uint64
	Err      error
}

func (c *ParseBytesTestCase) Assert(t *testing.T) {
	got, err := humanize.ParseBytes(c.Given)
	if c.Err == nil {
		assert.NoError(t, err, "unexpected error: %s", err)
		assert.EqualValues(t, c.Expected, got)
	} else {
		assert.Equal(t, c.Err, err)
	}
}

type FormatBytesTestCase struct {
	Given    uint64
	Expected string
}

func (c *FormatBytesTestCase) Assert(t *testing.T) {
	assert.Equal(t, c.Expected, humanize.FormatBytes(c.Given))
}

type ParseByteUnitTestCase struct {
	Given    string
	Expected uint64
	Err      string
}

func (c *ParseByteUnitTestCase) Assert(t *testing.T) {
	got, err := humanize.ParseByteUnit(c.Given)
	if len(c.Err) == 0 {
		assert.NoError(t, err, "unexpected error: %s", err)
		assert.EqualValues(t, c.Expected, got)
	} else {
		assert.EqualError(t, err, c.Err)
	}
}

type FormatBytesUnitTestCase struct {
	Given    uint64
	Unit     uint64
	Expected string
}

func (c *FormatBytesUnitTestCase) Assert(t *testing.T) {
	assert.Equal(t, c.Expected, humanize.FormatBytesUnit(c.Given, c.Unit))
}

type FormatByteRateTestCase struct {
	Given    uint64
	Over     time.Duration
	Expected string
}

func (c *FormatByteRateTestCase) Assert(t *testing.T) {
	assert.Equal(t, c.Expected, humanize.FormatByteRate(c.Given, c.Over))
}

func TestParseBytes(t *testing.T) {
	for desc, c := range map[string]*ParseBytesTestCase{
		"parse byte (zero, empty)": {"", uint64(0), nil},
		"parse byte (empty)":       {"10", uint64(10 * math.Pow(2, 0)), nil},
		"parse byte":               {"10B", uint64(10 * math.Pow(2, 0)), nil},
		"parse kibibyte":           {"20KIB", uint64(20 * math.Pow(2, 10)), nil},
		"parse mebibyte":           {"30MIB", uint64(30 * math.Pow(2, 20)), nil},
		"parse gibibyte":           {"40GIB", uint64(40 * math.Pow(2, 30)), nil},
		"parse tebibyte":           {"50TIB", uint64(50 * math.Pow(2, 40)), nil},
		"parse pebibyte":           {"60PIB", uint64(60 * math.Pow(2, 50)), nil},

		"parse byte (lowercase)":     {"10b", uint64(10 * math.Pow(2, 0)), nil},
		"parse kibibyte (lowercase)": {"20kib", uint64(20 * math.Pow(2, 10)), nil},
		"parse mebibyte (lowercase)": {"30mib", uint64(30 * math.Pow(2, 20)), nil},
		"parse gibibyte (lowercase)": {"40gib", uint64(40 * math.Pow(2, 30)), nil},
		"parse tebibyte (lowercase)": {"50tib", uint64(50 * math.Pow(2, 40)), nil},
		"parse pebibyte (lowercase)": {"60pib", uint64(60 * math.Pow(2, 50)), nil},

		"parse byte (with space)":     {"10 B", uint64(10 * math.Pow(2, 0)), nil},
		"parse kibibyte (with space)": {"20 KIB", uint64(20 * math.Pow(2, 10)), nil},
		"parse mebibyte (with space)": {"30 MIB", uint64(30 * math.Pow(2, 20)), nil},
		"parse gibibyte (with space)": {"40 GIB", uint64(40 * math.Pow(2, 30)), nil},
		"parse tebibyte (with space)": {"50 TIB", uint64(50 * math.Pow(2, 40)), nil},
		"parse pebibyte (with space)": {"60 PIB", uint64(60 * math.Pow(2, 50)), nil},

		"parse byte (with space, lowercase)":     {"10 b", uint64(10 * math.Pow(2, 0)), nil},
		"parse kibibyte (with space, lowercase)": {"20 kib", uint64(20 * math.Pow(2, 10)), nil},
		"parse mebibyte (with space, lowercase)": {"30 mib", uint64(30 * math.Pow(2, 20)), nil},
		"parse gibibyte (with space, lowercase)": {"40 gib", uint64(40 * math.Pow(2, 30)), nil},
		"parse tebibyte (with space, lowercase)": {"50 tib", uint64(50 * math.Pow(2, 40)), nil},
		"parse pebibyte (with space, lowercase)": {"60 pib", uint64(60 * math.Pow(2, 50)), nil},

		"parse kilobyte": {"20KB", uint64(20 * math.Pow(10, 3)), nil},
		"parse megabyte": {"30MB", uint64(30 * math.Pow(10, 6)), nil},
		"parse gigabyte": {"40GB", uint64(40 * math.Pow(10, 9)), nil},
		"parse terabyte": {"50TB", uint64(50 * math.Pow(10, 12)), nil},
		"parse petabyte": {"60PB", uint64(60 * math.Pow(10, 15)), nil},

		"parse kilobyte (lowercase)": {"20kb", uint64(20 * math.Pow(10, 3)), nil},
		"parse megabyte (lowercase)": {"30mb", uint64(30 * math.Pow(10, 6)), nil},
		"parse gigabyte (lowercase)": {"40gb", uint64(40 * math.Pow(10, 9)), nil},
		"parse terabyte (lowercase)": {"50tb", uint64(50 * math.Pow(10, 12)), nil},
		"parse petabyte (lowercase)": {"60pb", uint64(60 * math.Pow(10, 15)), nil},

		"parse kilobyte (with space)": {"20 KB", uint64(20 * math.Pow(10, 3)), nil},
		"parse megabyte (with space)": {"30 MB", uint64(30 * math.Pow(10, 6)), nil},
		"parse gigabyte (with space)": {"40 GB", uint64(40 * math.Pow(10, 9)), nil},
		"parse terabyte (with space)": {"50 TB", uint64(50 * math.Pow(10, 12)), nil},
		"parse petabyte (with space)": {"60 PB", uint64(60 * math.Pow(10, 15)), nil},

		"parse kilobyte (with space, lowercase)": {"20 kb", uint64(20 * math.Pow(10, 3)), nil},
		"parse megabyte (with space, lowercase)": {"30 mb", uint64(30 * math.Pow(10, 6)), nil},
		"parse gigabyte (with space, lowercase)": {"40 gb", uint64(40 * math.Pow(10, 9)), nil},
		"parse terabyte (with space, lowercase)": {"50 tb", uint64(50 * math.Pow(10, 12)), nil},
		"parse petabyte (with space, lowercase)": {"60 pb", uint64(60 * math.Pow(10, 15)), nil},
	} {
		t.Run(desc, c.Assert)
	}
}

func TestFormatBytes(t *testing.T) {
	for desc, c := range map[string]*FormatBytesTestCase{
		"format bytes":     {uint64(1 * math.Pow(10, 0)), "1 B"},
		"format kilobytes": {uint64(1 * math.Pow(10, 3)), "1.0 KB"},
		"format megabytes": {uint64(1 * math.Pow(10, 6)), "1.0 MB"},
		"format gigabytes": {uint64(1 * math.Pow(10, 9)), "1.0 GB"},
		"format petabytes": {uint64(1 * math.Pow(10, 12)), "1.0 TB"},
		"format terabytes": {uint64(1 * math.Pow(10, 15)), "1.0 PB"},

		"format kilobytes under": {uint64(1.49 * math.Pow(10, 3)), "1.5 KB"},
		"format megabytes under": {uint64(1.49 * math.Pow(10, 6)), "1.5 MB"},
		"format gigabytes under": {uint64(1.49 * math.Pow(10, 9)), "1.5 GB"},
		"format petabytes under": {uint64(1.49 * math.Pow(10, 12)), "1.5 TB"},
		"format terabytes under": {uint64(1.49 * math.Pow(10, 15)), "1.5 PB"},

		"format kilobytes over": {uint64(1.51 * math.Pow(10, 3)), "1.5 KB"},
		"format megabytes over": {uint64(1.51 * math.Pow(10, 6)), "1.5 MB"},
		"format gigabytes over": {uint64(1.51 * math.Pow(10, 9)), "1.5 GB"},
		"format petabytes over": {uint64(1.51 * math.Pow(10, 12)), "1.5 TB"},
		"format terabytes over": {uint64(1.51 * math.Pow(10, 15)), "1.5 PB"},

		"format kilobytes exact": {uint64(1.3 * math.Pow(10, 3)), "1.3 KB"},
		"format megabytes exact": {uint64(1.3 * math.Pow(10, 6)), "1.3 MB"},
		"format gigabytes exact": {uint64(1.3 * math.Pow(10, 9)), "1.3 GB"},
		"format petabytes exact": {uint64(1.3 * math.Pow(10, 12)), "1.3 TB"},
		"format terabytes exact": {uint64(1.3 * math.Pow(10, 15)), "1.3 PB"},
	} {
		t.Run(desc, c.Assert)
	}
}

func TestParseByteUnit(t *testing.T) {
	for desc, c := range map[string]*ParseByteUnitTestCase{
		"parse byte":     {"B", uint64(math.Pow(2, 0)), ""},
		"parse kibibyte": {"KIB", uint64(math.Pow(2, 10)), ""},
		"parse mebibyte": {"MIB", uint64(math.Pow(2, 20)), ""},
		"parse gibibyte": {"GIB", uint64(math.Pow(2, 30)), ""},
		"parse tebibyte": {"TIB", uint64(math.Pow(2, 40)), ""},
		"parse pebibyte": {"PIB", uint64(math.Pow(2, 50)), ""},

		"parse byte (lowercase)":     {"b", uint64(math.Pow(2, 0)), ""},
		"parse kibibyte (lowercase)": {"kib", uint64(math.Pow(2, 10)), ""},
		"parse mebibyte (lowercase)": {"mib", uint64(math.Pow(2, 20)), ""},
		"parse gibibyte (lowercase)": {"gib", uint64(math.Pow(2, 30)), ""},
		"parse tebibyte (lowercase)": {"tib", uint64(math.Pow(2, 40)), ""},
		"parse pebibyte (lowercase)": {"pib", uint64(math.Pow(2, 50)), ""},

		"parse byte (with space)":     {" B", uint64(math.Pow(2, 0)), ""},
		"parse kibibyte (with space)": {" KIB", uint64(math.Pow(2, 10)), ""},
		"parse mebibyte (with space)": {" MIB", uint64(math.Pow(2, 20)), ""},
		"parse gibibyte (with space)": {" GIB", uint64(math.Pow(2, 30)), ""},
		"parse tebibyte (with space)": {" TIB", uint64(math.Pow(2, 40)), ""},
		"parse pebibyte (with space)": {" PIB", uint64(math.Pow(2, 50)), ""},

		"parse byte (with space, lowercase)":     {" b", uint64(math.Pow(2, 0)), ""},
		"parse kibibyte (with space, lowercase)": {" kib", uint64(math.Pow(2, 10)), ""},
		"parse mebibyte (with space, lowercase)": {" mib", uint64(math.Pow(2, 20)), ""},
		"parse gibibyte (with space, lowercase)": {" gib", uint64(math.Pow(2, 30)), ""},
		"parse tebibyte (with space, lowercase)": {" tib", uint64(math.Pow(2, 40)), ""},
		"parse pebibyte (with space, lowercase)": {" pib", uint64(math.Pow(2, 50)), ""},

		"parse kilobyte": {"KB", uint64(math.Pow(10, 3)), ""},
		"parse megabyte": {"MB", uint64(math.Pow(10, 6)), ""},
		"parse gigabyte": {"GB", uint64(math.Pow(10, 9)), ""},
		"parse terabyte": {"TB", uint64(math.Pow(10, 12)), ""},
		"parse petabyte": {"PB", uint64(math.Pow(10, 15)), ""},

		"parse kilobyte (lowercase)": {"kb", uint64(math.Pow(10, 3)), ""},
		"parse megabyte (lowercase)": {"mb", uint64(math.Pow(10, 6)), ""},
		"parse gigabyte (lowercase)": {"gb", uint64(math.Pow(10, 9)), ""},
		"parse terabyte (lowercase)": {"tb", uint64(math.Pow(10, 12)), ""},
		"parse petabyte (lowercase)": {"pb", uint64(math.Pow(10, 15)), ""},

		"parse kilobyte (with space)": {" KB", uint64(math.Pow(10, 3)), ""},
		"parse megabyte (with space)": {" MB", uint64(math.Pow(10, 6)), ""},
		"parse gigabyte (with space)": {" GB", uint64(math.Pow(10, 9)), ""},
		"parse terabyte (with space)": {" TB", uint64(math.Pow(10, 12)), ""},
		"parse petabyte (with space)": {" PB", uint64(math.Pow(10, 15)), ""},

		"parse kilobyte (with space, lowercase)": {"kb", uint64(math.Pow(10, 3)), ""},
		"parse megabyte (with space, lowercase)": {"mb", uint64(math.Pow(10, 6)), ""},
		"parse gigabyte (with space, lowercase)": {"gb", uint64(math.Pow(10, 9)), ""},
		"parse terabyte (with space, lowercase)": {"tb", uint64(math.Pow(10, 12)), ""},
		"parse petabyte (with space, lowercase)": {"pb", uint64(math.Pow(10, 15)), ""},

		"parse unknown unit": {"imag", 0, "unknown unit: \"imag\""},
	} {
		t.Run(desc, c.Assert)
	}
}

func TestFormatBytesUnit(t *testing.T) {
	for desc, c := range map[string]*FormatBytesUnitTestCase{
		"format bytes":     {uint64(1 * math.Pow(10, 0)), humanize.Byte, "1"},
		"format kilobytes": {uint64(1 * math.Pow(10, 3)), humanize.Byte, "1000"},
		"format megabytes": {uint64(1 * math.Pow(10, 6)), humanize.Byte, "1000000"},
		"format gigabytes": {uint64(1 * math.Pow(10, 9)), humanize.Byte, "1000000000"},
		"format petabytes": {uint64(1 * math.Pow(10, 12)), humanize.Byte, "1000000000000"},
		"format terabytes": {uint64(1 * math.Pow(10, 15)), humanize.Byte, "1000000000000000"},

		"format kilobytes under": {uint64(1.49 * math.Pow(10, 3)), humanize.Byte, "1490"},
		"format megabytes under": {uint64(1.49 * math.Pow(10, 6)), humanize.Byte, "1490000"},
		"format gigabytes under": {uint64(1.49 * math.Pow(10, 9)), humanize.Byte, "1490000000"},
		"format petabytes under": {uint64(1.49 * math.Pow(10, 12)), humanize.Byte, "1490000000000"},
		"format terabytes under": {uint64(1.49 * math.Pow(10, 15)), humanize.Byte, "1490000000000000"},

		"format kilobytes over": {uint64(1.51 * math.Pow(10, 3)), humanize.Byte, "1510"},
		"format megabytes over": {uint64(1.51 * math.Pow(10, 6)), humanize.Byte, "1510000"},
		"format gigabytes over": {uint64(1.51 * math.Pow(10, 9)), humanize.Byte, "1510000000"},
		"format petabytes over": {uint64(1.51 * math.Pow(10, 12)), humanize.Byte, "1510000000000"},
		"format terabytes over": {uint64(1.51 * math.Pow(10, 15)), humanize.Byte, "1510000000000000"},
	} {
		t.Run(desc, c.Assert)
	}
}

func TestFormateByteRate(t *testing.T) {
	for desc, c := range map[string]*FormatByteRateTestCase{
		"format bytes":     {uint64(1 * math.Pow(10, 0)), time.Second, "1 B/s"},
		"format kilobytes": {uint64(1 * math.Pow(10, 3)), time.Second, "1.0 KB/s"},
		"format megabytes": {uint64(1 * math.Pow(10, 6)), time.Second, "1.0 MB/s"},
		"format gigabytes": {uint64(1 * math.Pow(10, 9)), time.Second, "1.0 GB/s"},
		"format petabytes": {uint64(1 * math.Pow(10, 12)), time.Second, "1.0 TB/s"},
		"format terabytes": {uint64(1 * math.Pow(10, 15)), time.Second, "1.0 PB/s"},

		"format kilobytes under": {uint64(1.49 * math.Pow(10, 3)), time.Second, "1.5 KB/s"},
		"format megabytes under": {uint64(1.49 * math.Pow(10, 6)), time.Second, "1.5 MB/s"},
		"format gigabytes under": {uint64(1.49 * math.Pow(10, 9)), time.Second, "1.5 GB/s"},
		"format petabytes under": {uint64(1.49 * math.Pow(10, 12)), time.Second, "1.5 TB/s"},
		"format terabytes under": {uint64(1.49 * math.Pow(10, 15)), time.Second, "1.5 PB/s"},

		"format kilobytes over": {uint64(1.51 * math.Pow(10, 3)), time.Second, "1.5 KB/s"},
		"format megabytes over": {uint64(1.51 * math.Pow(10, 6)), time.Second, "1.5 MB/s"},
		"format gigabytes over": {uint64(1.51 * math.Pow(10, 9)), time.Second, "1.5 GB/s"},
		"format petabytes over": {uint64(1.51 * math.Pow(10, 12)), time.Second, "1.5 TB/s"},
		"format terabytes over": {uint64(1.51 * math.Pow(10, 15)), time.Second, "1.5 PB/s"},

		"format kilobytes exact": {uint64(1.3 * math.Pow(10, 3)), time.Second, "1.3 KB/s"},
		"format megabytes exact": {uint64(1.3 * math.Pow(10, 6)), time.Second, "1.3 MB/s"},
		"format gigabytes exact": {uint64(1.3 * math.Pow(10, 9)), time.Second, "1.3 GB/s"},
		"format petabytes exact": {uint64(1.3 * math.Pow(10, 12)), time.Second, "1.3 TB/s"},
		"format terabytes exact": {uint64(1.3 * math.Pow(10, 15)), time.Second, "1.3 PB/s"},

		"format bytes (non-second)":  {uint64(10 * math.Pow(10, 0)), 2 * time.Second, "5 B/s"},
		"format bytes (zero-second)": {uint64(10 * math.Pow(10, 0)), 0, "10 GB/s"},
	} {
		t.Run(desc, c.Assert)
	}
}
