// formatex.inc by Slice

#include <a_samp>

//Dependencies added by dy1zan;
#include <sarputil>

#if defined FORMAT_EXTRA_TAGS
	#define FORMAT_TAGS  _, PlayerText3D, Text, Text3D, Menu, DB, DBResult, File, Float, FORMAT_EXTRA_TAGS
#else
	#define FORMAT_TAGS  _, PlayerText3D, Text, Text3D, Menu, DB, DBResult, File, Float
#endif

#if !defined FORMAT_BUFFER_SIZE
	#define FORMAT_BUFFER_SIZE  2048
#endif

#if !defined FORMAT_PRINT_BUFFER_SIZE
	#define FORMAT_PRINT_BUFFER_SIZE 512
#endif

#if !defined FORMAT_CUSTOM_SPEC_BUFFER_SIZE
	#define FORMAT_CUSTOM_SPEC_BUFFER_SIZE  512
#endif

#if !defined FORMAT_REPLACE_NATIVES
	#define FORMAT_REPLACE_NATIVES true
#endif

#define FormatSpecifier<'%1'>(%2[%3],%4)  FMAT@1:F@%1(%2[FORMAT_CUSTOM_SPEC_BUFFER_SIZE],FMAT@2:___unused,%4)
#define FMAT@2:___unused,%1[              %1[
#define FMAT@1:%1(%2)                     forward %1(%2); public %1(%2)

static stock
	     gs_CustomFormatFunctions[127] = {-1, ...},
	bool:gs_bIsInitialized = false
;

forward __fmt_funcinc();
public __fmt_funcinc() {
	new szOutput[1];
	
	format(szOutput, 0, "");
}

static stock InitializeFormatSpecifiers() {
	new
		szFunctionName[4 char] = !"F@_",
		iIndex,
		iFunctionAddress
	;
	
	gs_bIsInitialized = true;
	
	for (new c = '@'; c <= 'z'; c++) {
		// Skip the chars that can't be used in function names
		if (c == 'Z' + 1)
			c = '_';
		else if (c == '_' + 1)
			c = 'a';
		
		szFunctionName{2} = c;
		
		// Get the function's address if it exists
		if (-1 != (iIndex = funcidx(szFunctionName))) {
			#emit LCTRL		   1
			#emit NEG
			#emit MOVE.alt
			#emit ADD.C		   32
			#emit STOR.S.pri   iFunctionAddress
			#emit LREF.S.pri   iFunctionAddress
			#emit ADD
			#emit LOAD.S.alt   iIndex
			#emit SHL.C.alt	   3
			#emit ADD
			#emit STOR.S.pri   iFunctionAddress
			#emit LREF.S.pri   iFunctionAddress
			#emit STOR.S.pri   iFunctionAddress
			
			gs_CustomFormatFunctions[c] = iFunctionAddress;
		}
	}
}

stock formatex(szOutput[], iLength = sizeof(szOutput), const szFormatString[], {FORMAT_TAGS}:...) {
	static
		     s_szBuffer[FORMAT_BUFFER_SIZE],
		bool:s_bIsInCustomSpecifier = false
	;
	
	// If formatex is called inside a custom specifier, the original "format" will be used
	// to prevent having s_szBuffer overwritten. Specifiers shouldn't depend on other specifiers, anyway.
	if (s_bIsInCustomSpecifier) {
		new
			iNumArgs,
			i
		;
		
		#emit LOAD.S.pri  8
		#emit STOR.S.pri  iNumArgs
		#emit SHR.C.pri   2
		#emit STOR.S.pri  i

		while (--i >= 0) {
			#emit LOAD.S.pri  i
			#emit SHL.C.pri   2
			#emit ADD.C       12
			#emit MOVE.alt
			#emit LCTRL       5
			#emit ADD
			#emit LOAD.I
			#emit PUSH.pri
		}
		
		#emit LOAD.S.pri  iNumArgs
		#emit PUSH.pri
		#emit MOVE.alt
		#emit SYSREQ.C    format
		#emit CONST.pri   4
		#emit ADD
		#emit MOVE.alt
		#emit LCTRL       4
		#emit ADD
		#emit SCTRL       4
	} else {
		new
			iPos = -1,
			iArg = 12 + (3 * 4),
			iArgCount,
			iAddress,
			iArgValue,
			aiArgs[128],
			i
		;
	
		if (!gs_bIsInitialized)
			InitializeFormatSpecifiers();
	
		iLength = min(FORMAT_BUFFER_SIZE, iLength);
	
		s_szBuffer[0] = 0;
	
		strunpack(s_szBuffer, szFormatString);
		
		while (-1 != (iPos = strfind(s_szBuffer, !"%", _, ++iPos))) {
			while (s_szBuffer[++iPos]) {
				// Look for custom formats
				if (1 <= s_szBuffer[iPos] < sizeof(gs_CustomFormatFunctions) && gs_CustomFormatFunctions[s_szBuffer[iPos]] != -1) {
					new
						iFunc = gs_CustomFormatFunctions[s_szBuffer[iPos]]
					;
				
					static
						s_szCustomFormatBuffer[FORMAT_CUSTOM_SPEC_BUFFER_SIZE]
					;
				
					strdel(s_szBuffer, iPos - 1, iPos + 1);
				
					s_szCustomFormatBuffer[0] = 0;
				
					#emit LCTRL       5
					#emit LOAD.S.alt  iArg
					#emit ADD
					#emit LOAD.I
					#emit MOVE.alt
					#emit LOAD.I
					#emit PUSH.pri
					#emit PUSH.alt
				
					iArg += 4;
					
					s_bIsInCustomSpecifier = true;
				
					#emit PUSH.C      s_szCustomFormatBuffer
					#emit PUSH.C      12
					#emit LCTRL       6
					#emit ADD.C       28
					#emit PUSH.pri
					#emit LOAD.S.pri  iFunc
					#emit SCTRL       6
					
					s_bIsInCustomSpecifier = false;
				
					strins(s_szBuffer, s_szCustomFormatBuffer, iPos - 1);
				
					break;
				}
			
				switch (s_szBuffer[iPos]) {
					// Handled by the original format function
					case '*', 'i', 'd', 'x', 'h', 'c', 's', 'f', 'b', 'q': {
						// Get the argument address and save it for later
						#emit LCTRL        5
						#emit LOAD.S.alt  iArg
						#emit ADD
						#emit LOAD.I
						#emit STOR.S.pri  iAddress
						#emit MOVE.pri
						#emit ADD.C       4
						#emit STOR.S.pri  iArg
					
						aiArgs[iArgCount++] = iAddress;
					
						if (s_szBuffer[iPos] == '*')
							continue;
					
						break;
					}
				
					// Unsigned numbers
					case 'u': {
						new
							szBuffer[11]
						;
					
						#emit LCTRL       5
						#emit LOAD.S.alt  iArg
						#emit ADD
						#emit LOAD.I
						#emit LOAD.I
						#emit STOR.S.pri  iArgValue
						#emit MOVE.pri
						#emit ADD.C       4
						#emit STOR.S.pri  iArg
					
						strdel(s_szBuffer, iPos - 1, iPos + 1);
					
						if (!iArgValue) {
							strins(s_szBuffer, "0", iPos - 1);
						} else {
							new
								j = sizeof(szBuffer) - 1
							;
						
							while (iArgValue) {
								// szBuffer[--i]
								#emit ADDR.alt    szBuffer  // alt = *szBuffer
								#emit LOAD.S.pri  j         // pri = i
								#emit DEC.pri               // pri -= 1
								#emit STOR.S.pri  j         // i = pri
								#emit IDXADDR               // pri = alt + i * 4
								#emit PUSH.pri              // Store pri for later

								// Now do an unsigned divide on uValue then use both the quotient and remainder!
								#emit LOAD.S.pri  iArgValue // pri = uValue
								#emit CONST.alt   10
								#emit UDIV                  // pri = uValue / 10; alt = uValue % 10
								#emit STOR.S.pri  iArgValue // uValue = pri
								#emit CONST.pri   '0'
								#emit ADD                   // pri = '0' + (uValue % 10)
								#emit POP.alt               // alt = szBuffer[i]
								#emit STOR.I                // szBuffer[i] = pri
							}
						
							strins(s_szBuffer, szBuffer[j], iPos - 1);
						}
					}
				
					case '0' .. '9', ' ', '.':
						continue;
				
					case '%':
						break;
			
					default: {
						break;
					}
				}
			}
		}
	
		i = iArgCount;
	
		// Push the arguments we stored above
		while (--i >= 0) {
			#emit ADDR.alt    aiArgs
			#emit LOAD.S.pri  i
			#emit LIDX
			#emit PUSH.pri
			#emit STOR.S.pri iAddress
		}
	
		// New format specifier
		#emit PUSH.C     s_szBuffer
	
		// Max length
		#emit PUSH.S     iLength
	
		// Output string
		#emit PUSH.S     szOutput
	
		// Argument count
		#emit LOAD.S.pri iArgCount
		#emit SHL.C.pri  2
		#emit ADD.C      12
		#emit PUSH.pri
	
		// Save the argument count for later
		#emit MOVE.alt
	
		// Call format (duh)
		#emit SYSREQ.C   format
	
		// Add 4 to the argument count
		#emit CONST.pri  4
		#emit ADD
		#emit MOVE.alt
	
		// Remove <argument count> from the stack
		#emit LCTRL      4
		#emit ADD
		#emit SCTRL      4
	}
	
	// Return in case anyone uses it
	return 1;
}

stock printfex(const szFormatString[], {FORMAT_TAGS}:...) {
	const
		iBufferSize = FORMAT_PRINT_BUFFER_SIZE
	;
	
	static
		s_szBuffer[FORMAT_PRINT_BUFFER_SIZE]
	;
	
	new
		iNumArgs = numargs(),
		i = iNumArgs - 1
	;
	
	while (--i >= 0) {
		#emit LOAD.S.pri  i
		#emit SHL.C.pri   2
		#emit ADD.C       16
		#emit MOVE.alt
		#emit LCTRL       5
		#emit ADD
		#emit LOAD.I
		#emit PUSH.pri
	}
	
	#emit PUSH.S      szFormatString
	#emit PUSH.C      iBufferSize
	#emit PUSH.C      s_szBuffer
	#emit LOAD.S.pri  iNumArgs
	#emit SHL.C.pri   2
	#emit ADD.C       8
	#emit PUSH.pri
	#emit LCTRL       6
	#emit ADD.C       28
	#emit PUSH.pri
	#emit CONST.pri   formatex
	#emit SCTRL       6
	
	print(s_szBuffer);
	return 1;
}

// -----------------------------------------------------------------------------------------------------
// Specifiers
// -----------------------------------------------------------------------------------------------------

// Inline color
FormatSpecifier<'C'>(output[], color) {
	format(output, sizeof(output), "{%06x}", color >>> 8);
}

// Player name
FormatSpecifier<'p'>(output[], playerid) {
	if (0 <= playerid < GetMaxPlayers() && IsPlayerConnected(playerid))
		format(output, sizeof output, "%s", Util:PlayerICName(playerid));
	else
		strcat(output, "<UNKNOWN>");
}

// Player name and color
FormatSpecifier<'P'>(output[], playerid) {
	if (0 <= playerid < GetMaxPlayers() && IsPlayerConnected(playerid)) {
		format(output, sizeof(output), "{%06x}", GetPlayerColor(playerid) >>> 8);
		format(output, sizeof output, "%s%s", output, Util:PlayerICName(playerid));
	} else
		strcat(output, "{FFFFFF}<UNKNOWN>");
}

// Weapon name
FormatSpecifier<'W'>(output[], weapon) {
	static const
		s_WeaponNames[][] = {
			{!"Fists"          		}, {!"Brass Knuckles"   }, {!"Golf club"       		}, //  0,  1,  2
			{!"Nightstick"     		}, {!"Knife"       		}, {!"Baseball Bat"         }, //  3,  4,  5
			{!"Shovel"         		}, {!"Pool Cue"    		}, {!"Katana"         		}, //  6,  7,  8
			{!"Chainsaw"       		}, {!"Purple Dildo"     }, {!"Dildo"       			}, //  9, 10, 11
			{!"Vibrator"       		}, {!"Silver Vibrator"	}, {!"Flowers"        		}, // 12, 13, 14
			{!"Cane"           		}, {!"Grenade"     		}, {!"Tear Gas"       		}, // 15, 16, 17
			{!"Molotov Cocktail"	}, {!""            		}, {!""               		}, // 18,   ,   
			{!""               		}, {!"9mm"         		}, {!"Silenced 9mm"			}, //   , 22, 23
			{!"Desert Eagle"    	}, {!"Shotgun"     		}, {!"Sawnoff Shotgun"      }, // 24, 25, 26
			{!"Combat Shotgun" 		}, {!"Micro Uzi"   		}, {!"MP5"            		}, // 27, 28, 29
			{!"AK-47"          		}, {!"M4"          		}, {!"Tec-9"          		}, // 30, 31, 32
			{!"Country Rifle"   	}, {!"Sniper Rifle"		}, {!"RPG" 					}, // 33, 34, 35
			{!"HS Rocket"			}, {!"Flamethrower"		}, {!"Minigun"        		}, // 36, 37, 38
			{!"Satchel Charge"  	}, {!"Detonator"   		}, {!"Spraycan"       		}, // 39, 40, 41
			{!"Fire Extinguisher"	}, {!"Camera"      		}, {!"Nightvision Goggles"  }, // 42, 43, 44
			{!"Thermal Goggles"     }, {!"Parachute"   		}, {!"Fake Pistol"    		}, // 45, 46, 47
			{!""               		}, {!"Vehicle"     		}, {!"Helikill"       		}, //   , 49, 50
			{!"Explosion"      		}, {!""            		}, {!"Drown"          		}, // 51,   , 53
			{!"Collision"      		}, {!"Splat"       		}, {!"Unknown"        		}  // 54, 55, 56
		}
	;
	
	if (0 <= weapon < sizeof(s_WeaponNames))
		strcat(output, s_WeaponNames[weapon]);
	else
		strcat(output, "Unknown");
}

// Weapon name, lower-case singular (for sentences)
FormatSpecifier<'w'>(output[], weapon) {
	static const
		s_WeaponNamesLowercaseSingular[][] = {
			{!"fists"             	}, {!"brass knuckles"     }, {!"a golfclub"       }, //  0,  1,  2
			{!"a nightstick"      	}, {!"a knife"       		}, {!"a baseball bat"            }, //  3,  4,  5
			{!"a shovel"          	}, {!"a pool cue"    		}, {!"a katana"         }, //  6,  7,  8
			{!"a chainsaw"        	}, {!"a purple dildo"     }, {!"a dildo"       }, //  9, 10, 11
			{!"a vibrator"        	}, {!"a silver vibrator"  }, {!"flowers"          }, // 12, 13, 14
			{!"a cane"            	}, {!"a grenade"     		}, {!"tear gas"          }, // 15, 16, 17
			{!"a molotov cocktail"	}, {!""              		}, {!""                 }, // 18,   ,   
			{!""                  	}, {!"a 9mm"         		}, {!"a silenced 9mm"}, //   , 22, 23
			{!"a desert eagle"    	}, {!"a shotgun"     		}, {!"swanoff shotgun"         }, // 24, 25, 26
			{!"a combat shotgun"  	}, {!"a micro uzi"      	}, {!"an mp5"           }, // 27, 28, 29
			{!"an ak-47"          	}, {!"an m4"         		}, {!"a tec-9"          }, // 30, 31, 32
			{!"a country rifle"   	}, {!"a sniper rifle"		}, {!"an rpg" }, // 33, 34, 35
			{!"a hs rpg" 		  	}, {!"a flamethrower"		}, {!"a minigun"        }, // 36, 37, 38
			{!"a satchel charge"  	}, {!"a detonator"   		}, {!"a spraycan"       }, // 39, 40, 41
			{!"a fire extinguisher" }, {!"a camera"      		}, {!"nightvision"      }, // 42, 43, 44
			{!"an infrared"       	}, {!"a parachute"   		}, {!"a fake pistol"    }, // 45, 46, 47
			{!""                  	}, {!"a vehicle"     		}, {!"a helikill"       }, //   , 49, 50
			{!"an explosion"      	}, {!""              		}, {!"drowning"         }, // 51,   , 53
			{!"a collision"       	}, {!"a splat"       		}, {!"something unknown"}  // 54, 55, 56
		}
	;
	
	if (0 <= weapon < sizeof(s_WeaponNamesLowercaseSingular))
		strcat(output, s_WeaponNamesLowercaseSingular[weapon]);
	else
		strcat(output, "something unknown");
}

// Vehicle name
FormatSpecifier<'v'>(output[], modelid) {
	static const
		s_VehicleNames[][] = {	
			{!"Landstalker"      }, {!"Bravura"          }, {!"Buffalo"          }, {!"Linerunner"       },
			{!"Perrenial"        }, {!"Sentinel"         }, {!"Dumper"           }, {!"Firetruck"        },
			{!"Trashmaster"      }, {!"Stretch"          }, {!"Manana"           }, {!"Infernus"         },
			{!"Voodoo"           }, {!"Pony"             }, {!"Mule"             }, {!"Cheetah"          },
			{!"Ambulance"        }, {!"Leviathan"        }, {!"Moonbeam"         }, {!"Esperanto"        },
			{!"Taxi"             }, {!"Washington"       }, {!"Bobcat"           }, {!"Mr Whoopee"       },
			{!"BF Injection"     }, {!"Hunter"           }, {!"Premier"          }, {!"Enforcer"         },
			{!"Securicar"        }, {!"Banshee"          }, {!"Predator"         }, {!"Bus"              },
			{!"Rhino"            }, {!"Barracks"         }, {!"Hotknife"         }, {!"Trailer 1"        },
			{!"Previon"          }, {!"Coach"            }, {!"Cabbie"           }, {!"Stallion"         },
			{!"Rumpo"            }, {!"RC Bandit"        }, {!"Romero"           }, {!"Packer"           },
			{!"Monster"          }, {!"Admiral"          }, {!"Squalo"           }, {!"Seasparrow"       },
			{!"Pizzaboy"         }, {!"Tram"             }, {!"Trailer 2"        }, {!"Turismo"          },
			{!"Speeder"          }, {!"Reefer"           }, {!"Tropic"           }, {!"Flatbed"          },
			{!"Yankee"           }, {!"Caddy"            }, {!"Solair"           }, {!"Berkley's RC Van" },
			{!"Skimmer"          }, {!"PCJ-600"          }, {!"Faggio"           }, {!"Freeway"          },
			{!"RC Baron"         }, {!"RC Raider"        }, {!"Glendale"         }, {!"Oceanic"          },
			{!"Sanchez"          }, {!"Sparrow"          }, {!"Patriot"          }, {!"Quad"             },
			{!"Coastguard"       }, {!"Dinghy"           }, {!"Hermes"           }, {!"Sabre"            },
			{!"Rustler"          }, {!"ZR-350"           }, {!"Walton"           }, {!"Regina"           },
			{!"Comet"            }, {!"BMX"              }, {!"Burrito"          }, {!"Camper"           },
			{!"Marquis"          }, {!"Baggage"          }, {!"Dozer"            }, {!"Maverick"         },
			{!"News Chopper"     }, {!"Rancher"          }, {!"FBI Rancher"      }, {!"Virgo"            },
			{!"Greenwood"        }, {!"Jetmax"           }, {!"Hotring"          }, {!"Sandking"         },
			{!"Blista Compact"   }, {!"Police Maverick"  }, {!"Boxville"         }, {!"Benson"           },
			{!"Mesa"             }, {!"RC Goblin"        }, {!"Hotring Racer A"  }, {!"Hotring Racer B"  },
			{!"Bloodring Banger" }, {!"Rancher"          }, {!"Super GT"         }, {!"Elegant"          },
			{!"Journey"          }, {!"Bike"             }, {!"Mountain Bike"    }, {!"Beagle"           },
			{!"Cropdust"         }, {!"Stunt"            }, {!"Tanker"           }, {!"Roadtrain"        },
			{!"Nebula"           }, {!"Majestic"         }, {!"Buccaneer"        }, {!"Shamal"           },
			{!"Hydra"            }, {!"FCR-900"          }, {!"NRG-500"          }, {!"HPV1000"          },
			{!"Cement Truck"     }, {!"Tow Truck"        }, {!"Fortune"          }, {!"Cadrona"          },
			{!"FBI Truck"        }, {!"Willard"          }, {!"Forklift"         }, {!"Tractor"          },
			{!"Combine"          }, {!"Feltzer"          }, {!"Remington"        }, {!"Slamvan"          },
			{!"Blade"            }, {!"Freight"          }, {!"Streak"           }, {!"Vortex"           },
			{!"Vincent"          }, {!"Bullet"           }, {!"Clover"           }, {!"Sadler"           },
			{!"Firetruck LA"     }, {!"Hustler"          }, {!"Intruder"         }, {!"Primo"            },
			{!"Cargobob"         }, {!"Tampa"            }, {!"Sunrise"          }, {!"Merit"            },
			{!"Utility"          }, {!"Nevada"           }, {!"Yosemite"         }, {!"Windsor"          },
			{!"Monster A"        }, {!"Monster B"        }, {!"Uranus"           }, {!"Jester"           },
			{!"Sultan"           }, {!"Stratum"          }, {!"Elegy"            }, {!"Raindance"        },
			{!"RC Tiger"         }, {!"Flash"            }, {!"Tahoma"           }, {!"Savanna"          },
			{!"Bandito"          }, {!"Freight Flat"     }, {!"Streak Carriage"  }, {!"Kart"             },
			{!"Mower"            }, {!"Duneride"         }, {!"Sweeper"          }, {!"Broadway"         },
			{!"Tornado"          }, {!"AT-400"           }, {!"DFT-30"           }, {!"Huntley"          },
			{!"Stafford"         }, {!"BF-400"           }, {!"Newsvan"          }, {!"Tug"              },
			{!"Trailer 3"        }, {!"Emperor"          }, {!"Wayfarer"         }, {!"Euros"            },
			{!"Hotdog"           }, {!"Club"             }, {!"Freight Carriage" }, {!"Trailer 3"        },
			{!"Andromada"        }, {!"Dodo"             }, {!"RC Cam"           }, {!"Launch"           },
			{!"Police Car (LSPD)"}, {!"Police Car (SFPD)"}, {!"Police Car (LVPD)"}, {!"Police Ranger"    },
			{!"Picador"          }, {!"S.W.A.T. Van"     }, {!"Alpha"            }, {!"Phoenix"          },
			{!"Glendale"         }, {!"Sadler"           }, {!"Luggage Trailer A"}, {!"Luggage Trailer B"},
			{!"Stair Trailer"    }, {!"Boxville"         }, {!"Farm Plow"        }, {!"Utility Trailer"  }
		}
	;
	
	if (0 <= (modelid -= 400) < sizeof(s_VehicleNames))
		strcat(output, s_VehicleNames[modelid]);
	else
		strcat(output, "Unknown");
}

// Unsigned 4-byte hex
FormatSpecifier<'X'>(output[], value) {
	format(output, sizeof(output), "%02x%06x", value >>> 24, value & 0xFFFFFF);
}

// Do this last so the specifiers always use the native function
#if FORMAT_REPLACE_NATIVES
	#define format  formatex
	#define printf  printfex
#endif