package go_alac

import (
	"bytes"
	"github.com/edgeware/mp4ff/bits"
	"github.com/edgeware/mp4ff/mp4"
	"io"
	"time"
)

type FormatEncoder struct {
	encoder          *FrameEncoder
	writer           io.Writer
	sampleRate       int
	buffer           []byte
	outputBuffer     [][]byte
	segmentDuration  time.Duration
	samplesPerPacket int
	packetsWritten   int
	trackId          uint32
	seqNumber        uint32
}

type alacBox struct {
	cookie []byte
}

func (b *alacBox) Type() string {
	return "alac"
}

// Size - calculated size of box
func (b *alacBox) Size() uint64 {
	return uint64(12 + len(b.cookie))
}

// Encode - write box to w
func (b *alacBox) Encode(w io.Writer) error {
	sw := bits.NewFixedSliceWriter(int(b.Size()))
	err := b.EncodeSW(sw)
	if err != nil {
		return err
	}
	_, err = w.Write(sw.Bytes())
	return err
}

func (b *alacBox) EncodeSW(sw bits.SliceWriter) error {
	err := mp4.EncodeHeaderSW(b, sw)
	if err != nil {
		return err
	}
	sw.WriteUint32(0) //version
	sw.WriteBytes(b.cookie)
	return sw.AccError()
}

func (b *alacBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
	return nil
}

func NewFormatEncoder(writer io.Writer, sampleRate, channels, bitDepth int, fastMode bool, segmentDuration time.Duration) *FormatEncoder {
	e := &FormatEncoder{
		encoder:         NewFrameEncoder(sampleRate, channels, bitDepth, fastMode),
		writer:          writer,
		sampleRate:      sampleRate,
		segmentDuration: segmentDuration,
	}

	if e.encoder == nil {
		return nil
	}

	e.samplesPerPacket = e.encoder.GetSamplesPerPacket()

	init := mp4.CreateEmptyInit()
	init.AddEmptyTrack(uint32(sampleRate), "audio", "en")
	e.trackId = init.Moov.Mvhd.NextTrackID - 1
	trak := init.Moov.Trak

	stsd := trak.Mdia.Minf.Stbl.Stsd

	//TODO: this does not work with 96kHz freq etc.
	mp4a := mp4.CreateAudioSampleEntryBox("alac", uint16(channels), uint16(bitDepth), uint16(sampleRate), &alacBox{
		cookie: e.encoder.GetMagicCookie(),
	})
	stsd.AddChild(mp4a)

	init.Encode(writer)

	return e
}

func (e *FormatEncoder) outputPacket(packet []byte) {
	e.outputBuffer = append(e.outputBuffer, packet)

	if time.Duration(float64(time.Second)*(float64(e.samplesPerPacket*len(e.outputBuffer))/float64(e.sampleRate))) >= e.segmentDuration {
		e.outputSegment()
	}

}

func (e *FormatEncoder) outputSegment() {
	seg := mp4.NewMediaSegment()
	frag, _ := mp4.CreateFragment(e.seqNumber, e.trackId)
	seg.AddFragment(frag)

	for _, b := range e.outputBuffer {
		frag.AddFullSampleToTrack(mp4.FullSample{
			Sample: mp4.Sample{
				Dur:  uint32(e.samplesPerPacket),
				Size: uint32(len(b)),
			},
			DecodeTime: uint64(e.samplesPerPacket * e.packetsWritten),
			Data:       b,
		}, e.trackId)
		e.packetsWritten++
	}

	seg.Encode(e.writer)
	e.seqNumber++
	e.outputBuffer = nil

}

func (e *FormatEncoder) Write(pcm []byte) {
	if e.encoder == nil {
		return
	}
	e.buffer = append(e.buffer, pcm...)
	inputSize := e.encoder.GetInputSize()
	for len(e.buffer) >= inputSize {
		e.outputPacket(e.encoder.WritePacket(e.buffer[:inputSize]))
		e.buffer = e.buffer[inputSize:]
	}
}
func (e *FormatEncoder) Flush() {
	if e.encoder == nil {
		return
	}

	if len(e.buffer) > 0 {
		e.outputPacket(e.encoder.WritePacket(e.buffer))
		e.buffer = nil
	}

	if len(e.outputBuffer) > 0 {
		e.outputSegment()
	}
}

type FormatDecoder struct {
	trex      *mp4.TrexBox
	handle    *FrameDecoder
	parsedMp4 *mp4.File

	currentSegment  int
	currentFragment int
}

func NewFormatDecoder(reader io.Reader) *FormatDecoder {
	parsedMp4, err := mp4.DecodeFile(reader)
	if err != nil {
		return nil
	}

	var trexEntry *mp4.TrexBox
	var magicCookie []byte

	//TODO: handle non-segmented

	for _, trak := range parsedMp4.Moov.Traks {
		if box, err := trak.Mdia.Minf.Stbl.Stsd.GetSampleDescription(0); err == nil && box.Type() == "alac" {
			if parsedMp4.Moov.Mvex == nil {
				continue
			}
			for _, trex := range parsedMp4.Moov.Mvex.Trexs {
				if trex.TrackID == trak.Tkhd.TrackID {
					trexEntry = trex
					break
				}
			}

			if trexEntry == nil {
				return nil
			}

			buf := new(bytes.Buffer)
			box.Encode(buf)

			boxBytes := buf.Bytes()

			boxOffset := 36 + 12

			magicCookie = boxBytes[boxOffset:]
			break
		}
	}

	if trexEntry == nil || magicCookie == nil {
		return nil
	}

	decoder := NewFrameDecoder(magicCookie)
	if decoder == nil {
		return nil
	}

	return &FormatDecoder{
		handle:    decoder,
		trex:      trexEntry,
		parsedMp4: parsedMp4,
	}
}

func (d *FormatDecoder) GetChannels() int {
	return d.handle.GetChannels()
}

func (d *FormatDecoder) GetBitDepth() int {
	return d.handle.GetBitDepth()
}

func (d *FormatDecoder) GetSampleRate() int {
	return d.handle.GetSampleRate()
}

func (d *FormatDecoder) Read() (buf []byte) {
	if d.currentSegment >= len(d.parsedMp4.Segments) {
		//EOF
		return nil
	}
	segment := d.parsedMp4.Segments[d.currentSegment]

	if d.currentFragment >= len(segment.Fragments) {
		d.currentSegment++
		d.currentFragment = 0
		return d.Read()
	}

	frag := segment.Fragments[d.currentFragment]

	samples, err := frag.GetFullSamples(d.trex)
	if err != nil {
		return nil
	}

	for _, sample := range samples {
		_, pcm := d.handle.ReadPacket(sample.Data)

		buf = append(buf, pcm...)
	}
	d.currentFragment++

	return
}
