#include <algorithm>

#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Log.h"
#include "Core/MemMapHelpers.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/System.h"
#include "Core/HLE/AtracCtx2.h"
#include "Core/Util/AtracTrack.h"
#include "Core/HW/Atrac3Standalone.h"
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "Common/File/FileUtil.h"

// Convenient command line:
// Windows\x64\debug\PPSSPPHeadless.exe  --root pspautotests/tests/../ -o --compare --new-atrac --timeout=30 --graphics=software pspautotests/tests/audio/atrac/stream.prx
//
// See the big comment in sceAtrac.cpp for an overview of the different modes of operation.

// To run on the real PSP, without gentest.py:
// cmd1> C:\dev\ppsspp\pspautotests\tests\audio\atrac> make
// cmd2> C:\dev\ppsspp\pspautotests> usbhostfs_pc -b 4000
// cmd3> C:\dev\ppsspp\pspautotests> pspsh -p 4000
// cmd3> > cd host0:/test/audio/atrac
// cmd3> stream.prx
// cmd1> C:\dev\ppsspp\pspautotests\tests\audio\atrac>copy /Y ..\..\..\__testoutput.txt stream.expected
// Then run the test, see above.

struct AT3BitrateMeta {
	u16 sampleSize;
	u8 dataByte;
	u8 jointStereo;  // I think?
};

static const AT3BitrateMeta g_at3BitrateMeta[5] = {
	{0x180, 0x04, 0},
	{0x130, 0x06, 0},
	{0x0C0, 0x0B, 1},
	{0x0C0, 0x0E, 0},
	{0x098, 0x0F, 0},
};

// Needs to support negative numbers, and to handle non-powers-of-two.
static int RoundDownToMultiple(int size, int grain) {
	return size - (size % grain);
}

static int RoundDownToMultipleWithOffset(int offset, int size, int grain) {
	if (size > offset) {
		return ((size - offset) / grain) * grain + offset;
	} else {
		return size;
	}
}

static int ComputeSkipFrames(const SceAtracIdInfo &info, int seekPos) {
	// No idea why this is the rule, but this is the rule.
	return (seekPos & info.SamplesFrameMask()) < info.SkipSamples() ? 2 : 1;
}

static int ComputeFileOffset(const SceAtracIdInfo &info, int seekPos) {
	int frameOffset = ((seekPos / info.SamplesPerFrame()) - 1) * info.sampleSize;
	if ((seekPos & info.SamplesFrameMask()) < info.SkipSamples() && (frameOffset != 0)) {
		frameOffset -= info.sampleSize;
	}
	return frameOffset + info.dataOff;
}

// Unlike the above, this one need to be inclusive.
static int ComputeLoopEndFileOffset(const SceAtracIdInfo &info, int seekPos) {
	return (seekPos / info.SamplesPerFrame() + 1) * info.sampleSize + info.dataOff;
}

static int ComputeSpaceUsed(const SceAtracIdInfo &info) {
	// The odd case: If streaming from the second buffer, and we're past the loop end (we're in the tail)...
	if (info.decodePos > info.loopEnd && info.curBuffer == 1) {
		int space = info.secondBufferByte;
		if (info.secondStreamOff < space) {
			space = RoundDownToMultipleWithOffset(info.secondStreamOff, info.secondBufferByte, info.sampleSize);
		}
		if ((info.secondStreamOff <= space) && (space - info.secondStreamOff < info.streamDataByte)) {
			return info.streamDataByte - (space - info.secondStreamOff);
		}
		return 0;
	}

	// The normal case.
	return info.streamDataByte;
}

static int ComputeRemainFrameStream(const SceAtracIdInfo &info) {
	if (info.streamDataByte >= info.fileDataEnd - info.curFileOff) {
		// Already done.
		return PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY;
	}
	// Since we're streaming, the remaining frames are what's valid in the buffer.
	return std::max(0, info.streamDataByte / info.sampleSize - (int)info.numSkipFrames);
}

// This got so complicated!
static int ComputeRemainFrameLooped(const SceAtracIdInfo &info) {
	const int loopStartFileOffset = ComputeFileOffset(info, info.loopStart);
	const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd);
	const int writeFileOff = info.curFileOff + info.streamDataByte;
	const int leftToRead = writeFileOff - loopEndFileOffset;

	int remainFrames;
	if (writeFileOff <= loopEndFileOffset) {
		// Simple case - just divide to find the number of frames remaining in the buffer.
		remainFrames = info.streamDataByte / info.sampleSize;
	} else {
		// Darn, we need to take looping into account...
		const int skipFramesAtLoopStart = ComputeSkipFrames(info, info.loopStart);
		const int firstPartLength = loopEndFileOffset - loopStartFileOffset;
		const int secondPartLength = leftToRead % firstPartLength;
		// Sum up all the parts (the buffered, the space remaining before the loop point
		// and the space after the loop point), each divided by sample size, need to take skipped frames into account.
		remainFrames = (loopEndFileOffset - info.curFileOff) / info.sampleSize +
			(leftToRead / firstPartLength) * (firstPartLength / info.sampleSize - skipFramesAtLoopStart);
		if (secondPartLength > skipFramesAtLoopStart * info.sampleSize) {
			remainFrames += secondPartLength / info.sampleSize - skipFramesAtLoopStart;
		}
	}

	// Clamp to zero.
	remainFrames = std::max(0, remainFrames - (int)info.numSkipFrames);
	if (info.loopNum < 0) {
		// Infinite looping while streaming, we never return that we're done reading data.
		return remainFrames;
	}

	// Additional check for distance to end of playback if we're looping a finite amount of times.
	const int streamBufferEndFileOffset = info.curFileOff + info.streamDataByte;
	if (streamBufferEndFileOffset >= loopEndFileOffset) {
		const int numBufferedLoops = (streamBufferEndFileOffset - loopEndFileOffset) / (loopEndFileOffset - loopStartFileOffset);
		if (info.loopNum <= numBufferedLoops) {
			return PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY;
		}
	}
	return remainFrames;
}

static void InitLengthAndLoop(SceAtracIdInfo *ctx, int endSample, int waveDataSize, int firstSampleOffset, int loopBegin, int loopEnd) {
	const int off = ctx->codec == 0x1001 ? 0x45 : 0x170;
	const int blockShift = 0x100b - ctx->codec;
	const int firstValidSample = firstSampleOffset + off;
	int numSamplesInFile;
	if (endSample == 0) {
		numSamplesInFile = waveDataSize / ctx->sampleSize << (blockShift & 0x1f);
	} else {
		numSamplesInFile = endSample + firstValidSample;
	}
	ctx->decodePos = firstValidSample;
	ctx->loopNum = 0;
	ctx->endSample = numSamplesInFile - 1;
	ctx->numSkipFrames = (char)(firstValidSample >> (blockShift & 0x1f));
	if (-1 < loopBegin) {
		ctx->loopEnd = loopEnd + off;
		ctx->loopStart = loopBegin + off;
		return;
	}
	ctx->loopEnd = 0;
	ctx->loopStart = 0;
	return;
}

static int ComputeAtracStateAndInitSecondBuffer(SceAtracIdInfo *info, u32 readSize, u32 bufferSize) {
	AtracStatus state;
	int loopEndFileOffset;
	int loopEnd;

	if (bufferSize < (u32)info->fileDataEnd) {
		if (info->streamDataByte < (s32)info->sampleSize * 2) {
			return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
		}
		loopEnd = info->loopEnd;
		state = ATRAC_STATUS_STREAMED_WITHOUT_LOOP;
		if ((loopEnd != 0) && (state = ATRAC_STATUS_STREAMED_LOOP_FROM_END, loopEnd != info->endSample)) {
			loopEndFileOffset = ComputeLoopEndFileOffset(*info, loopEnd);
			loopEnd = (loopEndFileOffset - info->dataOff) + 1;
			info->state = ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
			if (loopEnd < info->streamDataByte) {
				info->streamDataByte = loopEnd;
			}
			info->secondStreamOff = 0;
			info->secondBuffer = 0;
			info->secondBufferByte = 0;
			return 0;
		}
	} else {
		state = ATRAC_STATUS_HALFWAY_BUFFER;
		if (readSize >= (u32)info->fileDataEnd) {
			state = ATRAC_STATUS_ALL_DATA_LOADED;
		}
	}
	info->state = state;
	return 0;
}

int InitContextFromTrackInfo(SceAtracContext *ctx, const TrackInfo *wave, u32 bufferAddr, int readSize, int bufferSize) {
	(ctx->info).numChan = (char)wave->numChans;
	const int extraSamples = (ctx->info).codec == 0x1001 ? 0x45 : 0x170;
	(ctx->info).firstValidSample = extraSamples + wave->firstSampleOffset;
	int endSample3 = wave->endSample;
	(ctx->info).sampleSize = wave->blockAlign;
	InitLengthAndLoop(&ctx->info, endSample3, wave->waveDataSize, wave->firstSampleOffset, wave->loopStart, wave->loopEnd);
	const int dataOff = wave->dataOff;
	const int endSample = (ctx->info).endSample;
	(ctx->info).streamDataByte = readSize - dataOff;
	(ctx->info).buffer = bufferAddr;
	(ctx->info).curFileOff = dataOff;
	(ctx->info).dataOff = dataOff;
	(ctx->info).fileDataEnd = wave->waveDataSize + dataOff;
	(ctx->info).curBuffer = 0;
	(ctx->info).bufferByte = bufferSize;
	(ctx->info).streamOff = dataOff;
	if ((ctx->info).loopEnd > endSample) {
		return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
	}

	const int numChunks = (endSample >> ((0x100b - (ctx->info).codec) & 0x1f));

	if ((numChunks * (u32)(ctx->info).sampleSize < (wave->waveDataSize + dataOff) - dataOff)) {
		int retval = ComputeAtracStateAndInitSecondBuffer(&ctx->info, readSize, bufferSize);
		if (retval < 0) {
			return retval;
		}
		if ((ctx->info).codec != PSP_CODEC_AT3) {
			// At3plus
			// Configure the codec for the sample size, or whatever that data is.
			(ctx->codec).unk40 = wave->sampleSizeMaybe;
			(ctx->codec).unk48 = 0;
			(ctx->codec).unk41 = wave->tailFlag;
			return 0;
		}
		// At3. Set up the hardware codec (hopefully we can correctly support this in sceAudiocodec and thus sceAtrac LLE in the future)
		// This is not actually necessary since we don't use the actual hardware codec.
		for (int counter = 4; counter >= 0; counter--) {
			if ((g_at3BitrateMeta[counter].sampleSize == (ctx->info).sampleSize) &&
				((int)g_at3BitrateMeta[counter].dataByte == wave->sampleSizeMaybe)) {
				(ctx->codec).unk40 = (char)g_at3BitrateMeta[counter].jointStereo;
				(ctx->codec).unk41 = 0;
				(ctx->codec).unk42 = 0;
				(ctx->codec).unk43 = 0;
				return 0;
			}
		}
		return 0;
	} else {
		return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
	}
}

int Atrac2::RemainingFrames() const {
	const SceAtracIdInfo &info = context_->info;

	// Handle the easy cases first.
	switch (info.state) {
	case ATRAC_STATUS_UNINITIALIZED:
	case ATRAC_STATUS_NO_DATA:
		return 0;
	case ATRAC_STATUS_ALL_DATA_LOADED:
		return PSP_ATRAC_ALLDATA_IS_ON_MEMORY;  // Not sure about no data.
	case ATRAC_STATUS_HALFWAY_BUFFER:
	{
		// Pretty simple - compute the remaining space, and divide by the sample size, adjusting for frames-to-skip.
		const int writeFileOff = info.dataOff + info.streamDataByte;
		if (info.curFileOff < writeFileOff) {
			return std::max(0, (writeFileOff - info.curFileOff) / info.sampleSize - (int)info.numSkipFrames);
		}
		return 0;
	}
	case ATRAC_STATUS_STREAMED_WITHOUT_LOOP:
		return ComputeRemainFrameStream(info);

	case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
		return ComputeRemainFrameLooped(info);

	case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER:
		if (info.decodePos <= info.loopEnd) {
			// If before the tail, just treat it as looped.
			return ComputeRemainFrameLooped(info);
		} else {
			// If in tail, treat is as unlooped.
			return ComputeRemainFrameStream(info);
		}
		break;
	default:
		return SCE_ERROR_ATRAC_BAD_ATRACID;
	}
}

Atrac2::Atrac2(u32 contextAddr, int codecType) {
	if (contextAddr) {
		context_ = PSPPointer<SceAtracContext>::Create(contextAddr);
		// First-time initialization. The rest is initialized in SetData.
		SceAtracIdInfo &info = context_->info;
		info.codec = codecType;
		info.state = ATRAC_STATUS_NO_DATA;
		info.curBuffer = 0;

		sas_.streamOffset = 0;
		sas_.bufPtr[0] = 0;
		sas_.bufPtr[1] = 0;
	} else {
		// We're loading state, we'll restore the context in DoState.
	}
}

Atrac2::~Atrac2() {
	DumpBufferToFile();
	delete[] decodeTemp_;
	// Nothing else to do here, the context is freed by the HLE.
}

void Atrac2::DumpBufferToFile() {
	if (dumped_) {
		// Already dumped, no need to dump again.
		return;
	}
	if (!dumpBuffer_.empty()) {
		std::string filename = StringFromFormat("atrac3_%08x_incomplete.at3", context_->info.endSample);
		DumpFileIfEnabled(dumpBuffer_.data(), (u32)dumpBuffer_.size(), filename, DumpFileType::Atrac3);
		dumpBuffer_.clear();
	}
	dumped_ = true;
}

void Atrac2::DoState(PointerWrap &p) {
	auto s = p.Section("Atrac2", 1, 3);
	if (!s)
		return;

	Do(p, outputChannels_);
	// The only thing we need to save now is the outputChannels_ and the context pointer. And technically, not even that since
	// it can be computed. Still, for future proofing, let's save it.
	Do(p, context_);

	// Actually, now we also need to save sas state. I guess this could also be saved on the Sas side, but this is easier.
	if (s >= 2) {
		Do(p, sas_.streamOffset);
		Do(p, sas_.bufPtr[0]);
	}
	// Added support for streaming sas audio, need some more context state.
	if (s >= 3) {
		Do(p, sas_.bufPtr[1]);
		Do(p, sas_.bufSize[0]);
		Do(p, sas_.bufSize[1]);
		Do(p, sas_.isStreaming);
		Do(p, sas_.curBuffer);
		Do(p, sas_.fileOffset);
	}

	const SceAtracIdInfo &info = context_->info;
	if (p.mode == p.MODE_READ && info.state != ATRAC_STATUS_NO_DATA) {
		CreateDecoder(info.codec, info.sampleSize, info.numChan);
	}
}

bool Atrac2::HasSecondBuffer() const {
	const SceAtracIdInfo &info = context_->info;
	return info.secondBufferByte != 0;
}

int Atrac2::GetSoundSample(int *outEndSample, int *outLoopStartSample, int *outLoopEndSample) const {
	const SceAtracIdInfo &info = context_->info;
	*outEndSample = info.endSample - info.firstValidSample;
	int loopEnd = -1;
	if (info.loopEnd == 0) {
		*outLoopStartSample = -1;
		*outLoopEndSample = -1;
	} else {
		*outLoopStartSample = info.loopStart - info.firstValidSample;
		*outLoopEndSample = info.loopEnd - info.firstValidSample;
	}
	return 0;
}

int Atrac2::ResetPlayPosition(int seekPos, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) {
	*delay = false;

	// This was mostly copied straight from the old impl.
	SceAtracIdInfo &info = context_->info;
	if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.secondBufferByte == 0) {
		return SCE_ERROR_ATRAC_SECOND_BUFFER_NEEDED;
	}

	seekPos += info.firstValidSample;

	if ((u32)seekPos > (u32)info.endSample) {
		return SCE_ERROR_ATRAC_BAD_SAMPLE;
	}

	int result = ResetPlayPositionInternal(seekPos, bytesWrittenFirstBuf, bytesWrittenSecondBuf);
	if (result >= 0) {
		int skipCount = 0;
		result = SkipFrames(&skipCount);
		if (skipCount) {
			*delay = true;
		}
	}
	return result;
}

u32 Atrac2::ResetPlayPositionInternal(int seekPos, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf) {
	// Redo the same calculation as before, for input validation.

	AtracResetBufferInfo bufferInfo;
	GetResetBufferInfoInternal(&bufferInfo, seekPos);

	// Input validation.
	if ((u32)bytesWrittenFirstBuf < bufferInfo.first.minWriteBytes || (u32)bytesWrittenFirstBuf > bufferInfo.first.writableBytes) {
		return SCE_ERROR_ATRAC_BAD_FIRST_RESET_SIZE;
	}
	if ((u32)bytesWrittenSecondBuf < bufferInfo.second.minWriteBytes || (u32)bytesWrittenSecondBuf > bufferInfo.second.writableBytes) {
		return SCE_ERROR_ATRAC_BAD_SECOND_RESET_SIZE;
	}

	SceAtracIdInfo &info = context_->info;
	info.decodePos = seekPos;
	info.numSkipFrames = ComputeSkipFrames(info, seekPos);
	info.loopNum = 0;
	info.curFileOff = ComputeFileOffset(info, seekPos);

	context_->codec.err = 0x20b; // wtf? testing shows it.

	switch (info.state) {
	case ATRAC_STATUS_ALL_DATA_LOADED:
		// We're done.
		return 0;

	case ATRAC_STATUS_HALFWAY_BUFFER:
		info.streamDataByte += bytesWrittenFirstBuf;
		if (info.dataOff + info.streamDataByte >= info.fileDataEnd) {
			// Buffer full, we can transition to a full buffer here, if all the bytes were written. Let's do it.
			info.state = ATRAC_STATUS_ALL_DATA_LOADED;
		}
		return 0;

	case ATRAC_STATUS_STREAMED_WITHOUT_LOOP:
	case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
		// We just adopt the bytes that were written as our stream data, no math needed.
		info.streamDataByte = bytesWrittenFirstBuf;
		info.curBuffer = 0;
		info.streamOff = 0;
		return 0;

	case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER:
	{
		// As usual with the second buffer and trailer, things get tricky here.
		const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd);
		if (info.curFileOff >= loopEndFileOffset) {
			const int secondBufferSizeRounded = RoundDownToMultiple(info.secondBufferByte, info.sampleSize);
			if (info.curFileOff < loopEndFileOffset + secondBufferSizeRounded) {
				info.streamDataByte = ((loopEndFileOffset + secondBufferSizeRounded) - info.curFileOff) + bytesWrittenFirstBuf;
				info.curBuffer = 1;
				info.secondStreamOff = info.curFileOff - loopEndFileOffset;
			} else {
				info.streamDataByte = bytesWrittenFirstBuf;
				info.curBuffer = 2;  // Temporary value! Will immediately switch back to 0.
				info.streamOff = 0;
			}
		} else {
			info.streamDataByte = bytesWrittenFirstBuf;
			info.curBuffer = 0;
			info.streamOff = 0;
		}
		return 0;
	}
	default:
		_dbg_assert_(false);
		return 0;
	}
}

// This is basically sceAtracGetBufferInfoForResetting.
// NOTE: Not const! This can cause SkipFrames!
int Atrac2::GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int seekPos, bool *delay) {
	const SceAtracIdInfo &info = context_->info;

	if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.secondBufferByte == 0) {
		return SCE_ERROR_ATRAC_SECOND_BUFFER_NEEDED;
	}

	seekPos += info.firstValidSample;

	if ((u32)seekPos > (u32)info.endSample) {
		return SCE_ERROR_ATRAC_BAD_SAMPLE;
	}

	GetResetBufferInfoInternal(bufferInfo, seekPos);
	// Yes, this happens here! If there are any frames to skip, they get skipped!
	// Even though this looks like a function that shouldn't change the state.
	int skipCount = 0;
	int retval = SkipFrames(&skipCount);
	if (skipCount > 0)
		*delay = true;
	return retval;
}

void Atrac2::GetResetBufferInfoInternal(AtracResetBufferInfo *bufferInfo, int seekPos) const {
	const SceAtracIdInfo &info = context_->info;

	switch (info.state) {
	case ATRAC_STATUS_NO_DATA:
	case ATRAC_STATUS_ALL_DATA_LOADED:
		// Everything is loaded, so nothing needs to be read.
		bufferInfo->first.writePosPtr = info.buffer;
		bufferInfo->first.writableBytes = 0;
		bufferInfo->first.minWriteBytes = 0;
		bufferInfo->first.filePos = 0;
		break;
	case ATRAC_STATUS_HALFWAY_BUFFER:
	{
		// Not too hard, we just ask to fill up the missing part of the buffer.
		const int streamPos = info.dataOff + info.streamDataByte;
		const int fileOff = info.dataOff + (seekPos / info.SamplesPerFrame() + 1) * info.sampleSize;
		bufferInfo->first.writePosPtr = info.buffer + streamPos;
		bufferInfo->first.writableBytes = info.fileDataEnd - streamPos;
		bufferInfo->first.filePos = streamPos;
		bufferInfo->first.minWriteBytes = std::max(0, fileOff - streamPos);
		break;
	}

	case ATRAC_STATUS_STREAMED_WITHOUT_LOOP:
	case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
	{
		// Relatively easy, just can't forget those skipped frames.
		const int curFileOffset = ComputeFileOffset(info, seekPos);
		const int bufferEnd = RoundDownToMultiple(info.bufferByte, info.sampleSize);
		bufferInfo->first.writePosPtr = info.buffer;
		bufferInfo->first.writableBytes = std::min(info.fileDataEnd - curFileOffset, bufferEnd);
		bufferInfo->first.minWriteBytes = (ComputeSkipFrames(info, seekPos) + 1) * info.sampleSize;
		bufferInfo->first.filePos = curFileOffset;
		break;
	}
	case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER:
	{
		// As usual, with the second buffer, things get crazy complicated...
		const int seekFileOffset = ComputeFileOffset(info, seekPos);
		const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd) - 1;
		const int bufferEnd = RoundDownToMultiple(info.bufferByte, info.sampleSize);
		const int skipBytes = (ComputeSkipFrames(info, seekPos) + 1) * info.sampleSize;
		const int secondBufferEnd = RoundDownToMultiple(info.secondBufferByte, info.sampleSize);
		if (seekFileOffset < loopEndFileOffset) {
			const int remainingBeforeLoop = (loopEndFileOffset - seekFileOffset) + 1;
			bufferInfo->first.writePosPtr = info.buffer;
			bufferInfo->first.writableBytes = std::min(bufferEnd, remainingBeforeLoop);
			bufferInfo->first.minWriteBytes = std::min(skipBytes, remainingBeforeLoop);
			bufferInfo->first.filePos = seekFileOffset;
		} else if (loopEndFileOffset + secondBufferEnd <= seekFileOffset) {
			bufferInfo->first.writePosPtr = info.buffer;
			bufferInfo->first.writableBytes = std::min(info.fileDataEnd - seekFileOffset, bufferEnd);
			bufferInfo->first.minWriteBytes = skipBytes;
			bufferInfo->first.filePos = seekFileOffset;
		} else if (loopEndFileOffset + (int)info.secondBufferByte + 1 < info.fileDataEnd) {
			const int endOffset = loopEndFileOffset + secondBufferEnd + 1;
			bufferInfo->first.writePosPtr = info.buffer;
			bufferInfo->first.writableBytes = std::min(info.fileDataEnd - endOffset, bufferEnd);
			bufferInfo->first.minWriteBytes = std::max(0, seekFileOffset + skipBytes - endOffset);
			bufferInfo->first.filePos = endOffset;
		} else {
			bufferInfo->first.writePosPtr = info.buffer;
			bufferInfo->first.writableBytes = 0;
			bufferInfo->first.minWriteBytes = 0;
			bufferInfo->first.filePos = 0;
		}
		break;
	}
	default:
		_dbg_assert_(false);
		break;
	}

	// Reset never needs a second buffer write, since the loop is in a fixed place.
	// It seems like second.writePosPtr is always the same as the first buffer's pos, weirdly (doesn't make sense).
	bufferInfo->second.writePosPtr = info.buffer;
	bufferInfo->second.writableBytes = 0;
	bufferInfo->second.minWriteBytes = 0;
	bufferInfo->second.filePos = 0;
}

int Atrac2::SetLoopNum(int loopNum) {
	SceAtracIdInfo &info = context_->info;
	if (info.loopEnd <= 0) {
		// File doesn't contain loop information, looping isn't allowed.
		return SCE_ERROR_ATRAC_NO_LOOP_INFORMATION;
	}
	// Just override the current loop counter.
	info.loopNum = loopNum;
	return 0;
}

int Atrac2::LoopNum() const {
	return context_->info.loopNum;
}

int Atrac2::LoopStatus() const {
	const SceAtracIdInfo &info = context_->info;
	if (info.loopEnd == 0) {
		// No loop available.
		return 0;
	} else if (info.loopNum != 0) {
		// We've got at least one loop to go.
		return 1;
	} else {
		// Return 1 if we haven't passed the loop point.
		return info.decodePos <= info.loopEnd ? 1 : 0;
	}
}

u32 Atrac2::GetNextSamples() {
	SceAtracIdInfo &info = context_->info;
	// TODO: Need to reformulate this.
	const int endOfCurrentFrame = info.decodePos | info.SamplesFrameMask();  // bit trick!
	const int remainder = std::max(0, endOfCurrentFrame - info.endSample);
	const int adjusted = (info.decodePos & info.SamplesFrameMask()) + remainder;
	return std::max(0, info.SamplesPerFrame() - adjusted);
}

int Atrac2::GetNextDecodePosition(int *pos) const {
	const SceAtracIdInfo &info = context_->info;
	// Check if we reached the end.
	if (info.decodePos > info.endSample) {
		return SCE_ERROR_ATRAC_ALL_DATA_DECODED;
	}
	// Check if remaining data in the file is smaller than a frame.
	if (info.fileDataEnd - info.curFileOff < info.sampleSize) {
		return SCE_ERROR_ATRAC_ALL_DATA_DECODED;
	}
	*pos = info.decodePos - info.firstValidSample;
	return 0;
}

int Atrac2::AddStreamData(u32 bytesToAdd) {
	SceAtracIdInfo &info = context_->info;

	// WARNING: bytesToAdd might not be sampleSize aligned, even though we return a sampleSize-aligned size
	// in GetStreamDataInfo, so other parts of the code still has to handle unaligned data amounts.
	if (info.state == ATRAC_STATUS_HALFWAY_BUFFER) {
		const int newFileOffset = info.streamDataByte + info.dataOff + bytesToAdd;
		if (newFileOffset == info.fileDataEnd) {
			info.state = ATRAC_STATUS_ALL_DATA_LOADED;
		} else if (newFileOffset > info.fileDataEnd) {
			return SCE_ERROR_ATRAC_ADD_DATA_IS_TOO_BIG;
		}
		info.streamDataByte += bytesToAdd;
	} else {
		// TODO: Check for SCE_ERROR_ATRAC_ADD_DATA_IS_TOO_BIG in the other modes too.
		info.streamDataByte += bytesToAdd;
	}
	return 0;
}

static int ComputeLoopedStreamWritableBytes(const SceAtracIdInfo &info, const int loopStartFileOffset, const u32 loopEndFileOffset) {
	const u32 writeOffset = info.curFileOff + info.streamDataByte;
	if (writeOffset >= loopEndFileOffset) {
		const int loopLength = loopEndFileOffset - loopStartFileOffset;
		return loopLength - ((info.curFileOff + info.streamDataByte) - loopEndFileOffset) % loopLength;
	} else {
		return loopEndFileOffset - writeOffset;
	}
}

static int IncrementAndLoop(int curOffset, int increment, int loopStart, int loopEnd) {
	const int sum = curOffset + increment;
	if (sum >= loopEnd) {
		return loopStart + (sum - loopEnd) % (loopEnd - loopStart);
	} else {
		return sum;
	}
}

static int WrapAroundRoundedBufferSize(int offset, int bufferSize, int addend, int grainSize) {
	bufferSize = RoundDownToMultipleWithOffset(offset, bufferSize, grainSize);
	const int sum = offset + addend;
	if (bufferSize <= sum) {
		return sum - bufferSize;
	} else {
		return sum;
	}
}

static void ComputeStreamBufferDataInfo(const SceAtracIdInfo &info, u32 *writePtr, u32 *bytesToWrite, u32 *readFileOffset) {
	// Streaming data info
	//
	// This really is the core logic of sceAtrac. Initially I had it quite complicated, but boiled it
	// all down to fairly simple logic. And then boiled it down further and fixed bugs.
	// And then had to make it WAY complicated again to support looping... Sigh.
	const u32 streamOff = info.curBuffer != 1 ? info.streamOff : 0;
	const int spaceUsed = ComputeSpaceUsed(info);
	const int spaceLeftAfterStreamOff = RoundDownToMultipleWithOffset(streamOff, info.bufferByte, info.sampleSize);
	const int streamPos = streamOff + spaceUsed;
	int spaceLeftInBuffer;
	if (streamPos >= spaceLeftAfterStreamOff) {
		spaceLeftInBuffer = spaceLeftAfterStreamOff - spaceUsed;
	} else {
		spaceLeftInBuffer = spaceLeftAfterStreamOff - streamPos;
	}
	const int loopStartFileOffset = ComputeFileOffset(info, info.loopStart);
	const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd);

	if (spaceLeftInBuffer < 0) {
		// Most likely, the file was truncated.
		WARN_LOG(Log::Atrac, "File corruption detected: spaceLeftInBuffer < 0: %d. Stumbling along.", spaceLeftInBuffer);
		spaceLeftInBuffer = 0;
	}

	switch (info.state) {
	case ATRAC_STATUS_STREAMED_WITHOUT_LOOP:
	{
		*bytesToWrite = std::clamp(info.fileDataEnd - (info.curFileOff + info.streamDataByte), 0, spaceLeftInBuffer);
		const int streamFileOff = info.curFileOff + info.streamDataByte;
		if (streamFileOff < info.fileDataEnd) {
			*readFileOffset = streamFileOff;
			*writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize);
		} else {
			*readFileOffset = 0;
			*writePtr = info.buffer;
		}
		break;
	}

	case ATRAC_STATUS_STREAMED_LOOP_FROM_END:
		*bytesToWrite = std::min(ComputeLoopedStreamWritableBytes(info, loopStartFileOffset, loopEndFileOffset), spaceLeftInBuffer);
		*readFileOffset = IncrementAndLoop(info.curFileOff, info.streamDataByte, loopStartFileOffset, loopEndFileOffset);
		*writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize);
		break;

	case ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER:
	{
		// Behaves like WITHOUT_LOOP or STREAMED_LOOP_FROM_END depending on the decode position.
		if (info.decodePos <= info.loopEnd) {
			*bytesToWrite = std::min(ComputeLoopedStreamWritableBytes(info, loopStartFileOffset, loopEndFileOffset), spaceLeftInBuffer);
			*readFileOffset = IncrementAndLoop(info.curFileOff, info.streamDataByte, loopStartFileOffset, loopEndFileOffset);
		} else {
			const int streamFileOff = info.curFileOff + info.streamDataByte;
			*bytesToWrite = std::clamp(info.fileDataEnd - streamFileOff, 0, spaceLeftInBuffer);
			if (streamFileOff < info.fileDataEnd) {
				*readFileOffset = streamFileOff;
			} else {
				*readFileOffset = 0;
			}
		}
		if (info.decodePos <= info.loopEnd || info.curBuffer != 1) {
			*writePtr = info.buffer + WrapAroundRoundedBufferSize(info.streamOff, info.bufferByte, info.streamDataByte, info.sampleSize);
		} else {
			*writePtr = info.buffer + WrapAroundRoundedBufferSize(0, info.bufferByte, spaceUsed, info.sampleSize);
		}
		break;
	}

	default:
		// unreachable
		_dbg_assert_(false);
		break;
	}
}

void Atrac2::GetStreamDataInfo(u32 *writePtr, u32 *bytesToWrite, u32 *readFileOffset) {
	const SceAtracIdInfo &info = context_->info;

	switch (info.state) {
	case ATRAC_STATUS_ALL_DATA_LOADED:
		// Nothing to do, the whole track is loaded already.
		*writePtr = info.buffer;
		*bytesToWrite = 0;
		*readFileOffset = 0;
		break;

	case ATRAC_STATUS_HALFWAY_BUFFER:
	{
		// This is both the file offset and the offset in the buffer, since it's direct mapped
		// in this mode (no wrapping or any other trickery).
		const int fileOffset = (int)info.dataOff + (int)info.streamDataByte;
		const int bytesLeftInFile = (int)info.fileDataEnd - fileOffset;
		// Just ask for the rest of the data. The game can supply as much of it as it wants at a time.
		*writePtr = info.buffer + fileOffset;
		*bytesToWrite = bytesLeftInFile;
		*readFileOffset = fileOffset;
		break;
	}

	default:
		ComputeStreamBufferDataInfo(info, writePtr, bytesToWrite, readFileOffset);
		break;
	}
}

u32 Atrac2::DecodeData(u8 *outbuf, u32 outbufAddr, int *SamplesNum, int *finish, int *remains) {
	SceAtracIdInfo &info = context_->info;

	const int tries = info.numSkipFrames + 1;
	for (int i = 0; i < tries; i++) {
		u32 result = DecodeInternal(outbufAddr, SamplesNum, finish);
		if (result != 0) {
			*SamplesNum = 0;
			return result;
		}
	}

	*remains = RemainingFrames();
	return 0;
}

u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
	SceAtracIdInfo &info = context_->info;

	// Check that there's enough data to decode.

	// Check for end of file.
	const int samplesToDecode = GetNextSamples();
	const int nextFileOff = info.curFileOff + info.sampleSize;
	if (nextFileOff > info.fileDataEnd || info.decodePos > info.endSample) {
		*finish = 1;
		return SCE_ERROR_ATRAC_ALL_DATA_DECODED;
	}

	DEBUG_LOG(Log::Atrac, "Decode(%08x): samplesToDecode: %d nextFileOff: %d", outbufAddr, samplesToDecode, nextFileOff);

	// Check for streaming buffer run-out.
	if (AtracStatusIsStreaming(info.state) && info.streamDataByte < info.sampleSize) {
		*finish = 0;
		return SCE_ERROR_ATRAC_BUFFER_IS_EMPTY;
	}

	// Check for halfway buffer end.
	if (info.state == ATRAC_STATUS_HALFWAY_BUFFER && info.dataOff + info.streamDataByte < nextFileOff) {
		*finish = 0;
		return SCE_ERROR_ATRAC_BUFFER_IS_EMPTY;
	}

	if (info.state == ATRAC_STATUS_FOR_SCESAS) {
		_dbg_assert_(false);
	}

	u32 streamOff;
	u32 bufferPtr;
	if (!AtracStatusIsStreaming(info.state)) {
		bufferPtr = info.buffer;
		streamOff = info.curFileOff;
	} else {
		const int bufferIndex = info.curBuffer & 1;
		bufferPtr = bufferIndex == 0 ? info.buffer : info.secondBuffer;
		streamOff = bufferIndex == 0 ? info.streamOff : info.secondStreamOff;
	}

	u32 inAddr = bufferPtr + streamOff;
	int16_t *outPtr;

	_dbg_assert_(samplesToDecode <= info.SamplesPerFrame());
	if (samplesToDecode != info.SamplesPerFrame()) {
		if (!decodeTemp_) {
			decodeTemp_ = new int16_t[info.SamplesPerFrame() * outputChannels_];
		}
		outPtr = decodeTemp_;
	} else {
		outPtr = outbufAddr ? (int16_t *)Memory::GetPointer(outbufAddr) : 0;  // outbufAddr can be 0 during skip!
	}

	context_->codec.inBuf = inAddr;
	context_->codec.outBuf = outbufAddr;

	if (!Memory::IsValidAddress(inAddr)) {
		ERROR_LOG(Log::Atrac, "DecodeInternal: Bad inAddr %08x", inAddr);
		return SCE_ERROR_ATRAC_API_FAIL;
	}

	int bytesConsumed = 0;
	int outSamples = 0;
	if (!decoder_->Decode(Memory::GetPointerUnchecked(inAddr), info.sampleSize, &bytesConsumed, outputChannels_, outPtr, &outSamples)) {
		// Decode failed.
		*finish = 0;
		// TODO: The error code here varies based on what the problem is, but not sure of the right values.
		// 0000020b and 0000020c have been observed for 0xFF and/or garbage data, there may be more codes.
		context_->codec.err = 0x20b;
		return SCE_ERROR_ATRAC_API_FAIL;  // tested.
	} else {
		context_->codec.err = 0;
	}

	_dbg_assert_(bytesConsumed == info.sampleSize);

	// Advance the file offset.
	info.curFileOff += info.sampleSize;

	if (info.numSkipFrames == 0) {
		*SamplesNum = samplesToDecode;
		if (info.endSample < info.decodePos + samplesToDecode) {
			*finish = info.loopNum == 0;
		} else {
			*finish = 0;
		}
		u8 *outBuf = outbufAddr ? Memory::GetPointerWrite(outbufAddr) : nullptr;
		if (samplesToDecode != info.SamplesPerFrame() && samplesToDecode != 0 && outBuf) {
			memcpy(outBuf, decodeTemp_, samplesToDecode * outputChannels_ * sizeof(int16_t));
		}

		// Handle increments and looping.
		info.decodePos += samplesToDecode;
		if (info.loopEnd != 0 && info.loopNum != 0 && info.decodePos > info.loopEnd) {
			info.curFileOff = ComputeFileOffset(info, info.loopStart);
			info.numSkipFrames = ComputeSkipFrames(info, info.loopStart);
			info.decodePos = info.loopStart;
			if (info.loopNum > 0) {
				info.loopNum--;
			}
		}
	} else {
		info.numSkipFrames--;
	}

	// Handle streaming special cases.
	if (AtracStatusIsStreaming(info.state)) {
		info.streamDataByte -= info.sampleSize;
		if (info.curBuffer == 1) {
			// If currently streaming from the second buffer...
			int nextStreamOff = info.secondStreamOff + info.sampleSize;
			if ((int)info.secondBufferByte < nextStreamOff + info.sampleSize) {
				// Done/ran out
				info.streamOff = 0;
				info.secondStreamOff = 0;
				info.curBuffer = 2;
			} else {
				info.secondStreamOff = nextStreamOff;
			}
		} else {
			// Normal streaming from the main buffer. Let's first look at wrapping around the end...
			const int nextStreamOff = info.streamOff + info.sampleSize;
			if (nextStreamOff + info.sampleSize > (int)info.bufferByte) {
				info.streamOff = 0;
			} else {
				info.streamOff = nextStreamOff;
			}

			// OK, that's the simple stuff done. Moving on to second buffer streaming...
			// This is quite a condition!
			// Basically, if we're in state LOOP_WITH_TRAILER and currently streaming from the main buffer,
			// and either there's no loop or we're just done with the final loop and haven't reached the loop point yet...
			if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER && info.curBuffer == 0 &&
				(info.loopEnd == 0 || (info.loopNum == 0 && info.loopEnd < info.decodePos))) {
				// If, at that point, our file streaming offset has indeed reached the loop point...
				if (info.curFileOff >= ComputeLoopEndFileOffset(info, info.loopEnd)) {
					// Then we switch to streaming from the secondary buffer, and also let's copy the last partial
					// packet from the second buffer back to the start of the main buffer...
					info.curBuffer = 1;
					info.streamDataByte = info.secondBufferByte;
					info.secondStreamOff = 0;
					memcpy(Memory::GetPointerWrite(info.buffer),
						Memory::GetPointer(info.secondBuffer + (info.secondBufferByte - info.secondBufferByte % info.sampleSize)),
						info.secondBufferByte % info.sampleSize);
				}
			}
		}
	}
	return 0;
}

int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 bufferSize, u32 fileSize, int outputChannels, bool isAA3) {
	_dbg_assert_(outputChannels == 1 || outputChannels == 2);
	TrackInfo trackInfo{};
	if (Memory::IsValidAddress(bufferAddr)) {
		// Turns out that games can abuse bufferSize, so we can't verify that it's a valid length with GetPointerRange.
		const u8 *bufferPtr = Memory::GetPointerUnchecked(bufferAddr);
		if (!Memory::IsValidRange(bufferAddr, readSize)) {
			WARN_LOG(Log::Atrac, "Atrac2::SetData: Bad buffer range %08x+%08x - however, proceeeding.", bufferAddr, readSize);
		}
		if (!isAA3) {
			int retval = ParseWaveAT3(bufferPtr, readSize, &trackInfo);
			if (retval < 0) {
				ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseWaveAT3 failed with %08x", retval);
				return retval;
			}
		} else {
			int retval = ParseAA3(bufferPtr, readSize, fileSize, &trackInfo);
			if (retval < 0) {
				ERROR_LOG(Log::Atrac, "Atrac2::SetData: ParseAA3 failed with %08x", retval);
				return retval;
			}
		}
	}

	int retval = InitContextFromTrackInfo(context_, &trackInfo, bufferAddr, readSize, bufferSize);
	if (retval < 0) {
		ERROR_LOG(Log::Atrac, "Atrac2::SetData: InitContextFromTrackInfo failed with %08x", retval);
		return retval;
	}

	SceAtracIdInfo &info = context_->info;

	CreateDecoder(info.codec, info.sampleSize, info.numChan);

	outputChannels_ = outputChannels;

	INFO_LOG(Log::Atrac,
		"Atrac: sampleSize: %d buffer: %08x bufferByte: %d firstValidSample: %d\n"
		"endSample: %d loopStart: %d loopEnd: %d\n"
		"dataOff: %d curFileOff: %d streamOff: %d streamDataByte: %d\n"
		"fileDataEnd: %d decodePos: %d numSkipFrames: %d channels: %d",
		info.sampleSize, info.buffer, info.bufferByte, info.firstValidSample,
		info.endSample, info.loopStart, info.loopEnd,
		info.dataOff, info.curFileOff, info.streamOff, info.streamDataByte,
		info.fileDataEnd, info.decodePos, info.numSkipFrames, info.numChan
	);

	int skipCount = 0;  // TODO: use for delay
	retval = SkipFrames(&skipCount);

	// Seen in Mui Mui house. Things go very wrong after this..
	if (retval == SCE_ERROR_ATRAC_API_FAIL) {
		ERROR_LOG(Log::Atrac, "Bad frame during initial skip");
	} else if (retval != 0) {
		ERROR_LOG(Log::Atrac, "SkipFrames during InitContext returned an error: %08x", retval);
	}
	WrapLastPacket();
	return retval;
}

void Atrac2::WrapLastPacket() {
	SceAtracIdInfo &info = context_->info;

	// If streaming, we need to handle wrapping the overshot partial packet at the end. If not we don't.
	if (!AtracStatusIsStreaming(info.state)) {
		return;
	}

	// This logic is similar to GetStreamDataInfo.
	int distanceToEnd = RoundDownToMultiple(info.bufferByte - info.streamOff, info.sampleSize);
	if (info.streamDataByte < distanceToEnd) {
		// There's space left without wrapping. Don't do anything.
		// INFO_LOG(Log::Atrac, "Packets fit into the buffer fully. %08x < %08x", readSize, bufferSize);
		// In this case, seems we need to zero some bytes. In one test, this seems to be 336.
		// Maybe there's a logical bug and the copy happens even when not needed, it's just that it'll
		// copy zeroes. Either way, let's just copy some bytes to make our sanity check hexdump pass.
		Memory::Memset(info.buffer, 0, 128);
	} else {
		// Wraps around.
		const int copyStart = info.streamOff + distanceToEnd;
		const int copyLen = info.bufferByte - copyStart;
		// Then, let's copy it.
		DEBUG_LOG(Log::Atrac, "Packets didn't fit evenly. Last packet got split into %d/%d (sum=%d). Copying to start of buffer.",
			copyLen, info.sampleSize - copyLen, info.sampleSize);
		Memory::Memcpy(info.buffer, info.buffer + copyStart, copyLen);
	}
}

u32 Atrac2::SkipFrames(int *skipCount) {
	SceAtracIdInfo &info = context_->info;
	*skipCount = 0;
	int finishIgnored;
	while (true) {
		if (info.numSkipFrames == 0) {
			return 0;
		}
		u32 retval = DecodeInternal(0, 0, &finishIgnored);
		if (retval != 0) {
			if (retval == SCE_ERROR_ATRAC_API_FAIL) {
				WARN_LOG(Log::Atrac, "Failed during skip-frame, ignoring: %08x", retval);
				(*skipCount)++;
			}
			return retval;
		} else {
			DEBUG_LOG(Log::Atrac, "Frame correctly decoded during skip. numSkipFrames == %d", info.numSkipFrames);
		}
		(*skipCount)++;
	}
	return 0;
}

// Where to read from to fill the second buffer.
int Atrac2::GetSecondBufferInfo(u32 *fileOffset, u32 *readSize) const {
	const SceAtracIdInfo &info = context_->info;
	if (info.state != ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) {
		// No second buffer needed in this state.
		*fileOffset = 0;
		*readSize = 0;
		return SCE_ERROR_ATRAC_SECOND_BUFFER_NOT_NEEDED;
	}

	const int loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd);
	*fileOffset = loopEndFileOffset;
	*readSize = info.fileDataEnd - loopEndFileOffset;
	return 0;
}

int Atrac2::SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) {
	SceAtracIdInfo &info = context_->info;

	u32 loopEndFileOffset = ComputeLoopEndFileOffset(info, info.loopEnd);
	if ((info.sampleSize * 3 <= (int)secondBufferSize ||
		(info.fileDataEnd - loopEndFileOffset) <= (int)secondBufferSize)) {
		if (info.state == ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) {
			info.secondBuffer = secondBuffer;
			info.secondBufferByte = secondBufferSize;
			info.secondStreamOff = 0;
			return 0;
		} else {
			return SCE_ERROR_ATRAC_SECOND_BUFFER_NOT_NEEDED;
		}
	}
	return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}

u32 Atrac2::GetInternalCodecError() const {
	if (context_.IsValid()) {
		return context_->codec.err;
	} else {
		return 0;
	}
}

int Atrac2::Bitrate() const {
	const SceAtracIdInfo &info = context_->info;
	return info.BitRate();
}

void Atrac2::InitLowLevel(const Atrac3LowLevelParams &params, int codecType) {
	SceAtracIdInfo &info = context_->info;
	info.codec = codecType;
	info.numChan = params.encodedChannels;
	outputChannels_ = params.outputChannels;
	info.sampleSize = params.bytesPerFrame;
	info.dataOff = 0;
	info.decodePos = 0;
	info.state = ATRAC_STATUS_LOW_LEVEL;
	CreateDecoder(codecType, info.sampleSize, info.numChan);
}

int Atrac2::DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) {
	SceAtracIdInfo &info = context_->info;

	const int channels = outputChannels_;
	int outSamples = 0;
	bool success = decoder_->Decode(srcData, info.sampleSize, bytesConsumed, channels, dstData, &outSamples);
	if (!success) {
		ERROR_LOG(Log::Atrac, "Low level decoding failed: sampleSize: %d bytesConsumed: %d", info.sampleSize, *bytesConsumed);
		// We proceed anyway, see issue #20452
		/*
		*bytesConsumed = 0;
		*bytesWritten = 0;
		return SCE_ERROR_ATRAC_API_FAIL;  // need to check what return value we get here.
		*/
	}
	*bytesWritten = outSamples * channels * sizeof(int16_t);
	// TODO: Possibly return a decode error on bad data.
	return 0;
}

void Atrac2::CheckForSas() {
	SceAtracIdInfo &info = context_->info;
	if (info.numChan != 1) {
		WARN_LOG(Log::Atrac, "Caller forgot to set channels to 1");
	}
	if (info.state != 0x10) {
		WARN_LOG(Log::Atrac, "Caller forgot to set state to 0x10");
	}
	sas_.isStreaming = info.fileDataEnd > (s32)info.bufferByte;
	if (sas_.isStreaming) {
		INFO_LOG(Log::Atrac, "SasAtrac stream mode");
	} else {
		INFO_LOG(Log::Atrac, "SasAtrac non-streaming mode");
	}
}

int Atrac2::EnqueueForSas(u32 address, u32 ptr) {
	SceAtracIdInfo &info = context_->info;
	// Set the new buffer up to be adopted by the next call to Decode that needs more data.
	// Note: Can't call this if the decoder isn't asking for another buffer to be queued.
	if (info.secondBuffer != 0xFFFFFFFF) {
		return SCE_SAS_ERROR_ATRAC3_ALREADY_QUEUED;
	}

	if (address == 0 && ptr == 0) {
		WARN_LOG(Log::Atrac, "Caller tries to send us a zero buffer. Something went wrong.");
	}

	DEBUG_LOG(Log::Atrac, "EnqueueForSas: Second buffer updated to %08x, sz: %08x", address, ptr);
	info.secondBuffer = address;
	info.secondBufferByte = ptr;
	return 0;
}

// Completely different streaming setup!
void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) {
	SceAtracIdInfo &info = context_->info;
	*bytesWritten = 0;

	// First frame handling. Not sure if accurate. Set up the initial buffer as the current streaming buffer.
	// Also works for the non-streaming case.
	if (info.buffer) {
		sas_.curBuffer = 0;
		sas_.bufPtr[0] = info.buffer;
		sas_.bufSize[0] = info.bufferByte - info.streamOff;  // also equals info.streamDataByte
		sas_.streamOffset = 0;
		sas_.fileOffset = info.bufferByte;  // Possibly should just set it to info.curFileOff
		info.buffer = 0;  // yes, this happens.
	}

	u8 assembly[1000];
	// Keep decoding from the current buffer until it runs out.
	if (sas_.streamOffset + (int)info.sampleSize <= (int)sas_.bufSize[sas_.curBuffer]) {
		// Just decode.
		const u8 *srcData = Memory::GetPointer(sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset);
		int bytesConsumed = 0;
		bool decodeResult = decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
		if (!decodeResult) {
			ERROR_LOG(Log::Atrac, "SAS failed to decode regular packet");
		}
		sas_.streamOffset += bytesConsumed;
	} else if (sas_.isStreaming) {
		// TODO: Do we need special handling for the first buffer, since SetData will wrap around that packet? I think yes!
		DEBUG_LOG(Log::Atrac, "Streaming atrac through sas, and hit the end of buffer %d", sas_.curBuffer);

		// Compute the part sizes using the current size.
		int part1Size = sas_.bufSize[sas_.curBuffer] - sas_.streamOffset;
		int part2Size = info.sampleSize - part1Size;
		_dbg_assert_(part1Size >= 0);
		if (part1Size >= 0) {
			// Grab the partial packet, before we switch over to the other buffer.
			Memory::Memcpy(assembly, sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset, part1Size);
		}

		// Check if we hit the end.
		if (sas_.fileOffset >= info.fileDataEnd) {
			DEBUG_LOG(Log::Atrac, "Streaming and hit the file end.");
			*bytesWritten = 0;
			*finish = 1;
			return;
		}

		// Check that a new buffer actually exists
		if (info.secondBuffer == sas_.bufPtr[sas_.curBuffer]) {
			ERROR_LOG(Log::Atrac, "Can't enqueue the same buffer twice in a row!");
			*bytesWritten = 0;
			*finish = 1;
			return;
		}

		if ((int)info.secondBuffer < 0) {
			ERROR_LOG(Log::Atrac, "AtracSas streaming ran out of data, no secondbuffer pending");
			*bytesWritten = 0;
			*finish = 1;
			return;
		}

		// Switch to the other buffer.
		sas_.curBuffer ^= 1;

		sas_.bufPtr[sas_.curBuffer] = info.secondBuffer;
		sas_.bufSize[sas_.curBuffer] = info.secondBufferByte;
		sas_.fileOffset += info.secondBufferByte;

		sas_.streamOffset = part2Size;

		// If we'll reach the end during this buffer, set second buffer to 0, signaling that we don't need more data.
		if (sas_.fileOffset >= info.fileDataEnd) {
			// We've reached the end.
			info.secondBuffer = 0;
			DEBUG_LOG(Log::Atrac, "%08x >= %08x: Reached the end.", sas_.fileOffset, info.fileDataEnd);
		} else {
			// Signal to the caller that we accept a new next buffer.
			info.secondBuffer = 0xFFFFFFFF;
		}

		DEBUG_LOG(Log::Atrac, "Switching over to buffer %d, updating buffer to %08x, sz: %08x. %s", sas_.curBuffer, info.secondBuffer, info.secondBufferByte, info.secondBuffer == 0xFFFFFFFF ? "Signalling for more data." : "");

		// Copy the second half (or if part1Size == 0, the whole packet) to the assembly buffer.
		Memory::Memcpy(assembly + part1Size, sas_.bufPtr[sas_.curBuffer], part2Size);
		// Decode the packet from the assembly, whether it's was assembled from two or one.
		const u8 *srcData = assembly;
		int bytesConsumed = 0;
		bool decodeResult = decoder_->Decode(srcData, info.sampleSize, &bytesConsumed, 1, dstData, bytesWritten);
		if (!decodeResult) {
			ERROR_LOG(Log::Atrac, "SAS failed to decode assembled packet");
		}
	}
}
