// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package yggconn

import (
	"context"
	"crypto/ed25519"
	"encoding/hex"
	"fmt"
	"net"
	"regexp"
	"strings"

	"github.com/matrix-org/gomatrixserverlib"
	"github.com/neilalexander/utp"
	"github.com/sirupsen/logrus"

	ironwoodtypes "github.com/Arceliar/ironwood/types"
	"github.com/yggdrasil-network/yggdrasil-go/src/core"
	yggdrasilcore "github.com/yggdrasil-network/yggdrasil-go/src/core"
	"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
	yggdrasilmulticast "github.com/yggdrasil-network/yggdrasil-go/src/multicast"

	gologme "github.com/gologme/log"
)

type Node struct {
	core      *yggdrasilcore.Core
	multicast *yggdrasilmulticast.Multicast
	log       *gologme.Logger
	utpSocket *utp.Socket
	incoming  chan net.Conn
}

func (n *Node) DialerContext(ctx context.Context, _, address string) (net.Conn, error) {
	tokens := strings.Split(address, ":")
	raw, err := hex.DecodeString(tokens[0])
	if err != nil {
		return nil, fmt.Errorf("hex.DecodeString: %w", err)
	}
	pk := make(ironwoodtypes.Addr, ed25519.PublicKeySize)
	copy(pk, raw[:])
	return n.utpSocket.DialAddrContext(ctx, pk)
}

func Setup(sk ed25519.PrivateKey, instanceName, storageDirectory, peerURI, listenURI string) (*Node, error) {
	n := &Node{
		log:      gologme.New(logrus.StandardLogger().Writer(), "", 0),
		incoming: make(chan net.Conn),
	}

	n.log.EnableLevel("error")
	n.log.EnableLevel("warn")
	n.log.EnableLevel("info")

	{
		var err error
		options := []yggdrasilcore.SetupOption{}
		if listenURI != "" {
			options = append(options, yggdrasilcore.ListenAddress(listenURI))
		}
		if peerURI != "" {
			for _, uri := range strings.Split(peerURI, ",") {
				options = append(options, yggdrasilcore.Peer{
					URI: uri,
				})
			}
		}
		if n.core, err = core.New(sk[:], n.log, options...); err != nil {
			panic(err)
		}
		n.core.SetLogger(n.log)

		if n.utpSocket, err = utp.NewSocketFromPacketConnNoClose(n.core); err != nil {
			panic(err)
		}
	}

	// Setup the multicast module.
	{
		var err error
		options := []multicast.SetupOption{
			multicast.MulticastInterface{
				Regex:    regexp.MustCompile(".*"),
				Beacon:   true,
				Listen:   true,
				Port:     0,
				Priority: 0,
			},
		}
		if n.multicast, err = multicast.New(n.core, n.log, options...); err != nil {
			panic(err)
		}
	}

	n.log.Printf("Public key: %x", n.core.PublicKey())
	go n.listenFromYgg()

	return n, nil
}

func (n *Node) Stop() {
	if err := n.multicast.Stop(); err != nil {
		n.log.Println("Error stopping multicast:", err)
	}
	n.core.Stop()
}

func (n *Node) DerivedServerName() string {
	return hex.EncodeToString(n.PublicKey())
}

func (n *Node) PrivateKey() ed25519.PrivateKey {
	return n.core.PrivateKey()
}

func (n *Node) PublicKey() ed25519.PublicKey {
	return n.core.PublicKey()
}

func (n *Node) PeerCount() int {
	return len(n.core.GetPeers())
}

func (n *Node) KnownNodes() []gomatrixserverlib.ServerName {
	nodemap := map[string]struct{}{}
	for _, peer := range n.core.GetPeers() {
		nodemap[hex.EncodeToString(peer.Key)] = struct{}{}
	}
	var nodes []gomatrixserverlib.ServerName
	for node := range nodemap {
		nodes = append(nodes, gomatrixserverlib.ServerName(node))
	}
	return nodes
}

func (n *Node) SetMulticastEnabled(enabled bool) {
	// TODO: There's no dynamic reconfiguration in Yggdrasil v0.4
	// so we need a solution for this.
}

func (n *Node) DisconnectMulticastPeers() {
	// TODO: There's no dynamic reconfiguration in Yggdrasil v0.4
	// so we need a solution for this.
}

func (n *Node) DisconnectNonMulticastPeers() {
	// TODO: There's no dynamic reconfiguration in Yggdrasil v0.4
	// so we need a solution for this.
}

func (n *Node) SetStaticPeer(uri string) error {
	// TODO: There's no dynamic reconfiguration in Yggdrasil v0.4
	// so we need a solution for this.
	return nil
}