package client

import (
	"sync"

	"github.com/iotaledger/goshimmer/packages/metrics"
	"github.com/iotaledger/goshimmer/packages/vote"
	"github.com/iotaledger/goshimmer/plugins/analysis/packet"
	"github.com/iotaledger/goshimmer/plugins/autopeering/local"
	"github.com/iotaledger/goshimmer/plugins/banner"
	"github.com/iotaledger/hive.go/identity"
)

var (
	finalized      map[string]vote.Opinion
	finalizedMutex sync.RWMutex
)

func onFinalized(ev *vote.OpinionEvent) {
	finalizedMutex.Lock()
	finalized[ev.ID] = ev.Opinion
	finalizedMutex.Unlock()
}

func onRoundExecuted(roundStats *vote.RoundStats) {
	// get own ID
	nodeID := make([]byte, len(identity.ID{}))
	if local.GetInstance() != nil {
		copy(nodeID, local.GetInstance().ID().Bytes())
	}

	chunks := splitFPCVoteContext(roundStats.ActiveVoteContexts)

	for _, chunk := range chunks {
		rs := vote.RoundStats{
			Duration:           roundStats.Duration,
			RandUsed:           roundStats.RandUsed,
			ActiveVoteContexts: chunk,
		}

		hb := &packet.FPCHeartbeat{
			Version:    banner.SimplifiedAppVersion,
			OwnID:      nodeID,
			RoundStats: rs,
		}

		finalizedMutex.Lock()
		hb.Finalized = finalized
		finalized = make(map[string]vote.Opinion)
		finalizedMutex.Unlock()

		// abort if empty round and no finalized conflicts.
		if len(chunk) == 0 && len(hb.Finalized) == 0 {
			return
		}

		data, err := packet.NewFPCHeartbeatMessage(hb)
		if err != nil {
			log.Debugw("FPC heartbeat message skipped", "error", err)
			return
		}

		log.Debugw("Client: onRoundExecuted data size", "len", len(data))

		if _, err = conn.Write(data); err != nil {
			log.Debugw("Error while writing to connection", "Description", err)
			return
		}
		// trigger AnalysisOutboundBytes event
		metrics.Events().AnalysisOutboundBytes.Trigger(uint64(len(data)))
	}
}

func splitFPCVoteContext(ctx map[string]*vote.Context) (chunk []map[string]*vote.Context) {
	chunk = make([]map[string]*vote.Context, 1)
	i, counter := 0, 0
	chunk[i] = make(map[string]*vote.Context)

	if len(ctx) < voteContextChunkThreshold {
		chunk[i] = ctx
		return
	}

	for conflictID, voteCtx := range ctx {
		counter++
		if counter >= voteContextChunkThreshold {
			counter = 0
			i++
			chunk = append(chunk, make(map[string]*vote.Context))
		}
		chunk[i][conflictID] = voteCtx
	}

	return
}