// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package iso

import (
	"bytes"
	"encoding/base64"
	"strings"
	"time"

	"github.com/relvacode/iso8601"
	"github.com/sosodev/duration"
)

// Wrappers for the native Go time types that will serialize to ISO 8601.
type (
	// Date is a date in ISO 8601 format, per RFC 3339.
	Date time.Time

	// DateTime is a date and time in ISO 8601 format, per RFC 3339.
	DateTime time.Time

	// Duration is a duration in ISO 8601 format.
	Duration time.Duration

	// Time is a time in ISO 8601 format, per RFC 3339.
	Time time.Time

	// ByteSlice is a wrapper for the native Go byte slice that will serialize
	// to Base64.
	ByteSlice []byte
)

// full-date (0) and full-time (1) schemas, as defined by RFC3339 section 5.6.
var (
	format = strings.Split(time.RFC3339, "T")
	zeroes = strings.Split(time.Time{}.Format(time.RFC3339), "T")
)

// String returns the date as an ISO 8601 string.
func (d Date) String() string {
	return time.Time(d).Format(format[0])
}

// MarshalText marshals the date to an ISO 8601 string.
func (d Date) MarshalText() ([]byte, error) {
	return []byte(d.String()), nil
}

// UnmarshalText unmarshals the date from an ISO 8601 string.
func (d *Date) UnmarshalText(b []byte) error {
	if !bytes.ContainsAny(b, "tT") {
		b = []byte(string(b) + "T" + zeroes[1])
	}
	parsed, err := iso8601.Parse(b)
	if err != nil {
		return err
	}
	*d = Date(parsed)
	return nil
}

// String returns the date-time as an ISO 8601 string.
func (dt DateTime) String() string {
	return time.Time(dt).Format(time.RFC3339)
}

// MarshalText marshals the date-time to an ISO 8601 string.
func (dt DateTime) MarshalText() ([]byte, error) {
	return []byte(dt.String()), nil
}

// UnmarshalText unmarshals the date-time from an ISO 8601 string.
func (dt *DateTime) UnmarshalText(b []byte) error {
	parsed, err := iso8601.Parse(b)
	if err != nil {
		return err
	}
	*dt = DateTime(parsed)
	return nil
}

// String returns the duration as an ISO 8601 string.
func (d Duration) String() string {
	return duration.Format(time.Duration(d))
}

// MarshalText marshals the duration to an ISO 8601 string.
func (d Duration) MarshalText() ([]byte, error) {
	return []byte(d.String()), nil
}

// UnmarshalText unmarshals the duration from an ISO 8601 string.
func (d *Duration) UnmarshalText(b []byte) error {
	parsed, err := duration.Parse(string(b))
	if err != nil {
		return err
	}
	*d = Duration(parsed.ToTimeDuration())
	return nil
}

// String returns the time as an ISO 8601 string.
func (t Time) String() string {
	return time.Time(t).Format(format[1])
}

// MarshalText marshals the time to an ISO 8601 string.
func (t Time) MarshalText() ([]byte, error) {
	return []byte(t.String()), nil
}

// UnmarshalText unmarshals the time from an ISO 8601 string.
func (t *Time) UnmarshalText(b []byte) error {
	if !bytes.ContainsAny(b, "tT") {
		b = []byte(zeroes[0] + "T" + string(b))
	}
	parsed, err := iso8601.Parse(b)
	if err != nil {
		return err
	}
	*t = Time(parsed)
	return nil
}

// String returns the byte slice as a Base64 string.
func (bs ByteSlice) String() string {
	return base64.StdEncoding.EncodeToString(bs)
}

// MarshalText marshals the byte slice to a Base64 string.
func (bs ByteSlice) MarshalText() ([]byte, error) {
	return []byte(bs.String()), nil
}

// UnmarshalText unmarshals the byte slice from a Base64 string.
func (bs *ByteSlice) UnmarshalText(b []byte) error {
	var err error
	*bs, err = base64.StdEncoding.DecodeString(string(b))
	return err
}
