// SPDX-License-Identifier: LGPL-3.0-only

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <rz_lib.h>
#include <rz_util.h>
#include <rz_flag.h>
#include <rz_analysis.h>
#include <rz_parse.h>

static int replace(int argc, const char *argv[], char *newstr) {
	int i, j, k;
	struct {
		char *op;
		char *str;
	} ops[] = {
		{ "add", "B += A" },
		{ "addc", "B += A + t" },
		{ "addv", "B += A; t = int_overflow (B)" },
		{ "and", "B &= A" },
		{ "and.b", "B &= A" },
		{ "bf", "if (!t) goto A" },
		{ "bf.s", "if (!t) goto A" },
		{ "bra", "goto A" },
		{ "brk", "_break_exception ()" },
		{ "bsr", "A ()" },
		{ "bsrf", "A ()" },
		{ "bt", "if (t) goto A" },
		{ "bt.s", "if (t) goto A" },
		{ "clrmac", "_clrmac ()" },
		{ "clrs", "_clrs ()" },
		{ "clrt", "_clrt ()" },
		{ "cmp/eq", "t = B == A ? 1 : 0" },
		{ "cmp/ge", "t = B >= A ? 1 : 0" },
		{ "cmp/gt", "t = B > A ? 1 : 0" },
		{ "cmp/hi", "t = (unsigned) B > (unsigned) A ? 1 : 0" },
		{ "cmp/hs", "t = (unsigned) B >= (unsigned) A ? 1 : 0" },
		{ "cmp/pl", "t = A > 0 ? 1 : 0" },
		{ "cmp/pz", "t = A >= 0 ? 1 : 0" },
		{ "cmp/str", "t = A ^ B ? 1 : 0" },
		{ "div1", "B /= A" },
		{ "dmuls.l", "mac = B * A" },
		{ "dmulu.l", "mac = (unsigned) B * (unsigned) A" },
		{ "dt", "A--; t = !A ? 1 : 0" },
		{ "exts.b", "B = (int) A" },
		{ "extu.b", "B = (unsigned int) A" },
		{ "exts.w", "B = (int) A" },
		{ "extu.w", "B = (unsigned int) A" },
		{ "fabs", "A = abs (A)" },
		{ "fadd", "B += A" },
		{ "fcmp/eq", "t = B == A ? 1 : 0" },
		{ "fcmp/gt", "t = B > A ? 1 : 0" },
		{ "fcnvds", "B = A" },
		{ "fdiv", "B /= A" },
		{ "flds", "B = A" },
		{ "fldi0", "A = 0.0f" },
		{ "fldi1", "A = 1.0f" },
		{ "float", "B = A" },
		{ "fmac", "C += A * B" },
		{ "fmov", "B = A" },
		{ "fmov.s", "B = A" },
		{ "fmul", "B *= A" },
		{ "fneg", "A = -A" },
		{ "fsqrt", "A = sqrt (A)" },
		{ "fsts", "B = A" },
		{ "fsub", "B -= A" },
		{ "ftrc", "B = trunc (A)" },
		{ "ftrv", "B *= A" },
		{ "jmp", "goto A" },
		{ "jsr", "A ()" },
		{ "ldr", "B = A" },
		{ "ldr.l", "B = A" },
		{ "lds", "B = A" },
		{ "lds.l", "B = A" },
		{ "mov", "B = A" },
		{ "mov.b", "B = A" },
		{ "mov.l", "B = A" },
		{ "mov.w", "B = A" },
		{ "movca.l", "B = A" },
		{ "movt", "A = t" },
		{ "muls.w", "macl = A * B" },
		{ "mulu.w", "macl = (unsigned) A * (unsigned) B" },
		{ "neg", "A = -A" },
		{ "negc", "A = (-A) - t" },
		{ "nop", "" },
		{ "not", "A = !A" },
		{ "or", "B |= A" },
		{ "rotcl", "t = A & 0x80000000 ? 0 : 1; A = (A << 1) | t" },
		{ "rotl", "A = (A << 1) | (A >> 31)" },
		{ "rotr", "A = (A << 31) | (A >> 1)" },
		{ "rte", "_rte ()" },
		{ "rts", "return" },
		{ "sets", "s = 1" },
		{ "sett", "t = 1" },
		{ "shad", "B = A >= 0 ? B << A : B >> (31 - A)" },
		{ "shal", "A <<= 1" },
		{ "shar", "A >>= 1" },
		{ "shld", "B = A >= 0 ? B << A : B >> (31 - A)" },
		{ "shll", "A <<= 1" },
		{ "shll2", "A <<= 2" },
		{ "shll8", "A <<= 8" },
		{ "shll16", "A <<= 16" },
		{ "shlr", "A >>= 1" },
		{ "shlr2", "A >>= 2" },
		{ "shlr8", "A >>= 8" },
		{ "shlr16", "A >>= 16" },
		{ "sleep", "_halt ()" },
		{ "stc", "B = A" },
		{ "stc.l", "B = A" },
		{ "sts", "B = A" },
		{ "sts.l", "B = A" },
		{ "sub", "B -= A" },
		{ "subc", "B -= A - t" },
		{ "subv", "B -= A; t = int_underflow (B)" },
		{ "swap.b", "swap_byte (B, A)" },
		{ "swap.w", "swap_word (B, A)" },
		{ "tas.b", "test_and_set (A)" },
		{ "trapa", "trap (A)" },
		{ "tst", "t = B & A ? 0 : 1" },
		{ "xor", "B ^= A" },
		{ "xor.b", "B ^= A" },
		{ NULL }
	};

	for (i = 0; ops[i].op != NULL; i++) {
		if (!strcmp(ops[i].op, argv[0])) {
			if (newstr != NULL) {
				for (j = k = 0; ops[i].str[j] != '\0'; j++, k++) {
					if (ops[i].str[j] >= 'A' && ops[i].str[j] <= 'J') {
						const char *w = argv[ops[i].str[j] - '@'];
						if (w != NULL) {
							strcpy(newstr + k, w);
							k += strlen(w) - 1;
						}
					} else {
						newstr[k] = ops[i].str[j];
					}
				}
				newstr[k] = '\0';
			}
			return true;
		}
	}

	/* TODO: this is slow */
	if (newstr != NULL) {
		newstr[0] = '\0';
		for (i = 0; i < argc; i++) {
			strcat(newstr, argv[i]);
			strcat(newstr, (i == 0 || i == argc - 1) ? " " : ", ");
		}
	}

	return false;
}

#define WSZ 128
static int parse(RzParse *p, const char *data, char *str) {
	int i, len = strlen(data);
	char w0[WSZ];
	char w1[WSZ];
	char w2[WSZ];
	char w3[WSZ];
	char w4[WSZ];
	char *buf, *ptr, *optr, *par;

	// malloc can be slow here :?
	if (!(buf = malloc(len + 1))) {
		return false;
	}
	memcpy(buf, data, len + 1);

	rz_str_trim(buf);
	if (*buf) {
		w0[0] = '\0';
		w1[0] = '\0';
		w2[0] = '\0';
		w3[0] = '\0';
		w4[0] = '\0';
		ptr = strchr(buf, ' ');
		if (!ptr) {
			ptr = strchr(buf, '\t');
		}
		if (ptr) {
			*ptr = '\0';
			for (++ptr; *ptr == ' '; ptr++) {
				//nothing to see here
			}
			strncpy(w0, buf, WSZ - 1);
			strncpy(w1, ptr, WSZ - 1);

			optr = ptr;
			par = strchr(ptr, '(');
			if (par && strchr(ptr, ',') > par) {
				ptr = strchr(ptr, ')');
				if (ptr) {
					ptr = strchr(ptr, ',');
				}
			} else {
				ptr = strchr(ptr, ',');
			}
			if (ptr) {
				*ptr = '\0';
				for (++ptr; *ptr == ' '; ptr++) {
					//nothing to see here
				}
				strncpy(w1, optr, WSZ - 1);
				strncpy(w2, ptr, WSZ - 1);
				optr = ptr;
				par = strchr(ptr, '(');
				if (par && strchr(ptr, ',') > par) {
					ptr = strchr(ptr, ')');
					if (ptr) {
						ptr = strchr(ptr, ',');
					}
				} else {
					ptr = strchr(ptr, ',');
				}
				if (ptr) {
					*ptr = '\0';
					for (++ptr; *ptr == ' '; ptr++) {
						//nothing to see here
					}
					strncpy(w2, optr, WSZ - 1);
					strncpy(w3, ptr, WSZ - 1);
					optr = ptr;
					// bonus
					par = strchr(ptr, '(');
					if (par && strchr(ptr, ',') > par) {
						ptr = strchr(ptr, ')');
						if (ptr) {
							ptr = strchr(ptr, ',');
						}
					} else {
						ptr = strchr(ptr, ',');
					}
					if (ptr) {
						*ptr = '\0';
						for (++ptr; *ptr == ' '; ptr++) {
							//nothing to see here
						}
						strncpy(w3, optr, WSZ - 1);
						strncpy(w4, ptr, WSZ - 1);
					}
				}
			}
		} else {
			strncpy(w0, buf, WSZ - 1);
		}
		{
			const char *wa[] = { w0, w1, w2, w3, w4 };
			int nw = 0;
			for (i = 0; i < 5; i++) {
				if (wa[i][0] != '\0') {
					nw++;
				}
			}
			replace(nw, wa, str);
		}
	}
	free(buf);
	return true;
}

RzParsePlugin rz_parse_plugin_sh_pseudo = {
	.name = "sh.pseudo",
	.desc = "SH-4 pseudo syntax",
	.parse = parse
};

#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
	.type = RZ_LIB_TYPE_PARSE,
	.data = &rz_parse_plugin_sh_pseudo,
	.version = RZ_VERSION
};
#endif
