package graph

import (
	"container/ring"
	"fmt"
	"strconv"
	"strings"

	socketio "github.com/googollee/go-socket.io"
	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
	"github.com/iotaledger/goshimmer/packages/parameter"
	"github.com/iotaledger/iota.go/consts"

	"github.com/iotaledger/hive.go/syncutils"
)

const (
	TX_BUFFER_SIZE = 1800
)

var (
	txRingBuffer *ring.Ring // transactions
	snRingBuffer *ring.Ring // confirmed transactions
	msRingBuffer *ring.Ring // Milestones

	broadcastLock    = syncutils.Mutex{}
	txRingBufferLock = syncutils.Mutex{}
)

type wsTransaction struct {
	Hash              string `json:"hash"`
	Address           string `json:"address"`
	Value             string `json:"value"`
	Tag               string `json:"tag"`
	Timestamp         string `json:"timestamp"`
	CurrentIndex      string `json:"current_index"`
	LastIndex         string `json:"last_index"`
	Bundle            string `json:"bundle_hash"`
	TrunkTransaction  string `json:"transaction_trunk"`
	BranchTransaction string `json:"transaction_branch"`
}

type wsTransactionSn struct {
	Hash              string `json:"hash"`
	Address           string `json:"address"`
	TrunkTransaction  string `json:"transaction_trunk"`
	BranchTransaction string `json:"transaction_branch"`
	Bundle            string `json:"bundle"`
}

type wsConfig struct {
	NetworkName string `json:"networkName"`
}

func initRingBuffers() {
	txRingBuffer = ring.New(TX_BUFFER_SIZE)
	snRingBuffer = ring.New(TX_BUFFER_SIZE)
	msRingBuffer = ring.New(20)
}

func onConnectHandler(s socketio.Conn) error {
	infoMsg := "Graph client connection established"
	if s != nil {
		infoMsg = fmt.Sprintf("%s (ID: %v)", infoMsg, s.ID())
	}
	log.Info(infoMsg)
	socketioServer.JoinRoom("broadcast", s)

	config := &wsConfig{NetworkName: parameter.NodeConfig.GetString("graph.networkName")}

	var initTxs []*wsTransaction
	txRingBuffer.Do(func(tx interface{}) {
		if tx != nil {
			initTxs = append(initTxs, tx.(*wsTransaction))
		}
	})

	var initSns []*wsTransactionSn
	snRingBuffer.Do(func(sn interface{}) {
		if sn != nil {
			initSns = append(initSns, sn.(*wsTransactionSn))
		}
	})

	var initMs []string
	msRingBuffer.Do(func(ms interface{}) {
		if ms != nil {
			initMs = append(initMs, ms.(string))
		}
	})

	s.Emit("config", config)
	s.Emit("inittx", initTxs)
	s.Emit("initsn", initSns)
	s.Emit("initms", initMs)
	s.Emit("donation", "0")
	s.Emit("donations", []int{})
	s.Emit("donation-address", "-")

	return nil
}

func onErrorHandler(conn socketio.Conn, e error) {
	errorMsg := "Graph meet error"
	if e != nil {
		errorMsg = fmt.Sprintf("%s: %s", errorMsg, e.Error())
	}
	log.Error(errorMsg)
}

func onDisconnectHandler(s socketio.Conn, msg string) {
	infoMsg := "Graph client connection closed"
	if s != nil {
		infoMsg = fmt.Sprintf("%s (ID: %v)", infoMsg, s.ID())
	}
	log.Info(fmt.Sprintf("%s: %s", infoMsg, msg))
	socketioServer.LeaveAllRooms(s)
}

var emptyTag = strings.Repeat("9", consts.TagTrinarySize/3)

func onNewTx(tx *value_transaction.ValueTransaction) {
	wsTx := &wsTransaction{
		Hash:              tx.GetHash(),
		Address:           tx.GetAddress(),
		Value:             strconv.FormatInt(tx.GetValue(), 10),
		Tag:               emptyTag,
		Timestamp:         strconv.FormatInt(int64(tx.GetTimestamp()), 10),
		CurrentIndex:      "0",
		LastIndex:         "0",
		Bundle:            consts.NullHashTrytes,
		TrunkTransaction:  tx.GetTrunkTransactionHash(),
		BranchTransaction: tx.GetBranchTransactionHash(),
	}

	txRingBufferLock.Lock()
	txRingBuffer.Value = wsTx
	txRingBuffer = txRingBuffer.Next()
	txRingBufferLock.Unlock()

	broadcastLock.Lock()
	socketioServer.BroadcastToRoom("broadcast", "tx", wsTx)
	broadcastLock.Unlock()
}