#include "Dolphin/card.h"
#include "Dolphin/dsp.h"

static void InitCallback(void* task);
static void DoneCallback(void* task);

static u8 CardData[] ATTRIBUTE_ALIGN(32) = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x02, 0xFF, 0x00, 0x21, 0x13, 0x06, 0x12, 0x03, 0x12, 0x04, 0x13, 0x05, 0x00, 0x92, 0x00, 0xFF,
	0x00, 0x88, 0xFF, 0xFF, 0x00, 0x89, 0xFF, 0xFF, 0x00, 0x8A, 0xFF, 0xFF, 0x00, 0x8B, 0xFF, 0xFF, 0x8F, 0x00, 0x02, 0xBF, 0x00, 0x88,
	0x16, 0xFC, 0xDC, 0xD1, 0x16, 0xFD, 0x00, 0x00, 0x16, 0xFB, 0x00, 0x01, 0x02, 0xBF, 0x00, 0x8E, 0x25, 0xFF, 0x03, 0x80, 0xFF, 0x00,
	0x02, 0x94, 0x00, 0x27, 0x02, 0xBF, 0x00, 0x8E, 0x1F, 0xDF, 0x24, 0xFF, 0x02, 0x40, 0x0F, 0xFF, 0x00, 0x98, 0x04, 0x00, 0x00, 0x9A,
	0x00, 0x10, 0x00, 0x99, 0x00, 0x00, 0x8E, 0x00, 0x02, 0xBF, 0x00, 0x94, 0x02, 0xBF, 0x86, 0x44, 0x02, 0xBF, 0x00, 0x88, 0x16, 0xFC,
	0xDC, 0xD1, 0x16, 0xFD, 0x00, 0x03, 0x16, 0xFB, 0x00, 0x01, 0x8F, 0x00, 0x02, 0xBF, 0x00, 0x8E, 0x03, 0x80, 0xCD, 0xD1, 0x02, 0x94,
	0x00, 0x48, 0x27, 0xFF, 0x03, 0x80, 0x00, 0x01, 0x02, 0x95, 0x00, 0x5A, 0x03, 0x80, 0x00, 0x02, 0x02, 0x95, 0x80, 0x00, 0x02, 0x9F,
	0x00, 0x48, 0x00, 0x21, 0x8E, 0x00, 0x02, 0xBF, 0x00, 0x8E, 0x25, 0xFF, 0x02, 0xBF, 0x00, 0x8E, 0x25, 0xFF, 0x02, 0xBF, 0x00, 0x8E,
	0x25, 0xFF, 0x02, 0xBF, 0x00, 0x8E, 0x00, 0xC5, 0xFF, 0xFF, 0x03, 0x40, 0x0F, 0xFF, 0x1C, 0x9F, 0x02, 0xBF, 0x00, 0x8E, 0x00, 0xC7,
	0xFF, 0xFF, 0x02, 0xBF, 0x00, 0x8E, 0x00, 0xC6, 0xFF, 0xFF, 0x02, 0xBF, 0x00, 0x8E, 0x00, 0xC0, 0xFF, 0xFF, 0x02, 0xBF, 0x00, 0x8E,
	0x20, 0xFF, 0x03, 0x40, 0x0F, 0xFF, 0x1F, 0x5F, 0x02, 0xBF, 0x00, 0x8E, 0x21, 0xFF, 0x02, 0xBF, 0x00, 0x8E, 0x23, 0xFF, 0x12, 0x05,
	0x12, 0x06, 0x02, 0x9F, 0x80, 0xB5, 0x00, 0x21, 0x27, 0xFC, 0x03, 0xC0, 0x80, 0x00, 0x02, 0x9D, 0x00, 0x88, 0x02, 0xDF, 0x27, 0xFE,
	0x03, 0xC0, 0x80, 0x00, 0x02, 0x9C, 0x00, 0x8E, 0x02, 0xDF, 0x2E, 0xCE, 0x2C, 0xCF, 0x00, 0xF8, 0xFF, 0xCD, 0x00, 0xF9, 0xFF, 0xC9,
	0x00, 0xFA, 0xFF, 0xCB, 0x26, 0xC9, 0x02, 0xC0, 0x00, 0x04, 0x02, 0x9D, 0x00, 0x9C, 0x02, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static u32 next = 1;

// bit manip macros for use in ReadArrayUnlock
#define SEC_AD1(x) ((u8)(((x) >> 29) & 0x03))
#define SEC_AD2(x) ((u8)(((x) >> 21) & 0xff))
#define SEC_AD3(x) ((u8)(((x) >> 19) & 0x03))
#define SEC_BA(x)  ((u8)(((x) >> 12) & 0x7f))

/**
 * @note Address: N/A
 * @note Size: 0x24
 */
int CARDRand()
{
	next = next * 1103515245 + 12345;
	return (int)((u32)(next / 65536) % 32768);
}

/**
 * @note Address: N/A
 * @note Size: 0x8
 * Fun fact: making the argument of this a u32 instead of uint
 * makes the inlined versions of this not match (:
 * LOVE this compiler.
 */
void CARDSrand(uint seed) { next = seed; }

/**
 * @note Address: N/A
 * @note Size: 0x174
 */
u32 exnor_1st(u32 data, u32 rshift)
{
	u32 wk;
	u32 w;
	u32 i;

	w = data;
	for (i = 0; i < rshift; i++) {
		wk = ~(w ^ (w >> 7) ^ (w >> 15) ^ (w >> 23));
		w  = (w >> 1) | ((wk << 30) & 0x40000000);
	}
	return w;
}

/**
 * @note Address: N/A
 * @note Size: 0x174
 */
u32 exnor(u32 data, u32 lshift)
{
	u32 wk;
	u32 w;
	u32 i;

	w = data;
	for (i = 0; i < lshift; i++) {
		// 1bit Left Shift
		wk = ~(w ^ (w << 7) ^ (w << 15) ^ (w << 23));
		w  = (w << 1) | ((wk >> 30) & 0x00000002);
	}
	return w;
}

/**
 * @note Address: 0x800D5920
 * @note Size: 0x16C
 */
u32 bitrev(u32 data)
{
	u32 wk;
	u32 i;
	u32 k = 0;
	u32 j = 1;

	wk = 0;
	for (i = 0; i < 32; i++) {
		if (i > 15) {
			if (i == 31) {
				wk |= (((data & (0x01 << 31)) >> 31) & 0x01);
			} else {
				wk |= ((data & (0x01 << i)) >> j);
				j += 2;
			}
		} else {
			wk |= ((data & (0x01 << i)) << (31 - i - k));
			k++;
		}
	}
	return wk;
}

/**
 * @note Address: 0x800D5A8C
 * @note Size: 0x144
 */
s32 ReadArrayUnlock(s32 channel, u32 data, void* buffer, s32 rlen, s32 mode)
{
	CARDControl* card;
	BOOL err;
	u8 cmd[5];

	card = &__CARDBlock[channel];
	if (!EXISelect(channel, 0, 4)) {
		return CARD_RESULT_NOCARD;
	}

	data &= 0xfffff000;
	memset(cmd, 0, 5);
	cmd[0] = 0x52;
	if (mode == 0) {
		cmd[1] = SEC_AD1(data);
		cmd[2] = SEC_AD2(data);
		cmd[3] = SEC_AD3(data);
		cmd[4] = SEC_BA(data);
	} else {
		cmd[1] = (u8)((data & 0xff000000) >> 24);
		cmd[2] = (u8)((data & 0x00ff0000) >> 16);
	}

	err = FALSE;
	err |= !EXIImmEx(channel, cmd, 5, 1);
	err |= !EXIImmEx(channel, card->workArea->header.buffer, card->latency, 1);
	err |= !EXIImmEx(channel, buffer, rlen, 0);
	err |= !EXIDeselect(channel);

	return err ? CARD_RESULT_NOCARD : CARD_RESULT_READY;
}

/**
 * @note Address: N/A
 * @note Size: 0x54
 */
u32 GetInitVal()
{
	u32 tmp;
	u32 tick;

	tick = OSGetTick();
	CARDSrand(tick);
	tmp = 0x7FEC8000;
	tmp |= CARDRand();
	tmp &= 0xFFFFF000;
	return tmp;
}

/**
 * @note Address: 0x800D5BD0
 * @note Size: 0xC4
 */
s32 DummyLen()
{
	u32 tick;
	u32 wk;
	s32 tmp;
	u32 max;

	wk   = 1;
	max  = 0;
	tick = OSGetTick();
	CARDSrand(tick);

	tmp = CARDRand();
	tmp &= 0x0000001f;
	tmp += 1;
	while ((tmp < 4) && (max < 10)) {
		tick = OSGetTick();
		tmp  = (s32)(tick << wk);
		wk++;
		if (wk > 16) {
			wk = 1;
		}
		CARDSrand((u32)tmp);
		tmp = CARDRand();
		tmp &= 0x0000001f;
		tmp += 1;
		max++;
	}
	if (tmp < 4) {
		tmp = 4;
	}

	return tmp;
}

/**
 * @note Address: 0x800D5C94
 * @note Size: 0xB58
 */
s32 __CARDUnlock(s32 channel, u8 flashID[12])
{
	u32 init_val;
	u32 data;

	s32 dummy;
	s32 rlen;
	u32 rshift;

	u8 fsts;
	u32 wk, wk1;
	u32 Ans1 = 0;
	u32 Ans2 = 0;
	u32* dp;
	u8 rbuf[64];
	u32 para1A = 0;
	u32 para1B = 0;
	u32 para2A = 0;
	u32 para2B = 0;

	CARDControl* card;
	DSPTaskInfo* task;
	CARDDecodeParameters* param;
	u8* input;
	u8* output;

	card   = &__CARDBlock[channel];
	task   = &card->task;
	param  = (CARDDecodeParameters*)card->workArea;
	input  = (u8*)((u8*)param + sizeof(CARDDecodeParameters));
	input  = (u8*)OSRoundUp32B(input);
	output = input + 32;

	fsts     = 0;
	init_val = GetInitVal();

	dummy = DummyLen();
	rlen  = dummy;
	if (ReadArrayUnlock(channel, init_val, rbuf, rlen, 0) < 0) {
		return CARD_RESULT_NOCARD;
	}

	rshift         = (u32)(dummy * 8 + 1);
	wk             = exnor_1st(init_val, rshift);
	wk1            = ~(wk ^ (wk >> 7) ^ (wk >> 15) ^ (wk >> 23));
	card->scramble = (wk | ((wk1 << 31) & 0x80000000));
	card->scramble = bitrev(card->scramble);
	dummy          = DummyLen();
	rlen           = 20 + dummy;
	data           = 0;
	if (ReadArrayUnlock(channel, data, rbuf, rlen, 1) < 0) {
		return CARD_RESULT_NOCARD;
	}
	dp             = (u32*)rbuf;
	para1A         = *dp++;
	para1B         = *dp++;
	Ans1           = *dp++;
	para2A         = *dp++;
	para2B         = *dp++;
	para1A         = (para1A ^ card->scramble);
	rshift         = 32;
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));
	para1B         = (para1B ^ card->scramble);
	rshift         = 32;
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));
	Ans1 ^= card->scramble;
	rshift         = 32;
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));
	para2A         = (para2A ^ card->scramble);
	rshift         = 32;
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));
	para2B         = (para2B ^ card->scramble);
	rshift         = (u32)(dummy * 8);
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));
	rshift         = 32 + 1;
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));

	*(u32*)&input[0] = para2A;
	*(u32*)&input[4] = para2B;

	param->inputAddr   = input;
	param->inputLength = 8;
	param->outputAddr  = output;
	param->aramAddr    = 0;

	DCFlushRange(input, 8);
	DCInvalidateRange(output, 4);
	DCFlushRange(param, sizeof(CARDDecodeParameters));

	task->priority        = 255;
	task->iram_mmem_addr  = (u16*)OSPhysicalToCached(CardData);
	task->iram_length     = 0x160;
	task->iram_addr       = 0;
	task->dsp_init_vector = 0x10;
	task->init_cb         = InitCallback;
	task->res_cb          = nullptr;
	task->done_cb         = DoneCallback;
	task->req_cb          = nullptr;
	DSPAddTask(task);

	dp    = (u32*)flashID;
	*dp++ = para1A;
	*dp++ = para1B;
	*dp   = Ans1;

	return CARD_RESULT_READY;
}

/**
 * @note Address: 0x800D67EC
 * @note Size: 0x70
 */
void InitCallback(void* dspTask)
{
	s32 chan;
	CARDControl* card;
	DSPTaskInfo* task;
	CARDDecodeParameters* param;

	task = dspTask;
	for (chan = 0; chan < 2; ++chan) {
		card = &__CARDBlock[chan];
		if ((DSPTaskInfo*)&card->task == task) {
			break;
		}
	}
	param = (CARDDecodeParameters*)card->workArea;

	DSPSendMailToDSP(0xff000000);
	while (DSPCheckMailToDSP()) { }

	DSPSendMailToDSP((u32)param);
	while (DSPCheckMailToDSP()) { }
}

/**
 * @note Address: 0x800D685C
 * @note Size: 0x324
 */
void DoneCallback(void* dspTask)
{
	u8 rbuf[64];
	u32 data;
	s32 dummy;
	s32 rlen;
	u32 rshift;

	u8 unk;
	u32 wk, wk1;
	u32 Ans2;

	s32 chan;
	CARDControl* card;
	s32 result;
	DSPTaskInfo* task;
	CARDDecodeParameters* param;

	u8* input;
	u8* output;
	task = dspTask;
	for (chan = 0; chan < 2; ++chan) {
		card = &__CARDBlock[chan];
		if ((DSPTaskInfo*)&card->task == task) {
			break;
		}
	}

	param  = (CARDDecodeParameters*)card->workArea;
	input  = (u8*)((u8*)param + sizeof(CARDDecodeParameters));
	input  = (u8*)OSRoundUp32B(input);
	output = input + 32;

	Ans2  = *(u32*)output;
	dummy = DummyLen();
	rlen  = dummy;
	data  = ((Ans2 ^ card->scramble) & 0xffff0000);
	if (ReadArrayUnlock(chan, data, rbuf, rlen, 1) < 0) {
		EXIUnlock(chan);
		__CARDMountCallback(chan, CARD_RESULT_NOCARD);
		return;
	}

	rshift         = (u32)((dummy + 4 + card->latency) * 8 + 1);
	wk             = exnor(card->scramble, rshift);
	wk1            = ~(wk ^ (wk << 7) ^ (wk << 15) ^ (wk << 23));
	card->scramble = (wk | ((wk1 >> 31) & 0x00000001));

	dummy = DummyLen();
	rlen  = dummy;
	data  = (((Ans2 << 16) ^ card->scramble) & 0xffff0000);
	if (ReadArrayUnlock(chan, data, rbuf, rlen, 1) < 0) {
		EXIUnlock(chan);
		__CARDMountCallback(chan, CARD_RESULT_NOCARD);
		return;
	}
	result = __CARDReadStatus(chan, &unk);
	if (!EXIProbe(chan)) {
		EXIUnlock(chan);
		__CARDMountCallback(chan, CARD_RESULT_NOCARD);
		return;
	}
	if (result == CARD_RESULT_READY && !(unk & 0x40)) {
		EXIUnlock(chan);
		result = CARD_RESULT_IOERROR;
	}
	__CARDMountCallback(chan, result);
}
