package upnp

import (
	"crypto/rand"
	"encoding/xml"
	"fmt"
	"io"
	"net/url"
	"regexp"
	"sync"
	"time"

	"github.com/anacrolix/log"
)

// TODO: Why use namespace prefixes in PropertySet et al? Because the spec
// uses them, and I believe the Golang standard library XML spec implementers
// incorrectly assume that you can get away with just xmlns="".

// propertyset is the root element sent in an event callback.
type PropertySet struct {
	XMLName    struct{} `xml:"e:propertyset"`
	Properties []Property
	// This should be set to `"urn:schemas-upnp-org:event-1-0"`.
	Space string `xml:"xmlns:e,attr"`
}

// propertys provide namespacing to the contained variables.
type Property struct {
	XMLName  struct{} `xml:"e:property"`
	Variable Variable
}

// Represents an evented state variable that has sendEvents="yes" in its
// service spec.
type Variable struct {
	XMLName xml.Name
	Value   string `xml:",chardata"`
}

type subscriber struct {
	sid     string
	nextSeq uint32 // 0 for initial event, wraps from Uint32Max to 1.
	urls    []*url.URL
	expiry  time.Time
}

// Intended to eventually be an embeddable implementation for managing
// eventing for a service. Not complete.
type Eventing struct {
	mutex       sync.Mutex
	subscribers map[string]*subscriber
}

func (me *Eventing) Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error) {
	me.mutex.Lock()
	defer me.mutex.Unlock()

	var uuid [16]byte
	io.ReadFull(rand.Reader, uuid[:])
	sid = FormatUUID(uuid[:])
	if _, ok := me.subscribers[sid]; ok {
		err = fmt.Errorf("already subscribed: %s", sid)
		return
	}
	ssr := &subscriber{
		sid:    sid,
		urls:   callback,
		expiry: time.Now().Add(time.Duration(timeoutSeconds) * time.Second),
	}
	if me.subscribers == nil {
		me.subscribers = make(map[string]*subscriber)
	}
	me.subscribers[sid] = ssr
	actualTimeout = int(ssr.expiry.Sub(time.Now()) / time.Second)
	return
}

func (me *Eventing) Unsubscribe(sid string) error {
	return nil
}

var callbackURLRegexp = regexp.MustCompile("<(.*?)>")

// Parse the CALLBACK HTTP header in an event subscription request. See UPnP
// Device Architecture 4.1.2.
func ParseCallbackURLs(callback string) (ret []*url.URL) {
	for _, match := range callbackURLRegexp.FindAllStringSubmatch(callback, -1) {
		_url, err := url.Parse(match[1])
		if err != nil {
			log.Printf("bad callback url: %q", match[1])
			continue
		}
		ret = append(ret, _url)
	}
	return
}
