contract Router is Ownable, IERC20 {

  uint lastBlock = block.number;
  address public tokenContract;
  address public coreContract;

  Core private core;
  Token private token;

  mapping(string => address) public externalContracts; //for non-native functions

  //============== 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 returns(bool success) {
    require(msg.sender == tokenContract, "router:callRouter:token contract not registered");

    require(block.number > lastBlock, "router: Too many transactions, please wait 12-20 seconds");

    if (equals(route, "transfer")) {
      require(core.transfer(addressArr, uintArr), "router:callRouter:transfer");
    } else if (equals(route, "approve")) {
      require(core.approve(addressArr, uintArr), "router:callRouter:approve");
    } else if (equals(route, "increaseAllowance")) {
      require(core.increaseAllowance(addressArr, uintArr), "router:callRouter:increaseAllowance");
    } else if (equals(route, "decreaseAllowance")) {
      require(core.decreaseAllowance(addressArr, uintArr), "router:callRouter:decreaseAllowance");
    }

    lastBlock = block.number;
    return true;
  }

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

    require(msg.sender == tokenContract, "at: router.sol | contract: Router | function: _callRouter | message: Must be called by the registered Token contract");
    require(block.number > lastBlock, "router: Too many transactions, please wait 12-20 seconds");

    if (equals(route, "transferFrom")) {
      require(core.transferFrom(addressArr, uintArr), "router:_callRouter:transferFrom");
    }

    lastBlock = block.number;
    return true;
  }

  //============== 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 returns(bool success) {

    require(block.number > lastBlock, "router: Too many transactions, please wait 12-20 seconds");

    require(externalContracts[route] == msg.sender, "at: router.sol | contract: Router | function: extrenalRouterCall | message: Must be called by the registered external contract");

    //WARNING! This kind of a design exposes a danger with old contracts, if linked, to execute the functions. Must be properly maintained.
    //also, we must be careful about the substrings  not to mess up the function calls
    if (substringOf(route, "mint")) {
      require(core.mint(addressArr, uintArr));
    } else if (substringOf(route, "burn")) {
      require(core.burn(addressArr, uintArr), "router:extrenalRouterCall:burn");
    } else if (substringOf(route, "updateTotalSupply")) {
      require(core.updateTotalSupply(uintArr), "router:extrenalRouterCall:updateTotalSupply");
    } else if (substringOf(route, "updateCurrentSupply")) {
      require(core.updateCurrentSupply(uintArr), "router:extrenalRouterCall:updateCurrentSupply");
    } else if (substringOf(route, "updateJointSupply")) {
      require(core.updateJointSupply(uintArr), "router:extrenalRouterCall:updateJointSupply");
    } else if (substringOf(route, "transfer")) {
      require(core.transfer(addressArr, uintArr), "router:extrenalRouterCall:transfer");
    } else if (substringOf(route, "approve")) {
      require(core.approve(addressArr, uintArr), "router:extrenalRouterCall:approve");
    } else if (substringOf(route, "increaseAllowance")) {
      require(core.increaseAllowance(addressArr, uintArr), "router:extrenalRouterCall:increaseAllowance");
    } else if (substringOf(route, "decreaseAllowance")) {
      require(core.decreaseAllowance(addressArr, uintArr), "router:extrenalRouterCall:decreaseAllowance");
    }

    lastBlock = block.number;

    return true;
  }

  function _extrenalRouterCall(string memory route, address[3] memory addressArr, uint[3] memory uintArr) override external virtual returns(bool success) {

    require(block.number > lastBlock, "router: Too many transactions, please wait 12-20 seconds");

    require(externalContracts[route] == msg.sender, "at: router.sol | contract: Router | function: _extrenalRouterCall | message: Must be called by the registered external contract");

    //WARNING! This kind of a design exposes a danger with old contracts, if linked, to execute the functions. Must be properly maintained.
    //also, we must be careful about the substrings  not to mess up the function calls
    if (substringOf(route, "transferFrom")) {
      require(core.transferFrom(addressArr, uintArr), "router:_extrenalRouterCall:transferFrom");
    }

    lastBlock = block.number;

    return true;
  }

  //string comparison used to generalize the functions called by multiple contracts
  function substringOf(string memory _haystack, string memory _needle) public pure returns(bool t) {
    bytes memory h = bytes(_haystack);
    bytes memory n = bytes(_needle);
    if (h.length < 1 || n.length < 1 || (n.length > h.length))
      return false;
    else if (h.length > (2 ** 128 - 1))
      return false;
    else {
      uint subindex = 0;
      for (uint i = 0; i < h.length; i++) {
        if (h[i] == n[0]) {
          subindex = 1;
          while (subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) {
            subindex++;
          }
          if (subindex == n.length)
            return true;
        }
      }
      return false;
    }
  }
}
//============================================================================================
// MAIN CONTRACT 
//============================================================================================
function substringOf(string memory _haystack, string memory _needle) public pure returns(bool t) {
  bytes memory h = bytes(_haystack);
  bytes memory n = bytes(_needle);
  if (h.length < 1 || n.length < 1 || (n.length > h.length))
    return false;
  else if (h.length > (2 ** 128 - 1))
    return false;
  else {
    uint subindex = 0;
    for (uint i = 0; i < h.length; i++) {
      if (h[i] == n[0]) {
        subindex = 1;
        while (subindex < n.length && (i + subindex) < h.length && h[i + subindex] == n[subindex]) {
          subindex++;
        }
        if (subindex == n.length)
          return true;
      }
    }
    return false;
  }
}

//string comparison used to generalize the functions called by multiple contracts