// Copyright 2021 The Oto Authors
//
// 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 mux offers APIs for a low-level multiplexer of audio players.
// Usually you don't have to use this package directly.
package mux

import (
	"errors"
	"fmt"
	"io"
	"math"
	"runtime"
	"sync"
	"time"
)

// Format must sync with oto's Format.
type Format int

const (
	FormatFloat32LE Format = iota
	FormatUnsignedInt8
	FormatSignedInt16LE
)

func (f Format) ByteLength() int {
	switch f {
	case FormatFloat32LE:
		return 4
	case FormatUnsignedInt8:
		return 1
	case FormatSignedInt16LE:
		return 2
	}
	panic(fmt.Sprintf("mux: unexpected format: %d", f))
}

// Mux is a low-level multiplexer of audio players.
type Mux struct {
	sampleRate   int
	channelCount int
	format       Format

	players map[*playerImpl]struct{}
	cond    *sync.Cond
}

// New creates a new Mux.
func New(sampleRate int, channelCount int, format Format) *Mux {
	m := &Mux{
		sampleRate:   sampleRate,
		channelCount: channelCount,
		format:       format,
		cond:         sync.NewCond(&sync.Mutex{}),
	}
	go m.loop()
	return m
}

func (m *Mux) shouldWait() bool {
	for p := range m.players {
		if p.canReadSourceToBuffer() {
			return false
		}
	}
	return true
}

func (m *Mux) wait() {
	m.cond.L.Lock()
	defer m.cond.L.Unlock()

	for m.shouldWait() {
		m.cond.Wait()
	}
}

func (m *Mux) loop() {
	var players []*playerImpl
	for {
		m.wait()

		m.cond.L.Lock()
		for i := range players {
			players[i] = nil
		}
		players = players[:0]
		for p := range m.players {
			players = append(players, p)
		}
		m.cond.L.Unlock()

		allZero := true
		for _, p := range players {
			n := p.readSourceToBuffer()
			if n != 0 {
				allZero = false
			}
		}

		// Sleeping is necessary especially on browsers.
		// Sometimes a player continues to read 0 bytes from the source and this loop can be a busy loop in such case.
		if allZero {
			time.Sleep(time.Millisecond)
		}
	}
}

func (m *Mux) addPlayer(player *playerImpl) {
	m.cond.L.Lock()
	defer m.cond.L.Unlock()

	if m.players == nil {
		m.players = map[*playerImpl]struct{}{}
	}
	m.players[player] = struct{}{}
	m.cond.Signal()
}

func (m *Mux) removePlayer(player *playerImpl) {
	m.cond.L.Lock()
	defer m.cond.L.Unlock()

	delete(m.players, player)
	m.cond.Signal()
}

// ReadFloat32s fills buf with the multiplexed data of the players as float32 values.
func (m *Mux) ReadFloat32s(buf []float32) {
	m.cond.L.Lock()
	players := make([]*playerImpl, 0, len(m.players))
	for p := range m.players {
		players = append(players, p)
	}
	m.cond.L.Unlock()

	for i := range buf {
		buf[i] = 0
	}
	for _, p := range players {
		p.readBufferAndAdd(buf)
	}
	m.cond.Signal()
}

type Player struct {
	p       *playerImpl
	cleanup runtime.Cleanup
}

type playerState int

const (
	playerPaused playerState = iota
	playerPlay
	playerClosed
)

type playerImpl struct {
	mux        *Mux
	src        io.Reader
	prevVolume float64
	volume     float64
	err        error
	state      playerState
	buf        []byte
	eof        bool
	bufferSize int

	m sync.Mutex
}

func (m *Mux) NewPlayer(src io.Reader) *Player {
	pl := &Player{
		p: &playerImpl{
			mux:        m,
			src:        src,
			prevVolume: 1,
			volume:     1,
			bufferSize: m.defaultBufferSize(),
		},
	}
	pl.cleanup = runtime.AddCleanup(pl, func(p *playerImpl) {
		_ = p.Close()
	}, pl.p)
	return pl
}

func (p *Player) Err() error {
	return p.p.Err()
}

func (p *playerImpl) Err() error {
	p.m.Lock()
	defer p.m.Unlock()

	return p.err
}

func (p *Player) Play() {
	p.p.Play()
}

func (p *playerImpl) Play() {
	// Goroutines don't work effiently on Windows. Avoid using them (hajimehoshi/ebiten#1768).
	if runtime.GOOS == "windows" {
		p.m.Lock()
		defer p.m.Unlock()

		p.playImpl()
	} else {
		ch := make(chan struct{})
		go func() {
			p.m.Lock()
			defer p.m.Unlock()

			close(ch)
			p.playImpl()
		}()
		<-ch
	}
}

func (p *Player) SetBufferSize(bufferSize int) {
	p.p.setBufferSize(bufferSize)
}

func (p *playerImpl) setBufferSize(bufferSize int) {
	p.m.Lock()
	defer p.m.Unlock()

	p.bufferSize = bufferSize
	if bufferSize == 0 {
		p.bufferSize = p.mux.defaultBufferSize()
	}
}

var theBufPool = sync.Pool{
	New: func() interface{} {
		var buf []byte
		return &buf
	},
}

func getBufferFromPool(size int) *[]byte {
	buf := theBufPool.Get().(*[]byte)

	if cap(*buf) < size {
		*buf = make([]byte, size)
	}

	*buf = (*buf)[:size]

	return buf
}

// read reads the source to buf.
// read unlocks the mutex temporarily and locks when reading finishes.
// This avoids locking during an external function call Read (#188).
//
// When read is called, the mutex m must be locked.
func (p *playerImpl) read(buf []byte) (int, error) {
	p.m.Unlock()
	defer p.m.Lock()
	return p.src.Read(buf)
}

// addToPlayers adds p to the players set.
//
// When addToPlayers is called, the mutex m must be locked.
func (p *playerImpl) addToPlayers() {
	p.m.Unlock()
	defer p.m.Lock()
	p.mux.addPlayer(p)
}

// removeFromPlayers removes p from the players set.
//
// When removeFromPlayers is called, the mutex m must be locked.
func (p *playerImpl) removeFromPlayers() {
	p.m.Unlock()
	defer p.m.Lock()
	p.mux.removePlayer(p)
}

func (p *playerImpl) playImpl() {
	if p.err != nil {
		return
	}
	if p.state != playerPaused {
		return
	}
	p.state = playerPlay

	if !p.eof {
		buf := getBufferFromPool(p.bufferSize)
		defer theBufPool.Put(buf)

		if p.buf == nil {
			p.buf = (*getBufferFromPool(p.bufferSize))[:0]
		}

		for len(p.buf) < p.bufferSize {
			n, err := p.read(*buf)
			if err != nil && err != io.EOF {
				p.setErrorImpl(err)
				return
			}
			p.buf = append(p.buf, (*buf)[:n]...)
			if err == io.EOF {
				p.eof = true
				break
			}
		}
	}

	if p.eof && len(p.buf) == 0 {
		p.returnBufferToPool()
		p.state = playerPaused
	}

	p.addToPlayers()
}

func (p *Player) Pause() {
	p.p.Pause()
}

func (p *playerImpl) Pause() {
	p.m.Lock()
	defer p.m.Unlock()

	if p.state != playerPlay {
		return
	}
	p.state = playerPaused
}

func (p *Player) Seek(offset int64, whence int) (int64, error) {
	return p.p.Seek(offset, whence)
}

func (p *playerImpl) Seek(offset int64, whence int) (int64, error) {
	p.m.Lock()
	defer p.m.Unlock()

	// If a player is playing, keep playing even after this seeking.
	if p.state == playerPlay {
		defer p.playImpl()
	}

	// Reset the internal buffer.
	p.resetImpl()

	// Check if the source implements io.Seeker.
	s, ok := p.src.(io.Seeker)
	if !ok {
		return 0, errors.New("mux: the source must implement io.Seeker")
	}
	return s.Seek(offset, whence)
}

func (p *Player) Reset() {
	p.p.Reset()
}

func (p *playerImpl) Reset() {
	p.m.Lock()
	defer p.m.Unlock()
	p.resetImpl()
}

func (p *playerImpl) resetImpl() {
	if p.state == playerClosed {
		return
	}
	p.state = playerPaused
	p.buf = p.buf[:0]
	p.eof = false
}

func (p *Player) IsPlaying() bool {
	return p.p.IsPlaying()
}

func (p *playerImpl) IsPlaying() bool {
	p.m.Lock()
	defer p.m.Unlock()
	return p.state == playerPlay
}

func (p *Player) Volume() float64 {
	return p.p.Volume()
}

func (p *playerImpl) Volume() float64 {
	p.m.Lock()
	defer p.m.Unlock()
	return p.volume
}

func (p *Player) SetVolume(volume float64) {
	p.p.SetVolume(volume)
}

func (p *playerImpl) SetVolume(volume float64) {
	p.m.Lock()
	defer p.m.Unlock()
	p.volume = volume
	if p.state != playerPlay {
		p.prevVolume = volume
	}
}

func (p *Player) BufferedSize() int {
	return p.p.BufferedSize()
}

func (p *playerImpl) BufferedSize() int {
	p.m.Lock()
	defer p.m.Unlock()
	return len(p.buf)
}

func (p *Player) Close() error {
	p.cleanup.Stop()
	return p.p.Close()
}

func (p *playerImpl) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	return p.closeImpl()
}

func (p *playerImpl) closeImpl() error {
	p.removeFromPlayers()

	if p.state == playerClosed {
		return p.err
	}
	p.state = playerClosed
	p.returnBufferToPool()

	return p.err
}

func (p *playerImpl) readBufferAndAdd(buf []float32) int {
	p.m.Lock()
	defer p.m.Unlock()

	if p.state != playerPlay {
		return 0
	}

	format := p.mux.format
	bitDepthInBytes := format.ByteLength()
	n := len(p.buf) / bitDepthInBytes
	if n > len(buf) {
		n = len(buf)
	}

	prevVolume := float32(p.prevVolume)
	volume := float32(p.volume)

	channelCount := p.mux.channelCount
	rateDenom := float32(n / channelCount)

	src := p.buf[:n*bitDepthInBytes]

	for i := 0; i < n; i++ {
		var v float32
		switch format {
		case FormatFloat32LE:
			v = math.Float32frombits(uint32(src[4*i]) | uint32(src[4*i+1])<<8 | uint32(src[4*i+2])<<16 | uint32(src[4*i+3])<<24)
		case FormatUnsignedInt8:
			v8 := src[i]
			v = float32(v8-(1<<7)) / (1 << 7)
		case FormatSignedInt16LE:
			v16 := int16(src[2*i]) | (int16(src[2*i+1]) << 8)
			v = float32(v16) / (1 << 15)
		default:
			panic(fmt.Sprintf("mux: unexpected format: %d", format))
		}
		if volume == prevVolume {
			buf[i] += v * volume
		} else {
			rate := float32(i/channelCount) / rateDenom
			if rate > 1 {
				rate = 1
			}
			buf[i] += v * (volume*rate + prevVolume*(1-rate))
		}
	}

	p.prevVolume = p.volume

	copy(p.buf, p.buf[n*bitDepthInBytes:])
	p.buf = p.buf[:len(p.buf)-n*bitDepthInBytes]

	if p.eof && len(p.buf) == 0 {
		p.returnBufferToPool()
		p.state = playerPaused
	}

	return n
}

func (p *playerImpl) canReadSourceToBuffer() bool {
	p.m.Lock()
	defer p.m.Unlock()

	if p.eof {
		return false
	}
	return len(p.buf) < p.bufferSize
}

func (p *playerImpl) readSourceToBuffer() int {
	p.m.Lock()
	defer p.m.Unlock()

	if p.err != nil {
		return 0
	}
	if p.state == playerClosed {
		return 0
	}

	if len(p.buf) >= p.bufferSize {
		return 0
	}

	buf := getBufferFromPool(p.bufferSize)
	defer theBufPool.Put(buf)
	n, err := p.read(*buf)

	if err != nil && err != io.EOF {
		p.setErrorImpl(err)
		return 0
	}

	if p.buf == nil {
		p.buf = (*getBufferFromPool(p.bufferSize))[:0]
	}

	p.buf = append(p.buf, (*buf)[:n]...)
	if err == io.EOF {
		p.eof = true
		if len(p.buf) == 0 {
			p.state = playerPaused
		}
	}
	return n
}

func (p *playerImpl) setErrorImpl(err error) {
	p.err = err
	p.closeImpl()
}

func (p *playerImpl) returnBufferToPool() {
	if p.buf != nil {
		buf := p.buf
		theBufPool.Put(&buf)
		p.buf = nil
	}
}

// TODO: The term 'buffer' is confusing. Name each buffer with good terms.

// defaultBufferSize returns the default size of the buffer for the audio source.
// This buffer is used when unreading on pausing the player.
func (m *Mux) defaultBufferSize() int {
	bytesPerSample := m.channelCount * m.format.ByteLength()
	s := m.sampleRate * bytesPerSample / 2 // 0.5[s]
	// Align s in multiples of bytes per sample, or a buffer could have extra bytes.
	return s / bytesPerSample * bytesPerSample
}
