// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"runtime"
	"time"

	"github.com/beevik/etree"  // BSD-2-clause
	"github.com/xmppo/go-xmpp" // BSD-3-Clause
)

func sendIQ(client *xmpp.Client, iqc chan xmpp.IQ, target string, iQtype string, content string) (xmpp.IQ, error) {
	var iq xmpp.IQ
	id := getID()
	c := make(chan xmpp.IQ, defaultBufferSize)
	go getIQ(id, c, iqc)
	_, err := client.RawInformation(client.JID(), target, id,
		iQtype, content)
	if err != nil {
		return iq, fmt.Errorf("sendIQ: failed to send iq: %w", err)
	}
	select {
	case iq = <-c:
	case <-time.After(60 * time.Second):
		return iq, fmt.Errorf("sendIQ: server didn't reply to IQ: %s", content)
	}
	return iq, nil
}

func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) {
	for {
		iq := <-iqc
		if iq.ID == id {
			c <- iq
			return
		}
	}
}

func rcvStanzas(client *xmpp.Client, ctx context.Context, iqc chan xmpp.IQ, msgc chan xmpp.Chat) {
	var received interface{}
	r := make(chan interface{}, defaultBufferSize)
	e := make(chan error, defaultBufferSize)
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			rcv, err := client.Recv()
			if err != nil {
				e <- err
			} else {
				r <- rcv
			}
		}
	}()
	for {
		select {
		case <-ctx.Done():
			return
		case err := <-e:
			if err != nil {
				if err != io.EOF {
					closeAndExit(client, err)
					return
				}
				return
			}
		case received = <-r:
		}
		switch v := received.(type) {
		case xmpp.Chat:

			msgc <- v
		case xmpp.IQ:
			switch v.Type {
			case "get":
				var xmlns *etree.Attr
				iq := etree.NewDocument()
				err := iq.ReadFromBytes(v.Query)
				if err != nil {
					log.Println("Couldn't parse IQ:",
						string(v.Query), err)
				}
				query := iq.SelectElement("query")
				if query != nil {
					xmlns = query.SelectAttr("xmlns")
				}
				if xmlns == nil {
					break
				}
				switch xmlns.Value {
				case nsDiscoInfo:
					root := etree.NewDocument()
					root.WriteSettings.AttrSingleQuote = true
					reply := root.CreateElement("iq")
					reply.CreateAttr("type", "result")
					reply.CreateAttr("from", client.JID())
					reply.CreateAttr("to", v.From)
					reply.CreateAttr("id", v.ID)
					replyQuery := reply.CreateElement("query")
					replyQuery.CreateAttr("xmlns", nsDiscoInfo)
					identity := replyQuery.CreateElement("identity")
					identity.CreateAttr("category", "client")
					identity.CreateAttr("type", "bot")
					identity.CreateAttr("name", "go-sendxmpp")
					feat := replyQuery.CreateElement("feature")
					feat.CreateAttr("var", nsDiscoInfo)
					feat2 := replyQuery.CreateElement("feature")
					feat2.CreateAttr("var", nsVersion)
					xmlString, err := root.WriteToString()
					if err == nil {
						_, err = client.SendOrg(xmlString)
						if err != nil {
							log.Println(err)
						}
					}
				case nsVersion:
					root := etree.NewDocument()
					root.WriteSettings.AttrSingleQuote = true
					reply := root.CreateElement("iq")
					reply.CreateAttr("type", "result")
					reply.CreateAttr("from", client.JID())
					reply.CreateAttr("to", v.From)
					reply.CreateAttr("id", v.ID)
					replyQuery := reply.CreateElement("query")
					replyQuery.CreateAttr("xmlns", nsVersion)
					rqName := replyQuery.CreateElement("name")
					rqName.CreateText("go-sendxmpp")
					rqVersion := replyQuery.CreateElement("version")
					rqVersion.CreateText(version)
					rqOS := replyQuery.CreateElement("os")
					rqOS.CreateText(runtime.GOOS)
					xmlString, err := root.WriteToString()
					if err == nil {
						_, err = client.SendOrg(xmlString)
						if err != nil {
							log.Println(err)
						}
					}
				default:
					root := etree.NewDocument()
					root.WriteSettings.AttrSingleQuote = true
					reply := root.CreateElement("iq")
					reply.CreateAttr("type", strError)
					reply.CreateAttr("from", client.JID())
					reply.CreateAttr("to", v.From)
					reply.CreateAttr("id", v.ID)
					errorReply := reply.CreateElement(strError)
					errorReply.CreateAttr("type", "cancel")
					su := errorReply.CreateElement("service-unavailable")
					su.CreateAttr("xmlns", nsXMPPStanzas)
					xmlString, err := root.WriteToString()
					if err == nil {
						_, err = client.SendOrg(xmlString)
						if err != nil {
							log.Println(err)
						}
					}
				}
			case "set":
				root := etree.NewDocument()
				root.WriteSettings.AttrSingleQuote = true
				reply := root.CreateElement("iq")
				reply.CreateAttr("type", strError)
				reply.CreateAttr("from", client.JID())
				reply.CreateAttr("to", v.From)
				reply.CreateAttr("id", v.ID)
				errorReply := reply.CreateElement(strError)
				errorReply.CreateAttr("type", "cancel")
				su := errorReply.CreateElement("service-unavailable")
				su.CreateAttr("xmlns", nsXMPPStanzas)
				xmlString, err := root.WriteToString()
				if err == nil {
					_, err = client.SendOrg(xmlString)
					if err != nil {
						log.Println(err)
					}
				}
			case "reply", strError:
				iqc <- v
			default:
				iqc <- v
			}
		}
	}
}
