/* radare2 - MIT - Copyright 2024-2025 - pancake */

#include <r_util.h>

typedef int(*AssemblerFunc)(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code);

typedef struct {
	const char *mnemonic;
	AssemblerFunc assemble;
} Instruction;

// Function to tokenize a string into tokens
static int tokenize(const char *str, char tokens[][256], int max_tokens) {
	int num_tokens = 0;
	const char *p = str;
	while (*p != '\0' && num_tokens < max_tokens) {
		// Skip leading delimiters
		while (*p == ' ' || *p == '\t' || *p == ',') {
			p++;
		}
		if (*p == '\0') {
			break;
		}
		// Copy the token into tokens[num_tokens]
		int len = 0;
		while (*p != '\0' && *p != ' ' && *p != '\t' && *p != ',') {
			if (len < 255) {
				tokens[num_tokens][len++] = *p;
			}
			p++;
		}
		tokens[num_tokens][len] = '\0';
		num_tokens++;
	}
	return num_tokens;
}

// Parse an immediate value. Accept optional leading '#' and hex (0x) or decimal.
static int parse_immediate_token(const char *token, int *out_val) {
	const char *p = token;
	if (*p == '#') {
		p++;
	}
	if (*p == '\0') {
		return 0;
	}
	char *endptr = NULL;
	long val = strtol (p, &endptr, 0);
	if (endptr == p) {
		return 0;
	}
	*out_val = (int)val;
	return 1;
}

// Function to map register name to register number
static int parse_register(const char *reg_str) {
	if (tolower (reg_str[0]) == 'd') {
		const int reg_num = atoi (reg_str + 1);
		if (reg_num >= 0 && reg_num <= 7) {
			return reg_num;
		}
	}
	return -1; // Invalid register
}

// Parse simple address register indirect: " (A<reg>)" returns reg 0..7 or -1
static int parse_addr_indirect(const char *tok) {
	if (!tok || tok[0] != '(') {
		return -1;
	}
	// Expect format (A<d>)
	if ((tok[1] != 'A' && tok[1] != 'a')) {
		return -1;
	}
	if (!tok[2] || tok[3] != ')' || tok[4] != '\0') {
		return -1;
	}
	int n = tok[2] - '0';
	if (n < 0 || n > 7) {
		return -1;
	}
	return n;
}

// Parse address register postincrement: " (A<reg>)+"
static int parse_addr_postinc(const char *tok) {
	size_t len = tok ? strlen (tok) : 0;
	if (len < 5 || tok[0] != '(' || (tok[1] != 'A' && tok[1] != 'a')) {
		return -1;
	}
	if (tok[len - 1] != '+' || tok[len - 2] != ')') {
		return -1;
	}
	// Expected format is exactly 5 chars: " (A<d>)+"
	if (len != 5) {
		return -1;
	}
	int n = tok[2] - '0';
	if (n < 0 || n > 7) {
		return -1;
	}
	return n;
}

// Parse address register predecrement: "- (A<reg>)"
static int parse_addr_predec(const char *tok) {
	if (!tok || tok[0] != '-' || tok[1] != '(' || (tok[2] != 'A' && tok[2] != 'a')) {
		return -1;
	}
	if (!tok[3] || tok[4] != ')' || tok[5] != '\0') {
		return -1;
	}
	int n = tok[3] - '0';
	if (n < 0 || n > 7) {
		return -1;
	}
	return n;
}

// Helper to parse number prefix before '(' in tokens like "123 (PC)" or "0x30 (A0,D3.W)"
static bool parse_number_before_paren(const char *tok, int *out) {
	const char *p = strchr (tok, '(');
	if (!p) {
		return false;
	}
	if (p == tok) {
		*out = 0;
		return true; // no number, treat as 0
	}
	char num[64];
	size_t n = R_MIN ((size_t) (p - tok), sizeof (num) - 1);
	memcpy (num, tok, n);
	num[n] = '\0';
	char *end = NULL;
	long v = strtol (num, &end, 0);
	if (end == num) {
		return false;
	}
	*out = (int)v;
	return true;
}

typedef struct {
	int mode;    // 0..7
	int reg;     // 0..7
	ut16 ext[2];
	int ext_len; // in bytes
} EA;

// Minimal EA parser for needed MOVE cases
static bool parse_ea(const char *tok, EA *ea) {
	memset (ea, 0, sizeof (*ea));
	int r = parse_register (tok);
	if (r >= 0) {
		ea->mode = 0; ea->reg = r; ea->ext_len = 0; return true; // Dn
	}
	r = parse_addr_indirect (tok);
	if (r >= 0) {
		ea->mode = 2; ea->reg = r; ea->ext_len = 0; return true; // (An)
	}
	r = parse_addr_postinc (tok);
	if (r >= 0) {
		ea->mode = 3; ea->reg = r; ea->ext_len = 0; return true; // (An)+
	}
	r = parse_addr_predec (tok);
	if (r >= 0) {
		ea->mode = 4; ea->reg = r; ea->ext_len = 0; return true; // - (An)
	}
	// PC-relative displaced: <disp> (PC)
	if (strstr (tok, " (PC)") || strstr (tok, " (pc)")) {
		int disp;
		if (!parse_number_before_paren (tok, &disp)) {
			return false;
		}
		ea->mode = 7; ea->reg = 2; // (d16,PC)
		ea->ext_len = 2;
		// On 68k, PC for (d16,PC) points to the extension word during EA calc.
		// Adjust displacement by -2 so users can specify target relative to opcode PC.
		ea->ext[0] = (ut16) ((disp - 2) & 0xFFFF);
		return true;
	}
	// Absolute .W/.L: <abs>.W or <abs>.L
	size_t len = strlen (tok);
	if (len > 2 && tok[len - 2] == '.' && (tok[len - 1] == 'W' || tok[len - 1] == 'L')) {
		char num[128];
		size_t n = R_MIN (len - 2, sizeof (num) - 1);
		memcpy (num, tok, n);
		num[n] = '\0';
		char *end = NULL;
		long v = strtol (num, &end, 0);
		if (end == num) {
			return false;
		}
		ea->mode = 7;
		if (tok[len - 1] == 'W') {
			ea->reg = 0; // abs.W
			ea->ext_len = 2;
			ea->ext[0] = (ut16) (v & 0xFFFF);
		} else {
			ea->reg = 1; // abs.L
			ea->ext_len = 4;
			ea->ext[0] = (ut16) ((v >> 16) & 0xFFFF);
			ea->ext[1] = (ut16) (v & 0xFFFF);
		}
		return true;
	}
	// Indexed: <disp> (A<base>, D<idx>.<sz>) brief extension word only
	const char *lp = strchr (tok, '(');
	const char *comma = lp ? strchr (lp, ',') : NULL;
	const char *rp = tok ? strrchr (tok, ')') : NULL;
	if (lp && comma && rp && rp > comma) {
		// Expect like "<disp> (A0,D3.W)"
		if (! ((lp[1] == 'A' || lp[1] == 'a') && isdigit ((unsigned char)lp[2]))) {
			return false;
		}
		int baseA = lp[2] - '0';
		if (baseA < 0 || baseA > 7) {
			return false;
		}
		// After comma: Dn.size)
		const char *q = comma + 1;
		while (*q == ' ') q++;
		if (! (*q == 'D' || *q == 'd')) {
			return false;
		}
		int idxD = atoi (q + 1);
		if (idxD < 0 || idxD > 7) {
			return false;
		}
		const char *dot = strchr (q, '.');
		char szc = dot ? dot[1] : 'W'; // default word
		bool idx_long = (szc == 'L' || szc == 'l');
		int disp;
		if (!parse_number_before_paren (tok, &disp)) {
			return false;
		}
		// Build brief extension word
		ut16 ext = 0;
		ext |= 0 << 15;                 // Dn index
		ext |= (idxD & 7) << 12;        // index reg
		ext |= (idx_long ? 1 : 0) << 11;// size 0=word 1=long
		ext |= (0 /*scale x1*/) << 9;   // scale
		ext |= 0 << 8;                  // brief format
		ext |= (ut16) (disp & 0xFF);     // 8-bit displacement
		ea->mode = 6; ea->reg = baseA & 7;
		ea->ext_len = 2;
		ea->ext[0] = ext;
		return true;
	}
	return false;
}

// Implement assembler functions for each instruction
static int assemble_reset(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2) {
		return 0; // Cannot assemble, buffer too small
	}
	buf[0] = 0x4e;
	buf[1] = 0x70;
	return 2;
}

static int assemble_nop(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2) {
		return 0; // Cannot assemble, buffer too small
	}
	// NOP opcode is 0x4E71
	buf[0] = 0x4E;
	buf[1] = 0x71;
	return 2;
}

static int assemble_moveq(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		R_LOG_ERROR ("Invalid number of operands for moveq");
		return 0;
	}

	// tokens[0] = "MOVEQ"
	// tokens[1] = "#<data>" or "<data>"
	// tokens[2] = "D<reg>"

	int imm_value = 0;
	if (!parse_immediate_token (tokens[1], &imm_value)) {
		R_LOG_ERROR ("Cannot parse immediate %s", tokens[1]);
		return 0; // Immediate parse failed
	}

	int8_t byte_value = (int8_t) (imm_value & 0xff);
	imm_value = byte_value;

	int register_number = parse_register (tokens[2]);
	if (register_number < 0) {
		R_LOG_ERROR ("Immediate register name %s", tokens[2]);
		return 0; // Invalid register
	}

	// Opcode: 0x7000 | (Dn << 9) | (imm_value & 0xFF)
	ut16 opcode = 0x7000;
	opcode |= (register_number << 9);
	opcode |= (imm_value & 0xFF);

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;

	return 2;
}

static int assemble_move(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	// Handle special tokenization when first operand contains a comma inside parentheses
	char combined[256];
	const char *op1, *op2;
	if (num_tokens == 3) {
		op1 = tokens[1];
		op2 = tokens[2];
	} else if (num_tokens == 4 && strchr (tokens[1], '(') && tokens[2][strlen (tokens[2]) - 1] == ')') {
		snprintf (combined, sizeof (combined), "%s,%s", tokens[1], tokens[2]);
		op1 = combined;
		op2 = tokens[3];
	} else {
		return 0;
	}

	int sc = (size_code >= 0) ? size_code : 1; // 0=byte,1=word,2=long
	if (sc < 0 || sc > 2) {
		return 0;
	}
	// MOVE size top bits: 00ss (01=byte -> 0x1000, 11=word -> 0x3000, 10=long -> 0x2000)
	ut16 base;
	switch (sc) {
	case 0: base = 0x1000; break; // byte
	case 1: base = 0x3000; break; // word
	case 2: base = 0x2000; break; // long
	default: return 0;
	}

	EA src = {0}, dst = {0};
	if (!parse_ea (op1, &src)) {
		return 0;
	}
	if (!parse_ea (op2, &dst)) {
		return 0;
	}

	ut16 opcode = base;
	opcode |= (dst.reg & 7) << 9;  // destination reg
	opcode |= (dst.mode & 7) << 6; // destination mode
	opcode |= (src.mode & 7) << 3; // source mode
	opcode |= (src.reg & 7);       // source reg

	int total = 2 + src.ext_len + dst.ext_len;
	if (size < total) {
		return 0;
	}
	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	int off = 2;
	// Emit source EA extensions first
	if (src.ext_len == 2) {
		buf[off++] = (src.ext[0] >> 8) & 0xFF;
		buf[off++] = src.ext[0] & 0xFF;
	} else if (src.ext_len == 4) {
		buf[off++] = (src.ext[0] >> 8) & 0xFF;
		buf[off++] = src.ext[0] & 0xFF;
		buf[off++] = (src.ext[1] >> 8) & 0xFF;
		buf[off++] = src.ext[1] & 0xFF;
	}
	// Then destination EA extensions
	if (dst.ext_len == 2) {
		buf[off++] = (dst.ext[0] >> 8) & 0xFF;
		buf[off++] = dst.ext[0] & 0xFF;
	} else if (dst.ext_len == 4) {
		buf[off++] = (dst.ext[0] >> 8) & 0xFF;
		buf[off++] = dst.ext[0] & 0xFF;
		buf[off++] = (dst.ext[1] >> 8) & 0xFF;
		buf[off++] = dst.ext[1] & 0xFF;
	}
	return total;
}

static int assemble_addq(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}
	// tokens[0] = "ADDQ"
	// tokens[1] = "#<data>" or "<data>"
	// tokens[2] = "D<reg>"
	int imm_value = 0;
	if (!parse_immediate_token (tokens[1], &imm_value)) {
		return 0;
	}
	if (imm_value < 1 || imm_value > 8) {
		return 0;
	}
	if (imm_value == 8) {
		imm_value = 0;
	}
	int register_number = parse_register (tokens[2]);
	if (register_number < 0) {
		return 0; // Invalid register
	}
	int sc = (size_code >= 0) ? size_code : 1; // default word
	ut16 opcode = 0x5000;
	opcode |= (imm_value & 7) << 9;
	opcode |= (sc & 3) << 6; // size
	opcode |= (0 << 3); // Mode code for data register direct
	opcode |= register_number & 7;

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_subq(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}

	// tokens[0] = "SUBQ"
	// tokens[1] = "#<data>" or "<data>"
	// tokens[2] = "D<reg>"
	int imm_value = 0;
	if (!parse_immediate_token (tokens[1], &imm_value)) {
		return 0;
	}
	if (imm_value < 1 || imm_value > 8) {
		return 0;
	}
	if (imm_value == 8) {
		imm_value = 0;
	}
	int register_number = parse_register (tokens[2]);
	if (register_number < 0) {
		return 0; // Invalid register
	}

	int sc = (size_code >= 0) ? size_code : 1; // default word
	ut16 opcode = 0x5100;
	opcode |= (imm_value & 7) << 9;
	opcode |= (sc & 3) << 6; // size
	opcode |= (0 << 3); // Mode code for data register direct
	opcode |= register_number & 7;

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_bne(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (num_tokens != 2) {
		return 0;
	}
	// tokens[0] = "BNE"
	// tokens[1] = "<offset>"
	int offset = 0;
	if (!parse_immediate_token (tokens[1], &offset)) {
		return 0;
	}

	if (offset >= -128 && offset <= 127) {
		if (size < 2) {
			return 0;
		}
		offset -= 2; // instruction size
		ut16 opcode = 0x6600 | (offset & 0xFF);
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		return 2;
	} else if (offset >= -32768 && offset <= 32767) {
		if (size < 4) {
			return 0;
		}
		offset -= 4; // instruction size
		ut16 opcode = 0x6600;
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		ut16 displacement = offset & 0xFFFF;
		buf[2] = (displacement >> 8) & 0xFF;
		buf[3] = displacement & 0xFF;
		return 4;
	}
	return 0; // Offset out of range
}

static int assemble_beq(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (num_tokens != 2) {
		return 0;
	}
	int offset = 0;
	if (!parse_immediate_token (tokens[1], &offset)) {
		return 0;
	}
	if (offset >= -128 && offset <= 127) {
		if (size < 2) {
			return 0;
		}
		offset -= 2;
		ut16 opcode = 0x6700 | (offset & 0xFF);
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		return 2;
	} else if (offset >= -32768 && offset <= 32767) {
		if (size < 4) {
			return 0;
		}
		offset -= 4;
		ut16 opcode = 0x6700;
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		ut16 disp = offset & 0xFFFF;
		buf[2] = (disp >> 8) & 0xFF;
		buf[3] = disp & 0xFF;
		return 4;
	}
	return 0;
}

static int assemble_bra(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (num_tokens != 2) {
		return 0;
	}
	int offset = 0;
	if (!parse_immediate_token (tokens[1], &offset)) {
		return 0;
	}
	// 0x60xx for BRA with 8-bit displacement; 0x6000 + ext for word
	if (offset >= -128 && offset <= 127) {
		if (size < 2) {
			return 0;
		}
		offset -= 2; // PC after opcode
		ut16 opcode = 0x6000 | (offset & 0xFF);
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		return 2;
	} else if (offset >= -32768 && offset <= 32767) {
		if (size < 4) {
			return 0;
		}
		offset -= 2; // displacement relative to extension word address
		ut16 opcode = 0x6000;
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		ut16 disp = offset & 0xFFFF;
		buf[2] = (disp >> 8) & 0xFF;
		buf[3] = disp & 0xFF;
		return 4;
	}
	return 0;
}

static int assemble_eor(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}
	// EOR D<src>, <ea>
	const int src_reg = parse_register (tokens[1]);
	if (src_reg < 0) {
		return 0;
	}

	int sc = (size_code >= 0) ? size_code : 1; // default to word
	if (sc < 0 || sc > 2) {
		return 0;
	}

	int mode = -1, reg = -1;
	int dreg = parse_register (tokens[2]);
	if (dreg >= 0) {
		mode = 0; // data register direct
		reg = dreg & 7;
	} else {
		int areg = parse_addr_indirect (tokens[2]);
		if (areg >= 0) {
			mode = 2; // (An)
			reg = areg & 7;
		}
	}
	if (mode < 0) {
		// Unsupported EA for now
		return 0;
	}

	ut16 opcode = 0xB000;
	opcode |= (src_reg & 7) << 9; // source Dn
	opcode |= (1 << 8);           // direction bit: register -> memory/EA
	opcode |= (sc & 3) << 6;      // size
	opcode |= (mode & 7) << 3;    // EA mode
	opcode |= (reg & 7);          // EA reg

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_clr(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 2) {
		return 0;
	}

	// tokens[0] = "CLR" (size suffix parsed separately)
	// tokens[1] = "D<reg>"

	int dest_reg = parse_register (tokens[1]);
	if (dest_reg < 0) {
		return 0;
	}

	// Default to word size if unspecified
	int sc = (size_code >= 0) ? size_code : 1; // 0=byte,1=word,2=long
	if (sc < 0 || sc > 2) {
		return 0;
	}

	ut16 opcode = 0x4200;
	opcode |= (sc & 3) << 6; // size bits
	opcode |= (0 << 3); // dest_mode = 0 (data register direct)
	opcode |= dest_reg & 7;

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_tst(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	// TST.<size> <ea>
	if (num_tokens != 2) {
		return 0;
	}
	int sc = (size_code >= 0) ? size_code : 1; // default word
	if (sc < 0 || sc > 2) {
		return 0;
	}
	EA ea;
	if (!parse_ea (tokens[1], &ea)) {
		return 0;
	}
	ut16 opcode = 0x4A00;
	opcode |= (sc & 3) << 6;
	opcode |= (ea.mode & 7) << 3;
	opcode |= (ea.reg & 7);

	int total = 2 + ea.ext_len;
	if (size < total) {
		return 0;
	}
	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	int off = 2;
	if (ea.ext_len == 2) {
		buf[off++] = (ea.ext[0] >> 8) & 0xFF;
		buf[off++] = ea.ext[0] & 0xFF;
	} else if (ea.ext_len == 4) {
		buf[off++] = (ea.ext[0] >> 8) & 0xFF;
		buf[off++] = ea.ext[0] & 0xFF;
		buf[off++] = (ea.ext[1] >> 8) & 0xFF;
		buf[off++] = ea.ext[1] & 0xFF;
	}
	return total;
}

static int assemble_lea(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	// LEA <ea>, A<dest>
	if (num_tokens != 3) {
		return 0;
	}
	EA ea;
	if (!parse_ea (tokens[1], &ea)) {
		return 0;
	}
	// Destination must be An
	const char *t = tokens[2];
	if (! (t[0] == 'A' || t[0] == 'a')) {
		return 0;
	}
	int dest = atoi (t + 1);
	if (dest < 0 || dest > 7) {
		return 0;
	}
	ut16 opcode = 0x41C0; // base for LEA with ddd = 000
	opcode |= (dest & 7) << 9;
	opcode |= (ea.mode & 7) << 3;
	opcode |= (ea.reg & 7);

	int total = 2 + ea.ext_len;
	if (size < total) {
		return 0;
	}
	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	int off = 2;
	if (ea.ext_len == 2) {
		buf[off++] = (ea.ext[0] >> 8) & 0xFF;
		buf[off++] = ea.ext[0] & 0xFF;
	} else if (ea.ext_len == 4) {
		buf[off++] = (ea.ext[0] >> 8) & 0xFF;
		buf[off++] = ea.ext[0] & 0xFF;
		buf[off++] = (ea.ext[1] >> 8) & 0xFF;
		buf[off++] = ea.ext[1] & 0xFF;
	}
	return total;
}

static int assemble_bchg(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}
	// BCHG #imm,<ea>  or BCHG Dn,<ea> (including Dn)

	int imm_val = 0;
	if (parse_immediate_token (tokens[1], &imm_val)) {
		// Immediate bit number form: only valid with memory EAs
		EA ea;
		if (!parse_ea (tokens[2], &ea)) {
			return 0;
		}
		if (ea.mode == 0) { // data register direct not allowed with immediate form
			return 0;
		}
		ut16 opcode = 0x0840; // 0000 1000 01 mmm rrr
		opcode |= (ea.mode & 7) << 3;
		opcode |= (ea.reg & 7);
		int total = 2 + 2 + ea.ext_len; // opcode + imm word + EA ext
		if (size < total) {
			return 0;
		}
		buf[0] = (opcode >> 8) & 0xFF;
		buf[1] = opcode & 0xFF;
		// immediate extension word (only low 8 bits used)
		ut16 w = (ut16) (imm_val & 0xFF);
		buf[2] = (w >> 8) & 0xFF;
		buf[3] = w & 0xFF;
		int off = 4;
		if (ea.ext_len == 2) {
			buf[off++] = (ea.ext[0] >> 8) & 0xFF;
			buf[off++] = ea.ext[0] & 0xFF;
		} else if (ea.ext_len == 4) {
			buf[off++] = (ea.ext[0] >> 8) & 0xFF;
			buf[off++] = ea.ext[0] & 0xFF;
			buf[off++] = (ea.ext[1] >> 8) & 0xFF;
			buf[off++] = ea.ext[1] & 0xFF;
		}
		return total;
	}

	// Register-specified bit number form: BCHG Dn,<ea>
	int src_reg = parse_register (tokens[1]);
	if (src_reg < 0) {
		return 0;
	}
	EA ea;
	if (!parse_ea (tokens[2], &ea)) {
		return 0;
	}

	ut16 opcode = 0x0140; // 0000 0001 01 mmm rrr with reg in bits 11..9
	opcode |= (src_reg & 7) << 9;
	opcode |= (ea.mode & 7) << 3;
	opcode |= (ea.reg & 7);

	int total = 2 + ea.ext_len;
	if (size < total) {
		return 0;
	}
	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	if (ea.ext_len == 2) {
		buf[2] = (ea.ext[0] >> 8) & 0xFF;
		buf[3] = ea.ext[0] & 0xFF;
	} else if (ea.ext_len == 4) {
		buf[2] = (ea.ext[0] >> 8) & 0xFF;
		buf[3] = ea.ext[0] & 0xFF;
		buf[4] = (ea.ext[1] >> 8) & 0xFF;
		buf[5] = ea.ext[1] & 0xFF;
	}
	return total;
}

static int assemble_and(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}

	// tokens[0] = "AND"
	// tokens[1] = "D<src>"
	// tokens[2] = "D<dest>"

	int src_reg = parse_register (tokens[1]);
	int dest_reg = parse_register (tokens[2]);

	if (src_reg < 0 || dest_reg < 0) {
		return 0;
	}

	int sc = (size_code >= 0) ? size_code : 1; // default to word
	ut16 opcode = 0xC000;
	// AND <ea>,Dn encoding:
	// 1100 ddd ssm mmm rrr
	//  ddd = destination Dn (bits 11..9)
	//  ss  = size (00=byte,01=word,10=long) at bits 7..6
	//  mmm rrr = effective address (here data register direct)
	opcode |= (dest_reg << 9);     // Dn (destination)
	opcode |= (sc << 6);           // size in opmode field
	opcode |= (0 << 3);            // mode 000 = data register direct
	opcode |= src_reg;             // register for <ea>

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_or(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}

	// tokens[0] = "OR"
	// tokens[1] = "D<src>"
	// tokens[2] = "D<dest>"

	const int src_reg = parse_register (tokens[1]);
	const int dest_reg = parse_register (tokens[2]);

	if (src_reg < 0 || dest_reg < 0) {
		return 0;
	}

	// OR <ea>,Dn encoding (like AND):
	// 1000 ddd ssm mmm rrr
	// ddd = destination Dn, ss = size (00/01/10) in bits 7..6
	// mmm rrr = source effective address (data register direct here)
	int sc = (size_code >= 0) ? size_code : 1; // default word
	ut16 opcode = 0x8000;
	opcode |= (dest_reg << 9);   // destination Dn
	opcode |= (sc << 6);         // size
	opcode |= (0 << 3);          // mode 000 = data register direct
	opcode |= src_reg & 7;       // source reg

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_btst(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}

	// tokens[0] = "BTST"
	// tokens[1] = "D<src>"
	// tokens[2] = "D<dest>"

	const int src_reg = parse_register (tokens[1]);
	const int dest_reg = parse_register (tokens[2]);

	if (src_reg < 0 || dest_reg < 0) {
		return 0;
	}

	ut16 opcode = 0x0100;
	opcode |= (src_reg << 9);
	opcode |= (0 << 3); // dest_mode = 0
	opcode |= dest_reg & 7;

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

static int assemble_ori(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	// ORI #imm,<ea>
	if (num_tokens != 3) {
		return 0;
	}

	int sc = (size_code >= 0) ? size_code : 1; // default to word
	if (sc < 0 || sc > 2) {
		return 0;
	}

	int imm;
	if (!parse_immediate_token (tokens[1], &imm)) {
		return 0;
	}

	int mode = -1, reg = -1;
	int dreg = parse_register (tokens[2]);
	if (dreg >= 0) {
		mode = 0; // Dn
		reg = dreg & 7;
	} else {
		int areg = parse_addr_indirect (tokens[2]);
		if (areg >= 0) {
			mode = 2; // (An)
			reg = areg & 7;
		}
	}
	if (mode < 0) {
		return 0; // unsupported EA for now
	}

	// Base opcode for ORI: 0000 0000 ss mmm rrr
	ut16 opcode = 0x0000;
	opcode |= (sc & 3) << 6;      // size
	opcode |= (mode & 7) << 3;    // mode
	opcode |= (reg & 7);          // reg

	// Calculate total size and check buffer
	int total = 2; // opcode
	if (sc == 2) {
		total += 4; // long immediate
	} else {
		total += 2; // word immediate (used for byte and word sizes)
	}
	if (size < total) {
		return 0;
	}

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;

	if (sc == 2) {
		// long immediate
		ut32 v = (ut32)imm;
		buf[2] = (v >> 24) & 0xFF;
		buf[3] = (v >> 16) & 0xFF;
		buf[4] = (v >> 8) & 0xFF;
		buf[5] = v & 0xFF;
	} else {
		// word immediate; for .b only low 8 bits are significant
		ut16 v = (ut16)imm;
		buf[2] = (v >> 8) & 0xFF;
		buf[3] = v & 0xFF;
	}

	return total;
}

static int assemble_ror(char tokens[][256], int num_tokens, ut8 *buf, int size, int size_code) {
	if (size < 2 || num_tokens != 3) {
		return 0;
	}

	// tokens[0] = "ROR"
	// tokens[1] = "#<cnt>" | "D<src>" (count from register)
	// tokens[2] = "D<dest>"

	int dest_reg = parse_register (tokens[2]);
	if (dest_reg < 0) {
		return 0;
	}

	int sc = (size_code >= 0) ? size_code : 1; // default word
	if (sc < 0 || sc > 2) {
		return 0;
	}

	ut16 opcode = 0xE000; // shift/rotate group
	const int op_ror = 0x3; // ttt bits for ROR (bits 5..3)

	int imm_val = 0;
	if (parse_immediate_token (tokens[1], &imm_val)) {
		// Immediate count: 1..8 (8 encoded as 0)
		if (imm_val < 1 || imm_val > 8) {
			return 0;
		}
		if (imm_val == 8) {
			imm_val = 0;
		}
		opcode |= (imm_val & 7) << 9; // count
					      // bit8 = 0 for immediate (already 0)
	} else {
		// Try register-specified count variant: ROR size D<src>, D<dest>
		int src_reg = parse_register (tokens[1]);
		if (src_reg < 0) {
			return 0;
		}
		opcode |= (src_reg & 7) << 9; // count from Dx
		opcode |= (1 << 8);           // bit8=1 indicates register count
	}

	opcode |= (sc & 3) << 6;      // size bits
	opcode |= (op_ror & 7) << 3;  // operation = ROR
	opcode |= (dest_reg & 7);     // destination register

	buf[0] = (opcode >> 8) & 0xFF;
	buf[1] = opcode & 0xFF;
	return 2;
}

// Global array of instructions
static Instruction instruction_set[] = {
	{ "NOP", assemble_nop },
	{ "RESET", assemble_reset },
	{ "MOVEQ", assemble_moveq },
	{ "MOVE", assemble_move },
	{ "ADDQ", assemble_addq },
	{ "SUBQ", assemble_subq },
	{ "BEQ", assemble_beq },
	{ "BRA", assemble_bra },
	{ "BNE", assemble_bne },
	{ "EOR", assemble_eor },
	{ "TST", assemble_tst },
	{ "LEA", assemble_lea },
	{ "CLR", assemble_clr },
	{ "BCHG", assemble_bchg },
	{ "ORI", assemble_ori },
	{ "ROR", assemble_ror },
	{ "AND", assemble_and },
	{ "OR", assemble_or },
	{ "BTST", assemble_btst },
	{ NULL, NULL } // Sentinel value to mark the end of the array
};

// Main assemble_instruction function
static inline int m68kass(const char *instruction_str, ut8 *buf, int size) {
	// Convert instruction_str to uppercase
	char instr_upper[256];
	int i = 0;
	while (instruction_str[i] != '\0' && i < sizeof (instr_upper) - 1) {
		instr_upper[i] = toupper ((ut8)instruction_str[i]);
		i++;
	}
	instr_upper[i] = '\0';

	// Normalize original instruction (lowercase, collapse spaces)
	char norm[256];
	int ni = 0;
	int last_was_space = 0;
	const char *s = instruction_str;
	// Skip leading angle-bracketed token like "<asm>"
	if (*s == '<') {
		const char *p = s + 1;
		while (*p && *p != '>') {
			p++;
		}
		if (*p == '>') {
			s = p + 1;
		}
	}
	for (i = 0; s[i] && ni < (int)sizeof (norm) - 1; i++) {
		char c = s[i];
		if (c == '\t' || c == ',') {
			c = ' ';
		}
		// skip size suffixes like .b .w .l
		if (c == '.') {
			char next = s[i + 1];
			if (next == 'b' || next == 'w' || next == 'l' || next == 'B' || next == 'W' || next == 'L') {
				i++; // skip the size char too
				continue;
			}
		}
		if (c == ' ') {
			if (last_was_space) {
				continue;
			}
			last_was_space = 1;
		} else {
			last_was_space = 0;
			if (c >= 'A' && c <= 'Z') {
				c = c - 'A' + 'a';
			}
		}
		// skip angle brackets if present elsewhere
		if (c == '<' || c == '>') {
			continue;
		}
		norm[ni++] = c;
	}
	while (ni > 0 && norm[ni - 1] == ' ') {
		ni--;
	}
	norm[ni] = '\0';

	// Tokenize the instruction string
	char tokens[10][256]; // Up to 10 tokens
	int num_tokens = tokenize (instr_upper, tokens, 10);
	if (num_tokens == 0) {
		return 0; // Cannot parse instruction
	}

	// Strip size suffix from mnemonic token (e.g., ".b", ".w", ".l") and compute size_code
	int size_code = -1; // -1 means unspecified
	char *dot = strchr (tokens[0], '.');
	if (dot) {
		char sz = tolower ((unsigned char)dot[1]);
		if (sz == 'b') {
			size_code = 0;
		} else if (sz == 'w') {
			size_code = 1;
		} else if (sz == 'l') {
			size_code = 2;
		}
		*dot = '\0';
	}

	// Find the instruction in the instruction_set array
	for (i = 0; instruction_set[i].mnemonic != NULL; i++) {
		if (!strcmp (tokens[0], instruction_set[i].mnemonic)) {
			// Found the instruction, call its assembler function
			return instruction_set[i].assemble (tokens, num_tokens, buf, size, size_code);
		}
	}

	return 0;
}
#if 0
int main(int argc, char **argv) {
	ut8 out[4];
	if (argc < 2) {
		printf ("usage %s [m68kop]\n", argv[0]);
		return 1;
	}
	const char *text = argv[1];
	int len = assemble_instruction (text, out, sizeof (out));
	if (len > 0) {
		int i;
		for (i = 0; i < len; i++) {
			printf ("%02x", out[i]);
		}
		printf ("\n");
	}
	return 0;
}
#endif
