package rfctime

import (
	"encoding/json"
	"errors"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsoncodec"
	"go.mongodb.org/mongo-driver/bson/bsonrw"
	"go.mongodb.org/mongo-driver/bson/bsontype"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"reflect"
	"strconv"
	"time"
)

type UnixTime time.Time

func (t UnixTime) Time() time.Time {
	return time.Time(t)
}

func (t UnixTime) MarshalBinary() ([]byte, error) {
	return (time.Time)(t).MarshalBinary()
}

func (t *UnixTime) UnmarshalBinary(data []byte) error {
	return (*time.Time)(t).UnmarshalBinary(data)
}

func (t UnixTime) GobEncode() ([]byte, error) {
	return (time.Time)(t).GobEncode()
}

func (t *UnixTime) GobDecode(data []byte) error {
	return (*time.Time)(t).GobDecode(data)
}

func (t *UnixTime) UnmarshalJSON(data []byte) error {
	str := ""
	if err := json.Unmarshal(data, &str); err != nil {
		return err
	}
	t0, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		return err
	}
	*t = UnixTime(time.Unix(t0, 0))
	return nil
}

func (t UnixTime) MarshalJSON() ([]byte, error) {
	str := strconv.FormatInt(t.Time().Unix(), 10)
	return json.Marshal(str)
}

func (t UnixTime) MarshalText() ([]byte, error) {
	return []byte(strconv.FormatInt(t.Time().Unix(), 10)), nil
}

func (t *UnixTime) UnmarshalText(data []byte) error {
	t0, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return err
	}
	*t = UnixTime(time.Unix(t0, 0))
	return nil
}

func (t *UnixTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
	if bt == bson.TypeNull {
		// we can't set nil in UnmarshalBSONValue (so we use default(struct))
		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
		// https://stackoverflow.com/questions/75167597
		// https://jira.mongodb.org/browse/GODRIVER-2252
		*t = UnixTime{}
		return nil
	}
	if bt != bson.TypeDateTime {
		return errors.New(fmt.Sprintf("cannot unmarshal %v into UnixTime", bt))
	}
	var tt time.Time
	err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)
	if err != nil {
		return err
	}
	*t = UnixTime(tt)
	return nil
}

func (t UnixTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
	return bson.MarshalValue(time.Time(t))
}

func (t UnixTime) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
	if val.Kind() == reflect.Ptr && val.IsNil() {
		if !val.CanSet() {
			return errors.New("ValueUnmarshalerDecodeValue")
		}
		val.Set(reflect.New(val.Type().Elem()))
	}

	tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
	if err != nil {
		return err
	}

	if val.Kind() == reflect.Ptr && len(src) == 0 {
		val.Set(reflect.Zero(val.Type()))
		return nil
	}

	err = t.UnmarshalBSONValue(tp, src)
	if err != nil {
		return err
	}

	if val.Kind() == reflect.Ptr {
		val.Set(reflect.ValueOf(&t))
	} else {
		val.Set(reflect.ValueOf(t))
	}

	return nil
}

func (t UnixTime) Serialize() string {
	return strconv.FormatInt(t.Time().Unix(), 10)
}

func (t UnixTime) After(u AnyTime) bool {
	return t.Time().After(tt(u))
}

func (t UnixTime) Before(u AnyTime) bool {
	return t.Time().Before(tt(u))
}

func (t UnixTime) Equal(u AnyTime) bool {
	return t.Time().Equal(tt(u))
}

func (t UnixTime) IsZero() bool {
	return t.Time().IsZero()
}

func (t UnixTime) Date() (year int, month time.Month, day int) {
	return t.Time().Date()
}

func (t UnixTime) Year() int {
	return t.Time().Year()
}

func (t UnixTime) Month() time.Month {
	return t.Time().Month()
}

func (t UnixTime) Day() int {
	return t.Time().Day()
}

func (t UnixTime) Weekday() time.Weekday {
	return t.Time().Weekday()
}

func (t UnixTime) ISOWeek() (year, week int) {
	return t.Time().ISOWeek()
}

func (t UnixTime) Clock() (hour, min, sec int) {
	return t.Time().Clock()
}

func (t UnixTime) Hour() int {
	return t.Time().Hour()
}

func (t UnixTime) Minute() int {
	return t.Time().Minute()
}

func (t UnixTime) Second() int {
	return t.Time().Second()
}

func (t UnixTime) Nanosecond() int {
	return t.Time().Nanosecond()
}

func (t UnixTime) YearDay() int {
	return t.Time().YearDay()
}

func (t UnixTime) Add(d time.Duration) UnixTime {
	return UnixTime(t.Time().Add(d))
}

func (t UnixTime) Sub(u AnyTime) time.Duration {
	return t.Time().Sub(tt(u))
}

func (t UnixTime) AddDate(years int, months int, days int) UnixTime {
	return UnixTime(t.Time().AddDate(years, months, days))
}

func (t UnixTime) Unix() int64 {
	return t.Time().Unix()
}

func (t UnixTime) UnixMilli() int64 {
	return t.Time().UnixMilli()
}

func (t UnixTime) UnixMicro() int64 {
	return t.Time().UnixMicro()
}

func (t UnixTime) UnixNano() int64 {
	return t.Time().UnixNano()
}

func (t UnixTime) Format(layout string) string {
	return t.Time().Format(layout)
}

func (t UnixTime) GoString() string {
	return t.Time().GoString()
}

func (t UnixTime) String() string {
	return t.Time().String()
}

func (t UnixTime) Location() *time.Location {
	return t.Time().Location()
}

func NewUnix(t time.Time) UnixTime {
	return UnixTime(t)
}

func NewUnixPtr(t *time.Time) *UnixTime {
	if t == nil {
		return nil
	}
	return langext.Ptr(UnixTime(*t))
}

func NowUnix() UnixTime {
	return UnixTime(time.Now())
}