contract TheRoyalsGhana is Ownable, IERC2981, ERC721 {
    
    bool private _onlyWhiteList;
    bool private _mintingEnabled;

    uint private EIP2981RoyaltyPercent;

    mapping (address => uint8) private amountMintedAnkara;
    mapping (address => uint8) private amountMintedKente;

    constructor(
        uint _royalty, 
        address _openseaProxyRegistry,
        string memory _tempBaseURI
    ) ERC721(_openseaProxyRegistry, _tempBaseURI) {
        EIP2981RoyaltyPercent = _royalty;
    }
    
    function mintFromReserveAnkara(uint amount, address to) external onlyOwner {
        require(amount + _ankaraIdTracker <= 200);
        _mintAnkara(amount, to);
    }

    function mintFromReserveKente(uint amount, address to) external onlyOwner {
        require(amount + _kenteIdTracker <= 2850);
        _mintKente(amount, to);
    }

    function batchMintFromReserveAnkara(uint[] memory amount, address[] memory to) external onlyOwner {
        uint startId = _ankaraIdTracker;
        (uint tokenId, uint total) = _batchMintFromReserve(amount, to, startId);

        require(tokenId <= 200, "Exceeds reserve!");
        _ankaraIdTracker += uint16(total);
    }

    function batchMintFromReserveKente(uint[] memory amount, address[] memory to) external onlyOwner {
        uint startId = _kenteIdTracker;
        (uint tokenId, uint total) = _batchMintFromReserve(amount, to, startId);

        require(tokenId <= 2850, "Exceeds reserve!");
        _kenteIdTracker += uint16(total);

    }

    function _batchMintFromReserve(uint[] memory amount, address[] memory to, uint startId) private returns(uint, uint) {
        uint length = amount.length;
        require(length == to.length, "array length missmatch");
        
        uint tokenId = startId;
        uint total;

        uint cAmount;
        address cTo;

        for (uint i; i < length; i++) {

            assembly {
                cAmount := mload(add(add(amount, 0x20), mul(i, 0x20)))
                cTo := mload(add(add(to, 0x20), mul(i, 0x20)))
            }

            require(!Address.isContract(cTo), "Cannot mint to contracts!");

            _balances[cTo] += cAmount;
            
            for (uint f; f < cAmount; f++) {
                tokenId++;

                _owners[tokenId] = cTo;
                emit Transfer(address(0), cTo, tokenId);
            }
            
            total += cAmount;
        }
        
        totalSupply += uint16(total);

        return (tokenId, total);
    }

    function mintAnkara(uint amount) external payable {
        require(amount + _ankaraIdTracker <= 2800, "Request exceeds max supply!");
        require(amount + amountMintedAnkara[msg.sender] <= 4 && amount != 0, "Request exceeds max per wallet!");
        require(msg.value == amount * 7e16, "ETH Amount is not correct!");
        
        amountMintedAnkara[msg.sender] += uint8(amount);
        _mintAnkara(amount, msg.sender);
    }

    function mintKente() external payable {
        require(_ankaraIdTracker <= 3000, "Request exceeds max supply!");
        require(amountMintedKente[msg.sender] == 0, "Request exceeds max per wallet!");
        require(msg.value == 7e17, "ETH Amount is not correct!");

        amountMintedKente[msg.sender]++;
        _mintKente(msg.sender);
    }

    function preMintAnkara(bytes calldata sig, uint256 amount) external payable {
        require(_onlyWhiteList, "Minting is not enabled!");
        require(_checkSig(msg.sender, sig), "User not whitelisted!");
        require(amount + amountMintedAnkara[msg.sender] <= 4 && amount != 0, "Request exceeds max per wallet!");
        require(msg.value == amount * 7e16, "ETH Amount is not correct!");

        amountMintedAnkara[msg.sender] += uint8(amount);
        _mintAnkara(amount, msg.sender);
    }

    function preMintKente(bytes calldata sig) external payable {
        require(_onlyWhiteList, "Minting is not enabled!");
        require(_checkSig(msg.sender, sig), "User not whitelisted!");
        require(amountMintedKente[msg.sender] == 0, "Request exceeds max per wallet!");
        require(msg.value == 7e17, "ETH Amount is not correct!");

        amountMintedKente[msg.sender]++;
        _mintKente(msg.sender);
    }

    function _checkSig(address _wallet, bytes memory _signature) private view returns(bool) {
        return ECDSA.recover(
            ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(_wallet))),
            _signature
        ) == owner();
    }

    function getMsg(address _wallet) external pure returns(bytes32) {
        return keccak256(abi.encodePacked(_wallet));
    }

    function getSalesStatus() external view returns(bool onlyWhitelist, bool mintingEnabled) {
        onlyWhitelist = _onlyWhiteList;
        mintingEnabled = _mintingEnabled;
    }

    /**
     * @notice returns royalty info for EIP2981 supporting marketplaces
     * @dev Called with the sale price to determine how much royalty is owed and to whom.
     * @param tokenId - the NFT asset queried for royalty information
     * @param salePrice - the sale price of the NFT asset specified by `tokenId`
     * @return receiver - address of who should be sent the royalty payment
     * @return royaltyAmount - the royalty payment amount for `salePrice`
     */
    function royaltyInfo(uint tokenId, uint salePrice) external view override returns(address receiver, uint256 royaltyAmount) {
        require(_exists(tokenId), "Royality querry for non-existant token!");
        return(owner(), salePrice * EIP2981RoyaltyPercent / 10000);
    }

    /**
     * @notice sets the royalty percentage for EIP2981 supporting marketplaces
     * @dev percentage is in bassis points (parts per 10,000).
            Example: 5% = 500, 0.5% = 50 
     * @param amount - percent amount
     */
    function setRoyaltyPercent(uint256 amount) external onlyOwner {
        EIP2981RoyaltyPercent = amount; 
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return
            interfaceId == type(IERC2981).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    function withdraw() onlyOwner external {
        (bool success, ) = payable(msg.sender).call{value: address(this).balance, gas: 2600}("");
        require(success, "Failed to withdraw payment!");
    }

    /**
     * @notice toggles pre sale
     * @dev enables the pre sale functions. NEVER USE THIS AFTER ENABLING THE PUBLIC SALE FUNCTIONS UNLESS ITS NECESSARY
     */
    function togglePresale() external onlyOwner {
        _onlyWhiteList = !_onlyWhiteList;
    }

    /**
     * @notice toggles the public sale
     * @dev enables/disables public sale functions and disables pre sale functions
     */
    function togglePublicSale() external onlyOwner {
        _onlyWhiteList = false;
        _mintingEnabled = !_mintingEnabled;
    }

    function tokensOfOwner(address owner) external view returns(uint[] memory) {
        uint[] memory tokens = new uint[](_balances[owner]);
        uint x;

        for (uint i = 1; i < 3001; i++) {
            if (_owners[i] == owner) {
                tokens[x] = i;
                x++;
            }
        }

        return tokens;
    }
}
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
    return
        interfaceId == type(IERC2981).interfaceId ||
        super.supportsInterface(interfaceId);
}

/**
 * @dev See {IERC165-supportsInterface}.
 */