package flac

import (
	"encoding/binary"
	"io"

	"git.gammaspectra.live/S.O.N.G/flacgo/frame"
	"git.gammaspectra.live/S.O.N.G/flacgo/internal/hashutil/crc16"
	"git.gammaspectra.live/S.O.N.G/flacgo/internal/hashutil/crc8"
	"git.gammaspectra.live/S.O.N.G/flacgo/internal/utf8"
	"github.com/icza/bitio"
	"github.com/mewkiz/pkg/errutil"
)

// --- [ Frame ] ---------------------------------------------------------------

// WriteFrame encodes the given audio frame to the output stream. The Num field
// of the frame header is automatically calculated by the encoder.
func (enc *Encoder) WriteFrame(f *frame.Frame) error {
	// Sanity checks.
	nchannels := int(enc.Info.NChannels)
	if nchannels != len(f.Subframes) {
		return errutil.Newf("subframe and channel count mismatch; expected %d, got %d", nchannels, len(f.Subframes))
	}
	nsamplesPerChannel := f.Subframes[0].NSamples
	if !(16 <= nsamplesPerChannel && nsamplesPerChannel <= 65535) {
		return errutil.Newf("invalid number of samples per channel; expected >= 16 && <= 65535, got %d", nsamplesPerChannel)
	}
	for i, subframe := range f.Subframes {
		if nsamplesPerChannel != len(subframe.Samples) {
			return errutil.Newf("invalid number of samples in channel %d; expected %d, got %d", i, nsamplesPerChannel, len(subframe.Samples))
		}
	}
	if nchannels != f.Channels.Count() {
		return errutil.Newf("channel count mismatch; expected %d, got %d", nchannels, f.Channels.Count())
	}

	// Create a new CRC-16 hash writer which adds the data from all write
	// operations to a running hash.
	h := crc16.NewIBM()
	hw := io.MultiWriter(h, enc.w)

	// Encode frame header.
	f.Num = enc.curNum
	if f.HasFixedBlockSize {
		enc.curNum++
	} else {
		enc.curNum += uint64(nsamplesPerChannel)
	}
	enc.nsamples += uint64(nsamplesPerChannel)
	blockSize := uint16(nsamplesPerChannel)
	if enc.blockSizeMin == 0 || blockSize < enc.blockSizeMin {
		enc.blockSizeMin = blockSize
	}
	if enc.blockSizeMax == 0 || blockSize > enc.blockSizeMax {
		enc.blockSizeMax = blockSize
	}
	// TODO: track number of bytes written to hw, to update values of
	// frameSizeMin and frameSizeMax.
	// Add unencoded audio samples to running MD5 hash.
	f.Hash(enc.md5sum)
	if err := enc.encodeFrameHeader(hw, f.Header); err != nil {
		return errutil.Err(err)
	}

	// Encode subframes.
	bw := bitio.NewWriter(hw)
	for _, subframe := range f.Subframes {
		if err := encodeSubframe(bw, f.Header, subframe); err != nil {
			return errutil.Err(err)
		}
	}

	// Zero-padding to byte alignment.
	// Flush pending writes to subframe.
	if _, err := bw.Align(); err != nil {
		return errutil.Err(err)
	}

	// CRC-16 (polynomial = x^16 + x^15 + x^2 + x^0, initialized with 0) of
	// everything before the crc, back to and including the frame header sync
	// code.
	crc := h.Sum16()
	if err := binary.Write(enc.w, binary.BigEndian, crc); err != nil {
		return errutil.Err(err)
	}

	return nil
}

// --- [ Frame header ] --------------------------------------------------------

// encodeFrameHeader encodes the given frame header, writing to w.
func (enc *Encoder) encodeFrameHeader(w io.Writer, hdr frame.Header) error {
	// Create a new CRC-8 hash writer which adds the data from all write
	// operations to a running hash.
	h := crc8.NewATM()
	hw := io.MultiWriter(h, w)
	bw := bitio.NewWriter(hw)
	enc.c = bw

	//  Sync code: 11111111111110
	if err := bw.WriteBits(0x3FFE, 14); err != nil {
		return errutil.Err(err)
	}

	// Reserved: 0
	if err := bw.WriteBits(0x0, 1); err != nil {
		return errutil.Err(err)
	}

	// Blocking strategy:
	//    0 : fixed-blocksize stream; frame header encodes the frame number
	//    1 : variable-blocksize stream; frame header encodes the sample number
	if err := bw.WriteBool(!hdr.HasFixedBlockSize); err != nil {
		return errutil.Err(err)
	}

	// Encode block size.
	nblockSizeSuffixBits, err := encodeFrameHeaderBlockSize(bw, hdr.BlockSize)
	if err != nil {
		return errutil.Err(err)
	}

	// Encode sample rate.
	sampleRateSuffixBits, nsampleRateSuffixBits, err := encodeFrameHeaderSampleRate(bw, hdr.SampleRate)
	if err != nil {
		return errutil.Err(err)
	}

	// Encode channels assignment.
	if err := encodeFrameHeaderChannels(bw, hdr.Channels); err != nil {
		return errutil.Err(err)
	}

	// Encode bits-per-sample.
	if err := encodeFrameHeaderBitsPerSample(bw, hdr.BitsPerSample); err != nil {
		return errutil.Err(err)
	}

	// Reserved: 0
	if err := bw.WriteBits(0x0, 1); err != nil {
		return errutil.Err(err)
	}

	//    if (variable blocksize)
	//       <8-56>:"UTF-8" coded sample number (decoded number is 36 bits)
	//    else
	//       <8-48>:"UTF-8" coded frame number (decoded number is 31 bits)
	if err := utf8.Encode(bw, hdr.Num); err != nil {
		return errutil.Err(err)
	}

	// Write block size after the frame header (used for uncommon block sizes).
	if nblockSizeSuffixBits > 0 {
		// 0110 : get 8 bit (blocksize-1) from end of header
		// 0111 : get 16 bit (blocksize-1) from end of header
		if err := bw.WriteBits(uint64(hdr.BlockSize-1), nblockSizeSuffixBits); err != nil {
			return errutil.Err(err)
		}
	}

	// Write sample rate after the frame header (used for uncommon sample rates).
	if nsampleRateSuffixBits > 0 {
		if err := bw.WriteBits(sampleRateSuffixBits, nsampleRateSuffixBits); err != nil {
			return errutil.Err(err)
		}
	}

	// Flush pending writes to frame header.
	if _, err := bw.Align(); err != nil {
		return errutil.Err(err)
	}

	// CRC-8 (polynomial = x^8 + x^2 + x^1 + x^0, initialized with 0) of
	// everything before the crc, including the sync code.
	crc := h.Sum8()
	if err := binary.Write(w, binary.BigEndian, crc); err != nil {
		return errutil.Err(err)
	}

	return nil
}

// ~~~ [ Block size ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// encodeFrameHeaderBlockSize encodes the block size of the frame header,
// writing to bw. It returns the number of bits used to store block size after
// the frame header.
func encodeFrameHeaderBlockSize(bw *bitio.Writer, blockSize uint16) (nblockSizeSuffixBits byte, err error) {
	// Block size in inter-channel samples:
	//    0000 : reserved
	//    0001 : 192 samples
	//    0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608
	//    0110 : get 8 bit (blocksize-1) from end of header
	//    0111 : get 16 bit (blocksize-1) from end of header
	//    1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768
	var bits uint64
	switch blockSize {
	case 192:
		// 0001
		bits = 0x1

	// 0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608
	case 576:
		bits = 0x2
	case 1152:
		bits = 0x3
	case 2304:
		bits = 0x4
	case 4608:
		bits = 0x5
	// 1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768
	case 256:
		bits = 0x8
	case 512:
		bits = 0x9
	case 1024:
		bits = 0xa
	case 2048:
		bits = 0xb
	case 4096:
		bits = 0xc
	case 8192:
		bits = 0xd
	case 16384:
		bits = 0xe
	case 32768:
		bits = 0xf
	default:
		if blockSize <= 256 {
			// 0110 : get 8 bit (blocksize-1) from end of header
			bits = 0x6
			nblockSizeSuffixBits = 8
		} else {
			// 0111 : get 16 bit (blocksize-1) from end of header
			bits = 0x7
			nblockSizeSuffixBits = 16
		}
	}
	if err := bw.WriteBits(bits, 4); err != nil {
		return 0, errutil.Err(err)
	}
	return nblockSizeSuffixBits, nil
}

// ~~~ [ Sample rate ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// encodeFrameHeaderSampleRate encodes the sample rate of the frame header,
// writing to bw. It returns the bits and the number of bits used to store
// sample rate after the frame header.
func encodeFrameHeaderSampleRate(bw *bitio.Writer, sampleRate uint32) (sampleRateSuffixBits uint64, nsampleRateSuffixBits byte, err error) {
	// Sample rate:
	//    0000 : get from STREAMINFO metadata block
	//    0001 : 88.2kHz
	//    0010 : 176.4kHz
	//    0011 : 192kHz
	//    0100 : 8kHz
	//    0101 : 16kHz
	//    0110 : 22.05kHz
	//    0111 : 24kHz
	//    1000 : 32kHz
	//    1001 : 44.1kHz
	//    1010 : 48kHz
	//    1011 : 96kHz
	//    1100 : get 8 bit sample rate (in kHz) from end of header
	//    1101 : get 16 bit sample rate (in Hz) from end of header
	//    1110 : get 16 bit sample rate (in tens of Hz) from end of header
	//    1111 : invalid, to prevent sync-fooling string of 1s
	var bits uint64
	switch sampleRate {
	case 0:
		// 0000 : get from STREAMINFO metadata block
		bits = 0
	case 88200:
		// 0001 : 88.2kHz
		bits = 0x1
	case 176400:
		// 0010 : 176.4kHz
		bits = 0x2
	case 192000:
		// 0011 : 192kHz
		bits = 0x3
	case 8000:
		// 0100 : 8kHz
		bits = 0x4
	case 16000:
		// 0101 : 16kHz
		bits = 0x5
	case 22050:
		// 0110 : 22.05kHz
		bits = 0x6
	case 24000:
		// 0111 : 24kHz
		bits = 0x7
	case 32000:
		// 1000 : 32kHz
		bits = 0x8
	case 44100:
		// 1001 : 44.1kHz
		bits = 0x9
	case 48000:
		// 1010 : 48kHz
		bits = 0xA
	case 96000:
		// 1011 : 96kHz
		bits = 0xB
	default:
		switch {
		case sampleRate <= 255000 && sampleRate%1000 == 0:
			// 1100 : get 8 bit sample rate (in kHz) from end of header
			bits = 0xC
			sampleRateSuffixBits = uint64(sampleRate / 1000)
			nsampleRateSuffixBits = 8
		case sampleRate <= 65535:
			// 1101 : get 16 bit sample rate (in Hz) from end of header
			bits = 0xD
			sampleRateSuffixBits = uint64(sampleRate)
			nsampleRateSuffixBits = 16
		case sampleRate <= 655350 && sampleRate%10 == 0:
			// 1110 : get 16 bit sample rate (in tens of Hz) from end of header
			bits = 0xE
			sampleRateSuffixBits = uint64(sampleRate / 10)
			nsampleRateSuffixBits = 16
		default:
			return 0, 0, errutil.Newf("unable to encode sample rate %v", sampleRate)
		}
	}
	if err := bw.WriteBits(bits, 4); err != nil {
		return 0, 0, errutil.Err(err)
	}
	return sampleRateSuffixBits, nsampleRateSuffixBits, nil
}

// ~~~ [ Channels assignment ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// encodeFrameHeaderChannels encodes the channels assignment of the frame
// header, writing to bw.
func encodeFrameHeaderChannels(bw *bitio.Writer, channels frame.Channels) error {
	// Channel assignment.
	//    0000-0111 : (number of independent channels)-1. Where defined, the channel order follows SMPTE/ITU-R recommendations. The assignments are as follows:
	//        1 channel: mono
	//        2 channels: left, right
	//        3 channels: left, right, center
	//        4 channels: front left, front right, back left, back right
	//        5 channels: front left, front right, front center, back/surround left, back/surround right
	//        6 channels: front left, front right, front center, LFE, back/surround left, back/surround right
	//        7 channels: front left, front right, front center, LFE, back center, side left, side right
	//        8 channels: front left, front right, front center, LFE, back left, back right, side left, side right
	//    1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel
	//    1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel
	//    1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel
	//    1011-1111 : reserved
	var bits uint64
	switch channels {
	case frame.ChannelsMono, frame.ChannelsLR, frame.ChannelsLRC, frame.ChannelsLRLsRs, frame.ChannelsLRCLsRs, frame.ChannelsLRCLfeLsRs, frame.ChannelsLRCLfeCsSlSr, frame.ChannelsLRCLfeLsRsSlSr:
		// 1 channel: mono.
		// 2 channels: left, right.
		// 3 channels: left, right, center.
		// 4 channels: left, right, left surround, right surround.
		// 5 channels: left, right, center, left surround, right surround.
		// 6 channels: left, right, center, LFE, left surround, right surround.
		// 7 channels: left, right, center, LFE, center surround, side left, side right.
		// 8 channels: left, right, center, LFE, left surround, right surround, side left, side right.
		bits = uint64(channels.Count() - 1)
	case frame.ChannelsLeftSide:
		// 2 channels: left, side; using inter-channel decorrelation.
		// 1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel
		bits = 0x8
	case frame.ChannelsSideRight:
		// 2 channels: side, right; using inter-channel decorrelation.
		// 1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel
		bits = 0x9
	case frame.ChannelsMidSide:
		// 2 channels: mid, side; using inter-channel decorrelation.
		// 1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel
		bits = 0xA
	default:
		return errutil.Newf("support for channel assignment %v not yet implemented", channels)
	}
	if err := bw.WriteBits(bits, 4); err != nil {
		return errutil.Err(err)
	}
	return nil
}

// ~~~ [ Bits-per-sample ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// encodeFrameHeaderBitsPerSample encodes the bits-per-sample of the frame
// header, writing to bw.
func encodeFrameHeaderBitsPerSample(bw *bitio.Writer, bps uint8) error {
	// Sample size in bits:
	//    000 : get from STREAMINFO metadata block
	//    001 : 8 bits per sample
	//    010 : 12 bits per sample
	//    011 : reserved
	//    100 : 16 bits per sample
	//    101 : 20 bits per sample
	//    110 : 24 bits per sample
	//    111 : reserved
	var bits uint64
	switch bps {
	case 0:
		// 000 : get from STREAMINFO metadata block
		bits = 0x0
	case 8:
		// 001 : 8 bits per sample
		bits = 0x1
	case 12:
		// 010 : 12 bits per sample
		bits = 0x2
	case 16:
		// 100 : 16 bits per sample
		bits = 0x4
	case 20:
		// 101 : 20 bits per sample
		bits = 0x5
	case 24:
		// 110 : 24 bits per sample
		bits = 0x6
	default:
		return errutil.Newf("support for sample size %v not yet implemented", bps)
	}
	if err := bw.WriteBits(bits, 3); err != nil {
		return errutil.Err(err)
	}
	return nil
}
