/* 7zDec.c -- Decoding from 7z folder
2010-11-02 : Igor Pavlov : Public domain */

#include <string.h>

/* #define _7ZIP_PPMD_SUPPPORT */

#include "7z.h"

#include "Bcj2.h"
#include "Bra.h"
#include "CpuArch.h"
#include "LzmaDec.h"
#include "Lzma2Dec.h"
#ifdef _7ZIP_PPMD_SUPPPORT
#include "Ppmd7.h"
#endif

#define k_Copy 0
#define k_LZMA2 0x21
#define k_LZMA  0x30101
#define k_BCJ   0x03030103
#define k_PPC   0x03030205
#define k_ARM   0x03030501
#define k_ARMT  0x03030701
#define k_SPARC 0x03030805
#define k_BCJ2  0x0303011B

#ifdef _7ZIP_PPMD_SUPPPORT

#define k_PPMD 0x30401

typedef struct
{
	IByteIn p;
	const Byte *cur;
	const Byte *end;
	const Byte *begin;
	UInt64 processed;
	Bool extra;
	SRes res;
	ILookInStream *inStream;
} CByteInToLook;

static Byte ReadByte(void *pp)
{
	CByteInToLook *p = (CByteInToLook *)pp;
	if (p->cur != p->end)
		return *p->cur++;
	if (p->res == SZ_OK)
	{
		size_t size = p->cur - p->begin;
		p->processed += size;
		p->res = p->inStream->Skip(p->inStream, size);
		size = (1 << 25);
		p->res = p->inStream->Look(p->inStream, (const void **)&p->begin, &size);
		p->cur = p->begin;
		p->end = p->begin + size;
		if (size != 0)
			return *p->cur++;;
	}
	p->extra = True;
	return 0;
}

static SRes SzDecodePpmd(CSzCoderInfo *coder, UInt64 inSize, ILookInStream *inStream,
	Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
{
	CPpmd7 ppmd;
	CByteInToLook s;
	SRes res = SZ_OK;

	s.p.Read = ReadByte;
	s.inStream = inStream;
	s.begin = s.end = s.cur = NULL;
	s.extra = False;
	s.res = SZ_OK;
	s.processed = 0;

	if (coder->Props.size != 5)
		return SZ_ERROR_UNSUPPORTED;

	{
		unsigned order = coder->Props.data[0];
		UInt32 memSize = GetUi32(coder->Props.data + 1);
		if (order < PPMD7_MIN_ORDER ||
			order > PPMD7_MAX_ORDER ||
			memSize < PPMD7_MIN_MEM_SIZE ||
			memSize > PPMD7_MAX_MEM_SIZE)
			return SZ_ERROR_UNSUPPORTED;
		Ppmd7_Construct(&ppmd);
		if (!Ppmd7_Alloc(&ppmd, memSize, allocMain))
			return SZ_ERROR_MEM;
		Ppmd7_Init(&ppmd, order);
	}
	{
		CPpmd7z_RangeDec rc;
		Ppmd7z_RangeDec_CreateVTable(&rc);
		rc.Stream = &s.p;
		if (!Ppmd7z_RangeDec_Init(&rc))
			res = SZ_ERROR_DATA;
		else if (s.extra)
			res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
		else
		{
			SizeT i;
			for (i = 0; i < outSize; i++)
			{
				int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p);
				if (s.extra || sym < 0)
					break;
				outBuffer[i] = (Byte)sym;
			}
			if (i != outSize)
				res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
			else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc))
				res = SZ_ERROR_DATA;
		}
	}
	Ppmd7_Free(&ppmd, allocMain);
	return res;
}

#endif


static SRes SzDecodeLzma(CSzCoderInfo *coder, UInt64 inSize, ILookInStream *inStream, Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
{
	CLzmaDec state;
	SRes res = SZ_OK;

	LzmaDec_Construct(&state);
	RINOK(LzmaDec_AllocateProbs(&state, coder->Props.data, (unsigned)coder->Props.size, allocMain));
	state.dic = outBuffer;
	state.dicBufSize = outSize;
	LzmaDec_Init(&state);

	for (;;)
	{
		Byte *inBuf = NULL;
		size_t lookahead = (1 << 18);
		if (lookahead > inSize)
			lookahead = (size_t)inSize;
		res = inStream->Look((void *)inStream, (const void **)&inBuf, &lookahead);
		if (res != SZ_OK)
			break;

		{
			SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos;
			ELzmaStatus status;
			res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
			lookahead -= inProcessed;
			inSize -= inProcessed;
			if (res != SZ_OK)
				break;
			if (state.dicPos == state.dicBufSize || (inProcessed == 0 && dicPos == state.dicPos))
			{
				if (state.dicBufSize != outSize || lookahead != 0 ||
					(status != LZMA_STATUS_FINISHED_WITH_MARK && status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK))
					res = SZ_ERROR_DATA;
				break;
			}
			res = inStream->Skip((void *)inStream, inProcessed);
			if (res != SZ_OK)
				break;
		}
	}

	LzmaDec_FreeProbs(&state, allocMain);
	return res;
}

static SRes SzDecodeLzma2(CSzCoderInfo *coder, UInt64 inSize, ILookInStream *inStream,
	Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
{
	CLzma2Dec state;
	SRes res = SZ_OK;

	Lzma2Dec_Construct(&state);
	if (coder->Props.size != 1)
		return SZ_ERROR_DATA;
	RINOK(Lzma2Dec_AllocateProbs(&state, coder->Props.data[0], allocMain));
	state.decoder.dic = outBuffer;
	state.decoder.dicBufSize = outSize;
	Lzma2Dec_Init(&state);

	for (;;)
	{
		Byte *inBuf = NULL;
		size_t lookahead = (1 << 18);
		if (lookahead > inSize)
			lookahead = (size_t)inSize;
		res = inStream->Look((void *)inStream, (const void **)&inBuf, &lookahead);
		if (res != SZ_OK)
			break;

		{
			SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos;
			ELzmaStatus status;
			res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
			lookahead -= inProcessed;
			inSize -= inProcessed;
			if (res != SZ_OK)
				break;
			if (state.decoder.dicPos == state.decoder.dicBufSize || (inProcessed == 0 && dicPos == state.decoder.dicPos))
			{
				if (state.decoder.dicBufSize != outSize || lookahead != 0 ||
					(status != LZMA_STATUS_FINISHED_WITH_MARK))
					res = SZ_ERROR_DATA;
				break;
			}
			res = inStream->Skip((void *)inStream, inProcessed);
			if (res != SZ_OK)
				break;
		}
	}

	Lzma2Dec_FreeProbs(&state, allocMain);
	return res;
}

static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer)
{
	while (inSize > 0)
	{
		void *inBuf;
		size_t curSize = (1 << 18);
		if (curSize > inSize)
			curSize = (size_t)inSize;
		RINOK(inStream->Look((void *)inStream, (const void **)&inBuf, &curSize));
		if (curSize == 0)
			return SZ_ERROR_INPUT_EOF;
		memcpy(outBuffer, inBuf, curSize);
		outBuffer += curSize;
		inSize -= curSize;
		RINOK(inStream->Skip((void *)inStream, curSize));
	}
	return SZ_OK;
}

static Bool IS_MAIN_METHOD(UInt32 m)
{
	switch(m)
	{
	case k_Copy:
	case k_LZMA:
	case k_LZMA2:
#ifdef _7ZIP_PPMD_SUPPPORT
	case k_PPMD:
#endif
		return True;
	}
	return False;
}

static Bool IS_SUPPORTED_CODER(const CSzCoderInfo *c)
{
	return
		c->NumInStreams == 1 &&
		c->NumOutStreams == 1 &&
		c->MethodID <= (UInt32)0xFFFFFFFF &&
		IS_MAIN_METHOD((UInt32)c->MethodID);
}

#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumInStreams == 4 && (c)->NumOutStreams == 1)

static SRes CheckSupportedFolder(const CSzFolder *f)
{
	if (f->NumCoders < 1 || f->NumCoders > 4)
		return SZ_ERROR_UNSUPPORTED;
	if (!IS_SUPPORTED_CODER(&f->Coders[0]))
		return SZ_ERROR_UNSUPPORTED;
	if (f->NumCoders == 1)
	{
		if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBindPairs != 0)
			return SZ_ERROR_UNSUPPORTED;
		return SZ_OK;
	}
	if (f->NumCoders == 2)
	{
		CSzCoderInfo *c = &f->Coders[1];
		if (c->MethodID > (UInt32)0xFFFFFFFF ||
			c->NumInStreams != 1 ||
			c->NumOutStreams != 1 ||
			f->NumPackStreams != 1 ||
			f->PackStreams[0] != 0 ||
			f->NumBindPairs != 1 ||
			f->BindPairs[0].InIndex != 1 ||
			f->BindPairs[0].OutIndex != 0)
			return SZ_ERROR_UNSUPPORTED;
		switch ((UInt32)c->MethodID)
		{
		case k_BCJ:
		case k_ARM:
			break;
		default:
			return SZ_ERROR_UNSUPPORTED;
		}
		return SZ_OK;
	}
	if (f->NumCoders == 4)
	{
		if (!IS_SUPPORTED_CODER(&f->Coders[1]) ||
			!IS_SUPPORTED_CODER(&f->Coders[2]) ||
			!IS_BCJ2(&f->Coders[3]))
			return SZ_ERROR_UNSUPPORTED;
		if (f->NumPackStreams != 4 ||
			f->PackStreams[0] != 2 ||
			f->PackStreams[1] != 6 ||
			f->PackStreams[2] != 1 ||
			f->PackStreams[3] != 0 ||
			f->NumBindPairs != 3 ||
			f->BindPairs[0].InIndex != 5 || f->BindPairs[0].OutIndex != 0 ||
			f->BindPairs[1].InIndex != 4 || f->BindPairs[1].OutIndex != 1 ||
			f->BindPairs[2].InIndex != 3 || f->BindPairs[2].OutIndex != 2)
			return SZ_ERROR_UNSUPPORTED;
		return SZ_OK;
	}
	return SZ_ERROR_UNSUPPORTED;
}

static UInt64 GetSum(const UInt64 *values, UInt32 index)
{
	UInt64 sum = 0;
	UInt32 i;
	for (i = 0; i < index; i++)
		sum += values[i];
	return sum;
}

#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break;

static SRes SzFolder_Decode2(const CSzFolder *folder, const UInt64 *packSizes, ILookInStream *inStream, UInt64 startPos,
	Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain, Byte *tempBuf[])
{
	UInt32 ci;
	SizeT tempSizes[3] = { 0, 0, 0};
	SizeT tempSize3 = 0;
	Byte *tempBuf3 = 0;

	RINOK(CheckSupportedFolder(folder));

	for (ci = 0; ci < folder->NumCoders; ci++)
	{
		CSzCoderInfo *coder = &folder->Coders[ci];

		if (IS_MAIN_METHOD((UInt32)coder->MethodID))
		{
			UInt32 si = 0;
			UInt64 offset;
			UInt64 inSize;
			Byte *outBufCur = outBuffer;
			SizeT outSizeCur = outSize;
			if (folder->NumCoders == 4)
			{
				UInt32 indices[] = { 3, 2, 0 };
				UInt64 unpackSize = folder->UnpackSizes[ci];
				si = indices[ci];
				if (ci < 2)
				{
					Byte *temp;
					outSizeCur = (SizeT)unpackSize;
					if (outSizeCur != unpackSize)
						return SZ_ERROR_MEM;
					temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur);
					if (temp == 0 && outSizeCur != 0)
						return SZ_ERROR_MEM;
					outBufCur = tempBuf[1 - ci] = temp;
					tempSizes[1 - ci] = outSizeCur;
				}
				else if (ci == 2)
				{
					if (unpackSize > outSize) /* check it */
						return SZ_ERROR_PARAM;
					tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize);
					tempSize3 = outSizeCur = (SizeT)unpackSize;
				}
				else
					return SZ_ERROR_UNSUPPORTED;
			}
			offset = GetSum(packSizes, si);
			inSize = packSizes[si];
			RINOK(LookInStream_SeekTo(inStream, startPos + offset));

			if (coder->MethodID == k_Copy)
			{
				if (inSize != outSizeCur) /* check it */
					return SZ_ERROR_DATA;
				RINOK(SzDecodeCopy(inSize, inStream, outBufCur));
			}
			else if (coder->MethodID == k_LZMA)
			{
				RINOK(SzDecodeLzma(coder, inSize, inStream, outBufCur, outSizeCur, allocMain));
			}
			else if (coder->MethodID == k_LZMA2)
			{
				RINOK(SzDecodeLzma2(coder, inSize, inStream, outBufCur, outSizeCur, allocMain));
			}
			else
			{
#ifdef _7ZIP_PPMD_SUPPPORT
				RINOK(SzDecodePpmd(coder, inSize, inStream, outBufCur, outSizeCur, allocMain));
#else
				return SZ_ERROR_UNSUPPORTED;
#endif
			}
		}
		else if (coder->MethodID == k_BCJ2)
		{
			UInt64 offset = GetSum(packSizes, 1);
			UInt64 s3Size = packSizes[1];
			SRes res;
			if (ci != 3)
				return SZ_ERROR_UNSUPPORTED;
			RINOK(LookInStream_SeekTo(inStream, startPos + offset));
			tempSizes[2] = (SizeT)s3Size;
			if (tempSizes[2] != s3Size)
				return SZ_ERROR_MEM;
			tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]);
			if (tempBuf[2] == 0 && tempSizes[2] != 0)
				return SZ_ERROR_MEM;
			res = SzDecodeCopy(s3Size, inStream, tempBuf[2]);
			RINOK(res)

				res = Bcj2_Decode(
				tempBuf3, tempSize3,
				tempBuf[0], tempSizes[0],
				tempBuf[1], tempSizes[1],
				tempBuf[2], tempSizes[2],
				outBuffer, outSize);
			RINOK(res)
		}
		else
		{
			if (ci != 1)
				return SZ_ERROR_UNSUPPORTED;
			switch(coder->MethodID)
			{
			case k_BCJ:
				{
					UInt32 state;
					x86_Convert_Init(state);
					x86_Convert(outBuffer, outSize, 0, &state, 0);
					break;
				}
				CASE_BRA_CONV(ARM)
			default:
				return SZ_ERROR_UNSUPPORTED;
			}
		}
	}
	return SZ_OK;
}

SRes SzFolder_Decode(const CSzFolder *folder, const UInt64 *packSizes, ILookInStream *inStream, UInt64 startPos,
	Byte *outBuffer, size_t outSize, ISzAlloc *allocMain)
{
	Byte *tempBuf[3] = { 0, 0, 0};
	int i;
	SRes res = SzFolder_Decode2(folder, packSizes, inStream, startPos, outBuffer, (SizeT)outSize, allocMain, tempBuf);
	for (i = 0; i < 3; i++)
		IAlloc_Free(allocMain, tempBuf[i]);
	return res;
}
