package ical

import (
	"regexp"
	"strconv"
	"time"
)

type Node struct {
	Name       string
	Value      string
	Type       int // 1 = Object, 0 = Name/Value
	Parameters map[string]string
	Children   []*Node
}

func (this *Node) ChildrenByName(name string) []*Node {
	var output []*Node
	for _, child := range this.Children {
		if child.Name == name {
			output = append(output, child)
		}
	}
	return output
}

func (this *Node) ChildByName(name string) *Node {
	for _, child := range this.Children {
		if child.Name == name {
			return child
		}
	}
	return nil
}

func (this *Node) PropString(name string, defaultValue string) string {
	for _, child := range this.Children {
		if child.Name == name {
			return child.Value
		}
	}
	return defaultValue
}

func (this *Node) PropDate(name string, defaultValue time.Time) time.Time {
	node := this.ChildByName(name)
	if node == nil {
		return defaultValue
	}
	tzid := node.Parameter("TZID", "")
	allDay := node.Parameter("VALUE", "") == "DATE"
	var output time.Time
	var err error
	if tzid != "" {
		loc, err := time.LoadLocation(tzid)
		if err != nil {
			panic(err)
		}
		output, err = time.ParseInLocation("20060102T150405", node.Value, loc)
	} else if allDay {
		output, err = time.Parse("20060102", node.Value)
	} else {
		output, err = time.Parse("20060102T150405Z", node.Value)
	}

	if err != nil {
		panic(err)
	}
	return output
}

func (this *Node) PropDuration(name string) time.Duration {
	durStr := this.PropString(name, "")

	if durStr == "" {
		return time.Duration(0)
	}

	durRgx := regexp.MustCompile("PT(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)S)?")
	matches := durRgx.FindStringSubmatch(durStr)

	if len(matches) != 4 {
		return time.Duration(0)
	}

	strToDuration := func(value string) time.Duration {
		d := 0
		if value != "" {
			d, _ = strconv.Atoi(value)
		}
		return time.Duration(d)
	}

	hours := strToDuration(matches[1])
	min := strToDuration(matches[2])
	sec := strToDuration(matches[3])

	return hours*time.Hour + min*time.Minute + sec*time.Second
}

func (this *Node) PropInt(name string, defaultValue int) int {
	n := this.PropString(name, "")
	if n == "" {
		return defaultValue
	}
	output, err := strconv.Atoi(n)
	if err != nil {
		panic(err)
	}
	return output
}

func (this *Node) DigProperty(propPath ...string) (string, bool) {
	return this.dig("prop", propPath...)
}

func (this *Node) Parameter(name string, defaultValue string) string {
	if len(this.Parameters) <= 0 {
		return defaultValue
	}
	v, ok := this.Parameters[name]
	if !ok {
		return defaultValue
	}
	return v
}

func (this *Node) DigParameter(paramPath ...string) (string, bool) {
	return this.dig("param", paramPath...)
}

// Digs a value based on a given value path.
// valueType: can be "param" or "prop".
// valuePath: the path to access the value.
// Returns ("", false) when not found or (value, true) when found.
//
// Example:
// dig("param", "VCALENDAR", "VEVENT", "DTEND", "TYPE") -> It will search for "VCALENDAR" node,
// then a "VEVENT" node, then a "DTEND" note, then finally the "TYPE" param.
func (this *Node) dig(valueType string, valuePath ...string) (string, bool) {
	current := this
	lastIndex := len(valuePath) - 1
	for _, v := range valuePath[:lastIndex] {
		current = current.ChildByName(v)

		if current == nil {
			return "", false
		}
	}

	target := valuePath[lastIndex]

	value := ""
	if valueType == "param" {
		value = current.Parameter(target, "")
	} else if valueType == "prop" {
		value = current.PropString(target, "")
	}

	if value == "" {
		return "", false
	}

	return value, true
}

func (this *Node) String() string {
	s := ""
	if this.Type == 1 {
		s += "===== " + this.Name
		s += "\n"
	} else {
		s += this.Name
		s += ":" + this.Value
		s += "\n"
	}
	for _, child := range this.Children {
		s += child.String()
	}
	if this.Type == 1 {
		s += "===== /" + this.Name
		s += "\n"
	}
	return s
}
