package playhead

import (
	"bytes"
	"fmt"
	"strings"

	"code.justin.tv/video/spectre/auth"
	"code.justin.tv/video/spectre/swift"
	"code.justin.tv/video/spectre/vods"
	"github.com/stvp/rollbar"
)

var activeManifestWindowSize = 10 // num chunks in an active spectre manifest

type tags []tag
type tag interface {
	Bytes() []byte
}

type header struct {
	xMediaSequenceIndex int
	xTwitchElapsedSecs  float64
	xTwitchTotalSecs    float64
}

func (h header) Bytes() []byte {
	s := `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5

#EXT-X-TWITCH-ELAPSED-SECS:%.3f
#EXT-X-TWITCH-TOTAL-SECS:%.3f
#EXT-X-MEDIA-SEQUENCE:%d
`
	return []byte(fmt.Sprintf(s, h.xTwitchElapsedSecs, h.xTwitchTotalSecs, h.xMediaSequenceIndex))
}

type discontinuity struct{}

func (d discontinuity) Bytes() []byte {
	return []byte("#EXT-X-DISCONTINUITY")
}

type endlist struct{}

func (d endlist) Bytes() []byte {
	return []byte("#EXT-X-ENDLIST")
}

// ActiveManifest returns a byte string representing the active chunks of the current playhead
func (p *Playhead) ActiveManifest(format, streamID string) []byte {
	var ts tags
	var duration float64
	ts, duration = activeTags(p.CurrentVod, p.NextVod, p.ChunkIndex, format, streamID)
	ts = append(tags{header{p.Channel.XMediaSequenceIndex, p.XTwitchElapsedSecs, p.XTwitchElapsedSecs + duration}}, ts...)

	if p.Closing {
		ts = append(ts, endlist{})
	}

	return ts.Bytes()
}

// TODO: add test for playlist with 1 manifest of chunk size 1 and see it repeat
func activeTags(currentVod *vods.Vod, nextVod *vods.Vod, chunkIndex int, format string, streamID string) (tags, float64) {
	var numChunksFound int
	var ts []tag
	var duration float64

	transitioned := false
	appendDiscontinuity := false

	for numChunksFound < activeManifestWindowSize {
		currentManifest, found := currentVod.Manifests[format]
		if !found {
			// If the given format is not found, default to "chunked". This is to support
			// an edge case where not all vods in a playlist are guaranteed to have the same set of formats.
			// And we must guarantee a smooth transition between vods in a playlist
			currentManifest, found = currentVod.Manifests["chunked"]
			if !found {
				rollbar.Message(rollbar.WARN, "Somehow the chunked manifest was not found")
				return ts, 0
			}
		}

		if len(currentManifest.Chunks) > chunkIndex {
			if appendDiscontinuity {
				ts = append(ts, discontinuity{})
				appendDiscontinuity = false
			}
			ts = append(ts, signChunk(currentManifest.Chunks[chunkIndex], format, streamID))
			duration += currentManifest.Chunks[chunkIndex].Duration.Seconds()
			numChunksFound++
			chunkIndex++
		}

		if chunkIndex >= len(currentVod.Manifests["chunked"].Chunks) {
			if transitioned {
				break
			}

			transitioned = true
			chunkIndex = 0
			currentVod = nextVod
			appendDiscontinuity = true
		}
	}
	return ts, duration
}

func signChunk(c swift.Chunk, format string, streamID string) swift.Chunk {
	baseUrl := fmt.Sprintf("/spectre/%v/%v/%v", streamID, format, c.Uri)
	prefix := "?"
	if strings.Contains(c.Uri, "?") {
		prefix = "&"
	}
	signature, _ := auth.Sign(fmt.Sprintf("%v%svod_id=%v", baseUrl, prefix, c.VodID))
	c.Uri = fmt.Sprintf("%v%svod_id=%v&sig=%x", c.Uri, prefix, c.VodID, signature)

	return c
}

func (ts tags) Bytes() []byte {
	s := make([][]byte, len(ts))
	for i, t := range ts {
		s[i] = t.Bytes()
	}
	return []byte(bytes.Join(s, []byte("\n")))
}
