package xray

import (
	"encoding/json"
	"io"
	"log"
	"net"
)

var emitter io.Writer
var header = []byte(`{"format": "json", "version": 1}` + "\n")

func init() {
	refreshEmitter()
}

func refreshEmitter() {
	emitter, _ = net.DialUDP("udp", nil, config.DaemonAddr())
}

func emit(seg *segment) {
	if seg == nil || !seg.sampled {
		return
	}

	for _, p := range encodeIntoPieces(seg, nil) {
		emitter.Write(append(header, p...))
	}
}

func encodeIntoPieces(seg *segment, out [][]byte) [][]byte {
	seg.Lock()
	defer seg.Unlock()

	encode := func(s *segment) []byte {
		b, _ := json.Marshal(s)

		// While the segment is too big, remove children to make it smaller
	TrimLoop:
		for len(b) > maxSegmentSize {
			// There are no children left to remove
			if len(s.rawSubsegments) == 0 {
				// Try to remove stack trace instead
				if s.Cause != nil {
					s.Cause.Exceptions = nil
					log.Printf("Stripping stack-trace for %s:%s to reduce segment size", s.root().TraceID, s.ID)
					continue TrimLoop
				}

				// We've run out of options
				log.Printf("Failed to emit %s:%s, size (%dB) was greater than the %dB limit", s.root().TraceID, s.ID, len(b), maxSegmentSize)
				return nil
			}

			// Find the largest subsegment
			// By splitting the largest subsegment we minimize the number of splits
			size, idx := 0, 0
			for i, b := range s.Subsegments {
				if len(b) > size {
					size, idx = len(b), i
				}
			}

			// Remove the subsegment from the subsegments list
			child := s.rawSubsegments[idx]
			s.Subsegments = append(s.Subsegments[:idx], s.Subsegments[idx+1:]...)
			s.rawSubsegments = append(s.rawSubsegments[:idx], s.rawSubsegments[idx+1:]...)

			// Add extra information into child subsegment, re-marshal, and add to out
			child.Lock()
			child.TraceID = s.root().TraceID
			child.ParentID = s.ID
			child.Type = "subsegment"
			child.parent = nil
			child.sampled = s.sampled
			child.requestWasTraced = s.requestWasTraced

			cb, _ := json.Marshal(child)
			out = append(out, cb)
			child.Unlock()

			// Recompute current segment's encoded form
			b, _ = json.Marshal(s)
		}

		return b
	}

	for _, s := range seg.rawSubsegments {
		out = encodeIntoPieces(s, out)
		if b := encode(s); b != nil {
			seg.Subsegments = append(seg.Subsegments, b)
		}
	}
	if seg.parent == nil {
		if b := encode(seg); b != nil {
			out = append(out, b)
		}
	}
	return out
}
