contract NftPresale is INftPresale, Ownable {

	uint private _totalShares;
	mapping(address => uint) private _shares;
	address[] private _payees;

	uint public constant MAX_UNITS_FOR_WHITELIST_SALE = 5;
	uint public constant PRE_SALE_PRICE = 0.06 ether;
	uint public constant START_TIME = 1646397086;
	uint public finishTime = 1646550084;

	INFT public nft;
	
	address public verifyAddress = 0xe919f2ccF96B18095A91c32a855EFB822C5b1BC8;
	mapping(address => uint) public buyers;

    constructor(address _nftAddress, address[] memory payees_, uint[] memory shares_) {
		require(payees_.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
        require(payees_.length > 0, "PaymentSplitter: no payees");

		nft = INFT(_nftAddress);
		for (uint i = 0; i < payees_.length; i++) {
            _addPayee(payees_[i], shares_[i]);
        }
	}

	/*
     * @dev function to set finishTime
     * @param _finishTime finish time unix timestamp
     */
	function changeFinishTime(uint _finishTime) public onlyOwner {
		finishTime = _finishTime;
	}

	/*
     * @dev Add a new payee to the contract.
     * @param account_ The address of the payee to add.
     * @param shares_ The number of shares owned by the payee.
     */
    function _addPayee(address account_, uint shares_) private {
        require(account_ != address(0), "PaymentSplitter: account is the zero address");
        require(shares_ > 0, "PaymentSplitter: shares are 0");
        require(_shares[account_] == 0, "PaymentSplitter: account already has shares");

        _payees.push(account_);
        _shares[account_] = shares_;
        _totalShares += shares_;
    }

	/*
	 * @dev function to buy tokens.
	 * @param _amount how much tokens can be bought.
	 * @param _signature Signed message from verifyAddress private key
	 */
	function buyBatch(uint _amount, bytes memory _signature) external override payable {
	    require(block.timestamp >= START_TIME && block.timestamp < finishTime, "not a presale time");
		require(verify(_signature), "invalid signature");
		require(_amount > 0, "empty input");
		require(buyers[msg.sender] + _amount <= MAX_UNITS_FOR_WHITELIST_SALE, "maximum five tokens can be bought on presale");
		require(msg.value == _amount * PRE_SALE_PRICE, "wrong amount");
		buyers[msg.sender] += _amount;

		nft.mintBatch(msg.sender, _amount);
	}
	
	/*
	 * @dev function to withdraw all tokens
	 * @param _to ETH receiver address
	 */
    function cashOut() public onlyOwner{
        uint _balance = address(this).balance;
		for (uint i = 0; i < _payees.length; i++) {
            uint _payment = _balance * _shares[_payees[i]] / _totalShares; 
			Address.sendValue(payable(_payees[i]), _payment);
        }
    }

	/// 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));
    }
}
function prefixed(bytes32 hash) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}

/// builds a prefixed hash to mimic the behavior of eth_sign.