package values

import (
	"fmt"
	"github.com/dolanor/caldav-go/utils"
	"log"
	"math"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var _ = log.Print

// a representation of duration for iCalendar
type Duration struct {
	d time.Duration
}

// breaks apart the duration into its component time parts
func (d *Duration) Decompose() (weeks, days, hours, minutes, seconds int64) {

	// chip away at this
	rem := time.Duration(math.Abs(float64(d.d)))

	div := time.Hour * 24 * 7
	weeks = int64(rem / div)
	rem = rem % div
	div = div / 7
	days = int64(rem / div)
	rem = rem % div
	div = div / 24
	hours = int64(rem / div)
	rem = rem % div
	div = div / 60
	minutes = int64(rem / div)
	rem = rem % div
	div = div / 60
	seconds = int64(rem / div)

	return

}

// returns the native golang duration
func (d *Duration) NativeDuration() time.Duration {
	return d.d
}

// returns true if the duration is negative
func (d *Duration) IsPast() bool {
	return d.d < 0
}

// encodes the duration of time into iCalendar format
func (d *Duration) EncodeICalValue() (string, error) {
	var parts []string
	weeks, days, hours, minutes, seconds := d.Decompose()
	if d.IsPast() {
		parts = append(parts, "-")
	}
	parts = append(parts, "P")
	if weeks > 0 {
		parts = append(parts, fmt.Sprintf("%dW", weeks))
	}
	if days > 0 {
		parts = append(parts, fmt.Sprintf("%dD", days))
	}
	if hours > 0 || minutes > 0 || seconds > 0 {
		parts = append(parts, "T")
		if hours > 0 {
			parts = append(parts, fmt.Sprintf("%dH", hours))
		}
		if minutes > 0 {
			parts = append(parts, fmt.Sprintf("%dM", minutes))
		}
		if seconds > 0 {
			parts = append(parts, fmt.Sprintf("%dS", seconds))
		}
	}
	return strings.Join(parts, ""), nil
}

var durationRegEx = regexp.MustCompile("(\\d+)(\\w)")

// decodes the duration of time from iCalendar format
func (d *Duration) DecodeICalValue(value string) error {
	var seconds int64
	var isPast = strings.HasPrefix(value, "-P")
	var matches = durationRegEx.FindAllStringSubmatch(value, -1)
	for _, match := range matches {
		var multiplier int64
		ivalue, err := strconv.ParseInt(match[1], 10, 64)
		if err != nil {
			return utils.NewError(d.DecodeICalValue, "unable to decode duration value "+match[1], d, nil)
		}
		switch match[2] {
		case "S":
			multiplier = 1
		case "M":
			multiplier = 60
		case "H":
			multiplier = 60 * 60
		case "D":
			multiplier = 60 * 60 * 24
		case "W":
			multiplier = 60 * 60 * 24 * 7
		default:
			return utils.NewError(d.DecodeICalValue, "unable to decode duration segment "+match[2], d, nil)
		}
		seconds = seconds + multiplier*ivalue
	}
	d.d = time.Duration(seconds) * time.Second
	if isPast {
		d.d = -d.d
	}
	return nil
}

func (d *Duration) String() string {
	if s, err := d.EncodeICalValue(); err != nil {
		panic(err)
	} else {
		return s
	}
}

// creates a new iCalendar duration representation
func NewDuration(d time.Duration) *Duration {
	return &Duration{d: d}
}
