contract NftSale is Ownable{
	using SafeERC20 for IERC20;

	address payable public ethReceiver;
	mapping(address => uint) public presaleMinted;
	uint allowedToMintPresale;
	uint presaleStart;
	uint saleStart;
	INFT public nft;

	address immutable public verifyAddress = 0xdFFf98Bdf70A814dF3333CbaA558161D35390044;

	constructor(
		address payable _ethReceiver,
		address _nftAddress,
		uint _presaleStart,
		uint _saleStart,
		uint _allowedToMintPresale
	) {
		ethReceiver = _ethReceiver;
		presaleStart = _presaleStart;
		saleStart = _saleStart;
		allowedToMintPresale = _allowedToMintPresale;
		nft = INFT(_nftAddress);
	}

	// onlyOwner methods

	function setNewEthReceiver(address _newEthReceiver) external onlyOwner {
		ethReceiver = payable(_newEthReceiver);
	}

	function rescueToken(address token, address to, uint256 amount) external onlyOwner {
        IERC20(token).safeTransfer(to, amount);
    }

	function renounceOwnership() public view override onlyOwner {
        revert("renouncing ownership is blocked");
    }

	function rescueETH(address payable to, uint256 value) external onlyOwner {
        (bool success, ) = to.call{value: value}("");
        require(success, "ETH_TRANSFER_FAILED");
    }

	// buy method
	
	function presaleBuyWithSignature(
		uint tokenId, 
		bytes memory _signature
	) public payable {
		require(verify(_signature), "invalid signature");
		require(block.timestamp <= saleStart, "presale ended");
		require(block.timestamp >= presaleStart, "presale has not started");
		require(presaleMinted[msg.sender] < allowedToMintPresale, "limit exceeded");

		(, uint price) = nft.tokenTypeAndPrice(tokenId);
		require(price == msg.value, "wrong price");
		
		nft.mint(msg.sender, tokenId);
		presaleMinted[msg.sender]++;
		_safeTransferETH(ethReceiver, address(this).balance);
	}

	function buy(
		uint tokenId
	) public payable {
		require(block.timestamp >= saleStart, "sale has not started");

		(, uint price) = nft.tokenTypeAndPrice(tokenId);
		require(price == msg.value, "wrong price");
		
		nft.mint(msg.sender, tokenId);
		_safeTransferETH(ethReceiver, address(this).balance);
	}

	/// signature methods.
	
	function verify(
		bytes memory _signature
	) internal view returns(bool) {
		bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, address(this))));
        return (recoverSigner(message, _signature) == verifyAddress);
	}

    function recoverSigner(bytes32 message, bytes memory sig) internal pure returns (address) {
        (uint8 v, bytes32 r, bytes32 s) = abi.decode(sig, (uint8, bytes32, bytes32));
        return ecrecover(message, v, r, s);
    }

    /// builds a prefixed hash to mimic the behavior of eth_sign.
    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
   	}

	// internal methods

	function _safeTransferETH(address payable to, uint256 value) internal {
        (bool success, ) = to.call{value: value}('');
        require(success, 'ETH_TRANSFER_FAILED');
    }

}
function verify(
	bytes memory _signature
) internal view returns(bool) {
	bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, address(this))));
       return (recoverSigner(message, _signature) == verifyAddress);
}

/// signature methods.