package units

import (
	"fmt"
	"math"
)

type durationFormatter struct {
}

func NewDurationFormatter() Formatter {
	return &durationFormatter{}
}

func (*durationFormatter) Name() string {
	return "duration"
}

func (f *durationFormatter) Format(value float64, unit string) string {
	switch unit {
	case "ns":
		return toNanoSeconds(value)
	case "µs", "us":
		return toMicroSeconds(value)
	case "ms":
		return toMilliSeconds(value)
	case "s":
		return toSeconds(value)
	case "m", "min":
		return toMinutes(value)
	case "h":
		return toHours(value)
	case "d":
		return toDays(value)
	case "w", "wk":
		return toWeeks(value)
	}
	// When unit is not matched, return the value as it is.
	return fmt.Sprintf("%v", value)
}

// toNanoSeconds returns a easy to read string representation of the given value in nanoseconds.
func toNanoSeconds(value float64) string {
	absValue := math.Abs(value)

	if absValue < 1000 {
		return toFixed(value, nil) + " ns"
	} else if absValue < 1000000 { // 2000 ns is better represented as 2 µs
		return toFixedScaled(value/1000, " µs")
	} else if absValue < 1000000000 { // 2000000 ns is better represented as 2 ms
		return toFixedScaled(value/1000000, " ms")
	} else if absValue < 60000000000 {
		return toFixedScaled(value/1000000000, " s")
	} else if absValue < 3600000000000 {
		return toFixedScaled(value/60000000000, " min")
	} else if absValue < 86400000000000 {
		return toFixedScaled(value/3600000000000, " hour")
	} else {
		return toFixedScaled(value/86400000000000, " day")
	}
}

// toMicroSeconds returns a easy to read string representation of the given value in microseconds.
func toMicroSeconds(value float64) string {
	absValue := math.Abs(value)
	if absValue < 1000 {
		return toFixed(value, nil) + " µs"
	} else if absValue < 1000000 { // 2000 µs is better represented as 2 ms
		return toFixedScaled(value/1000, " ms")
	} else {
		return toFixedScaled(value/1000000, " s")
	}
}

// toMilliSeconds returns a easy to read string representation of the given value in milliseconds.
func toMilliSeconds(value float64) string {

	absValue := math.Abs(value)

	if absValue < 1000 {
		return toFixed(value, nil) + " ms"
	} else if absValue < 60000 {
		return toFixedScaled(value/1000, " s")
	} else if absValue < 3600000 {
		return toFixedScaled(value/60000, " min")
	} else if absValue < 86400000 { // 172800000 ms is better represented as 2 day
		return toFixedScaled(value/3600000, " hour")
	} else if absValue < 31536000000 {
		return toFixedScaled(value/86400000, " day")
	}

	return toFixedScaled(value/31536000000, " year")
}

// toSeconds returns a easy to read string representation of the given value in seconds.
func toSeconds(value float64) string {
	absValue := math.Abs(value)

	if absValue < 0.000001 {
		return toFixedScaled(value*1e9, " ns")
	} else if absValue < 0.001 {
		return toFixedScaled(value*1e6, " µs")
	} else if absValue < 1 {
		return toFixedScaled(value*1e3, " ms")
	} else if absValue < 60 {
		return toFixed(value, nil) + " s"
	} else if absValue < 3600 {
		return toFixedScaled(value/60, " min")
	} else if absValue < 86400 { // 56000 s is better represented as 15.56 hour
		return toFixedScaled(value/3600, " hour")
	} else if absValue < 604800 {
		return toFixedScaled(value/86400, " day")
	} else if absValue < 31536000 {
		return toFixedScaled(value/604800, " week")
	}

	return toFixedScaled(value/3.15569e7, " year")
}

// toMinutes returns a easy to read string representation of the given value in minutes.
func toMinutes(value float64) string {
	absValue := math.Abs(value)

	if absValue < 60 {
		return toFixed(value, nil) + " min"
	} else if absValue < 1440 {
		return toFixedScaled(value/60, " hour")
	} else if absValue < 10080 {
		return toFixedScaled(value/1440, " day")
	} else if absValue < 604800 {
		return toFixedScaled(value/10080, " week")
	} else {
		return toFixedScaled(value/5.25948e5, " year")
	}
}

// toHours returns a easy to read string representation of the given value in hours.
func toHours(value float64) string {

	absValue := math.Abs(value)

	if absValue < 24 {
		return toFixed(value, nil) + " hour"
	} else if absValue < 168 {
		return toFixedScaled(value/24, " day")
	} else if absValue < 8760 {
		return toFixedScaled(value/168, " week")
	} else {
		return toFixedScaled(value/8760, " year")
	}
}

// toDays returns a easy to read string representation of the given value in days.
func toDays(value float64) string {
	absValue := math.Abs(value)

	if absValue < 7 {
		return toFixed(value, nil) + " day"
	} else if absValue < 365 {
		return toFixedScaled(value/7, " week")
	} else {
		return toFixedScaled(value/365, " year")
	}
}

// toWeeks returns a easy to read string representation of the given value in weeks.
func toWeeks(value float64) string {
	absValue := math.Abs(value)

	if absValue < 52 {
		return toFixed(value, nil) + " week"
	} else {
		return toFixedScaled(value/52, " year")
	}
}
