package properties

import (
	"fmt"
	"github.com/dolanor/caldav-go/utils"
	"log"
	"reflect"
	"strings"
)

var _ = log.Print

var propNameSanitizer = strings.NewReplacer(
	"_", "-",
	":", "\\:",
)

var propValueSanitizer = strings.NewReplacer(
	"\"", "'",
	"\\", "\\\\",
	"\n", "\\n",
)

var propNameDesanitizer = strings.NewReplacer(
	"-", "_",
	"\\:", ":",
)

var propValueDesanitizer = strings.NewReplacer(
	"'", "\"",
	"\\\\", "\\",
	"\\n", "\n",
)

type Property struct {
	Name                PropertyName
	Value, DefaultValue string
	Params              Params
	OmitEmpty, Required bool
}

func (p *Property) HasNameAndValue() bool {
	return p.Name != "" && p.Value != ""
}

func (p *Property) Merge(override *Property) {
	if override.Name != "" {
		p.Name = override.Name
	}
	if override.Value != "" {
		p.Value = override.Value
	}
	if override.Params != nil {
		p.Params = override.Params
	}
}

func PropertyFromStructField(fs reflect.StructField) (p *Property) {

	ftag := fs.Tag.Get("ical")
	if fs.PkgPath != "" || ftag == "-" {
		return
	}

	p = new(Property)

	// parse the field tag
	if ftag != "" {
		tags := strings.Split(ftag, ",")
		p.Name = PropertyName(tags[0])
		if len(tags) > 1 {
			if tags[1] == "omitempty" {
				p.OmitEmpty = true
			} else if tags[1] == "required" {
				p.Required = true
			} else {
				p.DefaultValue = tags[1]
			}
		}
	}

	// make sure we have a name
	if p.Name == "" {
		p.Name = PropertyName(fs.Name)
	}

	p.Name = PropertyName(strings.ToUpper(string(p.Name)))

	return

}

func MarshalProperty(p *Property) string {
	name := strings.ToUpper(propNameSanitizer.Replace(string(p.Name)))
	value := propValueSanitizer.Replace(p.Value)
	keys := []string{name}
	for name, value := range p.Params {
		name = ParameterName(strings.ToUpper(propNameSanitizer.Replace(string(name))))
		value = propValueSanitizer.Replace(value)
		if strings.ContainsAny(value, " :") {
			keys = append(keys, fmt.Sprintf("%s=\"%s\"", name, value))
		} else {
			keys = append(keys, fmt.Sprintf("%s=%s", name, value))
		}
	}
	name = strings.Join(keys, ";")
	return fmt.Sprintf("%s:%s", name, value)
}

func PropertyFromInterface(target interface{}) (p *Property, err error) {

	var ierr error
	if va, ok := target.(CanValidateValue); ok {
		if ierr = va.ValidateICalValue(); ierr != nil {
			err = utils.NewError(PropertyFromInterface, "interface failed validation", target, ierr)
			return
		}
	}

	p = new(Property)

	if enc, ok := target.(CanEncodeName); ok {
		if p.Name, ierr = enc.EncodeICalName(); ierr != nil {
			err = utils.NewError(PropertyFromInterface, "interface failed name encoding", target, ierr)
			return
		}
	}

	if enc, ok := target.(CanEncodeParams); ok {
		if p.Params, ierr = enc.EncodeICalParams(); ierr != nil {
			err = utils.NewError(PropertyFromInterface, "interface failed params encoding", target, ierr)
			return
		}
	}

	if enc, ok := target.(CanEncodeValue); ok {
		if p.Value, ierr = enc.EncodeICalValue(); ierr != nil {
			err = utils.NewError(PropertyFromInterface, "interface failed value encoding", target, ierr)
			return
		}
	}

	return

}

func UnmarshalProperty(line string) *Property {
	nvp := strings.SplitN(line, ":", 2)
	prop := new(Property)
	if len(nvp) > 1 {
		prop.Value = strings.TrimSpace(nvp[1])
	}
	npp := strings.Split(nvp[0], ";")
	if len(npp) > 1 {
		prop.Params = make(map[ParameterName]string, 0)
		for i := 1; i < len(npp); i++ {
			var key, value string
			kvp := strings.Split(npp[i], "=")
			key = strings.TrimSpace(kvp[0])
			key = propNameDesanitizer.Replace(key)
			if len(kvp) > 1 {
				value = strings.TrimSpace(kvp[1])
				value = propValueDesanitizer.Replace(value)
				value = strings.Trim(value, "\"")
			}
			prop.Params[ParameterName(key)] = value
		}
	}
	prop.Name = PropertyName(strings.TrimSpace(npp[0]))
	prop.Name = PropertyName(propNameDesanitizer.Replace(string(prop.Name)))
	prop.Value = propValueDesanitizer.Replace(prop.Value)
	return prop
}

func NewProperty(name, value string) *Property {
	return &Property{Name: PropertyName(name), Value: value}
}
