contract Router is Ownable, IERC20 {

	address public tokenContract;
	address public coreContract;
	Core private core;
	Token private token;

	mapping(string => address) public externalContracts; //for non-native functions
	mapping(address => bool) mutex; //against reentrancy attacks

	modifier onlyPayloadSize(uint size) {
      if(msg.data.length < size + 4) {
        revert();
      }
      _;
    }
	

	//============== NATIVE FUNCTIONS START HERE ==================================================
	//These functions should never change when introducing a new version of a router.
	//Router is expected to constantly change, and the code should be written under 
	//the "NON-CORE FUNCTIONS TO BE CODED BELOW".

	function equals(string memory a, string memory b) internal view virtual returns(bool isEqual) {
		return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
	}

	function currentTokenContract() override external view virtual returns(address routerAddress) {
		return tokenContract;
	}

	function currentCoreContract() override external view virtual returns(address routerAddress) {
		return coreContract;
	}

	function getExternalContractAddress(string memory contractName) override external view virtual returns(address routerAddress) {
		return externalContracts[contractName];
	}

	//function is not needed if token address is hard-coded in a constructor
	function setNewTokenContract(address newTokenAddress) onlyOwner public virtual returns(bool success) {
		tokenContract = newTokenAddress;
		token = Token(tokenContract);
		return true;
	}

	function setNewCoreContract(address newCoreAddress) onlyOwner public virtual returns(bool success) {
		coreContract = newCoreAddress;
		core = Core(coreContract);
		return true;
	}

	function setNewExternalContract(string memory contractName, address newContractAddress) onlyOwner public virtual returns(bool success) {
		externalContracts[contractName] = newContractAddress;
		return true;
	}

	function callRouter(string memory route, address[2] memory addressArr, uint[2] memory uintArr) override external virtual onlyPayloadSize(2 * 32) returns(bool success) {
		require(msg.sender == tokenContract, "at: router.sol | contract: Router | function: callRouter | message: Must be called by the registered Token contract");

        require(!mutex[addressArr[0]]);
        mutex[addressArr[0]] = true;

		if (equals(route, "transfer")) {
			if(!core.transfer(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "approve")) {
			if(!core.approve(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "increaseAllowance")) {
			if(!core.increaseAllowance(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "decreaseAllowance")) {
			if(!core.decreaseAllowance(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		}
		
        mutex[addressArr[0]] = false;

		return true;
	}

	function _callRouter(string memory route, address[3] memory addressArr, uint[3] memory uintArr) override external virtual onlyPayloadSize(2 * 32) returns(bool success) {

		require(msg.sender == tokenContract, "at: router.sol | contract: Router | function: _callRouter | message: Must be called by the registered Token contract");
		
        require(!mutex[addressArr[0]]);
        mutex[addressArr[0]] = true;

		if (equals(route, "transferFrom")) {
			if(!core.transferFrom(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		}
		
        mutex[addressArr[0]] = false;

		return true;
	}
	
	function revertWithMutex(address userAddress) private{
        mutex[userAddress] = false;
        require(mutex[userAddress], "at: router.sol | contract: Router | function: revertWithMutex | message: Prevented multiple calls with the mutex, your previous call must end or cancel");
	}
	
	//============== NATIVE FUNCTIONS END HERE ==================================================


	//=============== NON-NATIVE ROUTES TO BE CODED BELOW =======================================
        // This code is a subject to a change, should we decide to alter anything.
        // We can also design another external router, possibilities are infinite.
    
    
	function extrenalRouterCall(string memory route, address[2] memory addressArr, uint[2] memory uintArr) override external virtual onlyPayloadSize(2 * 32) returns(bool success) {
	    
        require(!mutex[addressArr[0]]);
        mutex[addressArr[0]] = true;
	    
		if (equals(route, "mint")) {
			require(externalContracts["mint"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'mint' contract");
			if(!core.mint(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "burn")) {
			require(externalContracts["burn"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'burn' contract");
			if(!core.burn(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "mint2")) {
			require(externalContracts["mint"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'mint' contract");
			if(!core.mint(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "burn2")) {
			require(externalContracts["burn"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'burn' contract");
			if(!core.burn(addressArr, uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals(route, "updateTotalSupply")){
			require(externalContracts["updateTotalSupply"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'updateTotalSupply' contract");
			if(!core.updateTotalSupply(uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals (route, "updateCurrentSupply")){
			require(externalContracts["updateCurrentSupply"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'updateCurrentSupply' contract");
			if(!core.updateCurrentSupply(uintArr)) revertWithMutex(addressArr[0]);
		} else if (equals (route, "updateJointSupply")){
			require(externalContracts["updateJointSupply"] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external 'updateJointSupply' contract");
			if(!core.updateJointSupply(uintArr)) revertWithMutex(addressArr[0]);
		}

        mutex[addressArr[0]] = false;

		return true;
	}

}
//============================================================================================
// MAIN CONTRACT 
//============================================================================================
function equals(string memory a, string memory b) internal view virtual returns(bool isEqual) {
	return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
}

//============== NATIVE FUNCTIONS START HERE ==================================================
//These functions should never change when introducing a new version of a router.
//Router is expected to constantly change, and the code should be written under 
//the "NON-CORE FUNCTIONS TO BE CODED BELOW".