package headers

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

	"github.com/bluenviron/gortsplib/v5/pkg/base"
)

func leadingZero(v uint) string {
	ret := ""
	if v < 10 {
		ret += "0"
	}
	ret += strconv.FormatUint(uint64(v), 10)
	return ret
}

// RangeSMPTETime is a time expressed in SMPTE unit.
type RangeSMPTETime struct {
	Time     time.Duration
	Frame    uint
	Subframe uint
}

func (t *RangeSMPTETime) unmarshal(s string) error {
	parts := strings.Split(s, ":")
	if len(parts) != 3 && len(parts) != 4 {
		return fmt.Errorf("invalid SMPTE time (%v)", s)
	}

	tmp, err := strconv.ParseUint(parts[0], 10, 64)
	if err != nil {
		return err
	}
	hours := tmp

	tmp, err = strconv.ParseUint(parts[1], 10, 64)
	if err != nil {
		return err
	}
	mins := tmp

	tmp, err = strconv.ParseUint(parts[2], 10, 64)
	if err != nil {
		return err
	}
	seconds := tmp

	t.Time = time.Duration(seconds+mins*60+hours*3600) * time.Second

	if len(parts) == 4 {
		parts = strings.Split(parts[3], ".")
		if len(parts) == 2 {
			tmp, err = strconv.ParseUint(parts[0], 10, 64)
			if err != nil {
				return err
			}
			t.Frame = uint(tmp)

			tmp, err = strconv.ParseUint(parts[1], 10, 64)
			if err != nil {
				return err
			}
			t.Subframe = uint(tmp)
		} else {
			tmp, err = strconv.ParseUint(parts[0], 10, 64)
			if err != nil {
				return err
			}
			t.Frame = uint(tmp)
		}
	}

	return nil
}

func (t RangeSMPTETime) marshal() string {
	d := uint64(t.Time.Seconds())
	hours := d / 3600
	d %= 3600
	mins := d / 60
	secs := d % 60

	ret := strconv.FormatUint(hours, 10) + ":" + leadingZero(uint(mins)) + ":" + leadingZero(uint(secs))

	if t.Frame > 0 || t.Subframe > 0 {
		ret += ":" + leadingZero(t.Frame)

		if t.Subframe > 0 {
			ret += "." + leadingZero(t.Subframe)
		}
	}

	return ret
}

// RangeSMPTE is a range expressed in SMPTE unit.
type RangeSMPTE struct {
	Start RangeSMPTETime
	End   *RangeSMPTETime
}

func (r *RangeSMPTE) unmarshal(start string, end string) error {
	err := r.Start.unmarshal(start)
	if err != nil {
		return err
	}

	if end != "" {
		var v RangeSMPTETime
		err = v.unmarshal(end)
		if err != nil {
			return err
		}
		r.End = &v
	}

	return nil
}

func (r RangeSMPTE) marshal() string {
	ret := "smpte=" + r.Start.marshal() + "-"
	if r.End != nil {
		ret += r.End.marshal()
	}
	return ret
}

func unmarshalRangeNPTTime(d *time.Duration, s string) error {
	parts := strings.Split(s, ":")
	if len(parts) > 3 {
		return fmt.Errorf("invalid NPT time (%v)", s)
	}

	var hours uint64
	if len(parts) == 3 {
		tmp, err := strconv.ParseUint(parts[0], 10, 64)
		if err != nil {
			return err
		}
		hours = tmp
		parts = parts[1:]
	}

	var mins uint64
	if len(parts) >= 2 {
		tmp, err := strconv.ParseUint(parts[0], 10, 64)
		if err != nil {
			return err
		}
		mins = tmp
		parts = parts[1:]
	}

	tmp, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return err
	}
	seconds := tmp

	*d = time.Duration(seconds*float64(time.Second)) +
		time.Duration(mins*60+hours*3600)*time.Second

	return nil
}

func marshalRangeNPTTime(d time.Duration) string {
	return strconv.FormatFloat(d.Seconds(), 'f', -1, 64)
}

// RangeNPT is a range expressed in NPT units.
type RangeNPT struct {
	Start time.Duration
	End   *time.Duration
}

func (r *RangeNPT) unmarshal(start string, end string) error {
	err := unmarshalRangeNPTTime(&r.Start, start)
	if err != nil {
		return err
	}

	if end != "" {
		var v time.Duration
		err = unmarshalRangeNPTTime(&v, end)
		if err != nil {
			return err
		}
		r.End = &v
	}

	return nil
}

func (r RangeNPT) marshal() string {
	ret := "npt=" + marshalRangeNPTTime(r.Start) + "-"
	if r.End != nil {
		ret += marshalRangeNPTTime(*r.End)
	}
	return ret
}

func unmarshalRangeUTCTime(t *time.Time, s string) error {
	tmp, err := time.Parse("20060102T150405Z", s)
	if err != nil {
		return err
	}
	*t = tmp
	return nil
}

func marshalRangeUTCTime(t time.Time) string {
	return t.Format("20060102T150405Z")
}

// RangeUTC is a range expressed in UTC units.
type RangeUTC struct {
	Start time.Time
	End   *time.Time
}

func (r *RangeUTC) unmarshal(start string, end string) error {
	err := unmarshalRangeUTCTime(&r.Start, start)
	if err != nil {
		return err
	}

	if end != "" {
		var v time.Time
		err = unmarshalRangeUTCTime(&v, end)
		if err != nil {
			return err
		}
		r.End = &v
	}

	return nil
}

func (r RangeUTC) marshal() string {
	ret := "clock=" + marshalRangeUTCTime(r.Start) + "-"
	if r.End != nil {
		ret += marshalRangeUTCTime(*r.End)
	}
	return ret
}

// RangeValue can be
// - RangeSMPTE
// - RangeNPT
// - RangeUTC
type RangeValue interface {
	unmarshal(string, string) error
	marshal() string
}

func rangeValueUnmarshal(s RangeValue, v string) error {
	parts := strings.Split(v, "-")
	if len(parts) != 2 {
		return fmt.Errorf("invalid value (%v)", v)
	}

	return s.unmarshal(parts[0], parts[1])
}

// Range is a Range header.
type Range struct {
	// range expressed in some measurement units.
	Value RangeValue

	// time at which the operation is to be made effective.
	Time *time.Time
}

// Unmarshal decodes a Range header.
func (h *Range) Unmarshal(v base.HeaderValue) error {
	if len(v) == 0 {
		return fmt.Errorf("value not provided")
	}

	if len(v) > 1 {
		return fmt.Errorf("value provided multiple times (%v)", v)
	}

	kvs, err := keyValParse(v[0], ';')
	if err != nil {
		return err
	}

	specFound := false

	for k, v := range kvs {
		switch k {
		case "smpte":
			s := &RangeSMPTE{}
			err = rangeValueUnmarshal(s, v)
			if err != nil {
				return err
			}

			specFound = true
			h.Value = s

		case "npt":
			s := &RangeNPT{}
			err = rangeValueUnmarshal(s, v)
			if err != nil {
				return err
			}

			specFound = true
			h.Value = s

		case "clock":
			s := &RangeUTC{}
			err = rangeValueUnmarshal(s, v)
			if err != nil {
				return err
			}

			specFound = true
			h.Value = s

		case "time":
			var t time.Time
			err = unmarshalRangeUTCTime(&t, v)
			if err != nil {
				return err
			}

			h.Time = &t
		}
	}

	if !specFound {
		return fmt.Errorf("value not found (%v)", v[0])
	}

	return nil
}

// Marshal encodes a Range header.
func (h Range) Marshal() base.HeaderValue {
	v := h.Value.marshal()
	if h.Time != nil {
		v += ";time=" + marshalRangeUTCTime(*h.Time)
	}
	return base.HeaderValue{v}
}
