contract ZOVault  is Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    event Transfer(address indexed from, address indexed to, uint256 value);

    address immutable public zoToken;
    mapping(address=>WalletInfo) public wallet;

    struct WalletInfo{
        uint256 balance;
        uint256 onceUnlock;
        uint256 unlockInterval;
        uint256 lastWithdrawTime;
    }

    constructor(address zo) public{
        zoToken=zo;
    }

    function name() external pure returns(string memory){   return "ZO Vault"; }
    function symbol() external pure returns(string memory){  return "ZOv";  }
    function decimals() external pure returns(uint8){return 18;}
    function totalSupply() external view   returns (uint256) {
        return IERC20(zoToken).balanceOf(address(this));
    }

    function balanceOf(address user) external view returns(uint256){
        return wallet[user].balance;
    }


    function lock(address to,uint256 amount,uint256 unlockTimes,uint256 lastWithdrawTime,uint256 unlockInterval)
    external onlyOwner {
        require(unlockInterval<90 days,"lock too long time");
        require(unlockInterval>0,"invalid unlockInterval");
        require(wallet[to].balance==0,"repeat lock");
        require(amount>0,"amount is zero");
        require(lastWithdrawTime<block.timestamp,"invalid time");
        // transfer
        IERC20(zoToken).safeTransferFrom(msg.sender,address(this),amount);

        uint256 once=amount.div(unlockTimes);
        wallet[to]= WalletInfo({
            balance:amount,
            onceUnlock: once,
            unlockInterval:unlockInterval,
            lastWithdrawTime:lastWithdrawTime
        });

        emit Transfer(address(0),to,amount);
    }

    function withdraw() external{
        _unlock(msg.sender);
    }

    function withdrawFor(address locker) external{
        _unlock(locker);
    }

    /**
     * @notice withdraw all unlocked ZO Token for locker
     */
    function _unlock(address locker) private {

        uint256 balance= balanceOfUnlock(locker);
        require(balance>0,"unlock amount is zero");

        WalletInfo memory info = wallet[locker];
        uint256  times=block.timestamp.sub(info.lastWithdrawTime).div(info.unlockInterval);
        wallet[locker].lastWithdrawTime=info.lastWithdrawTime + times.mul(info.unlockInterval);
        wallet[locker].balance= info.balance.sub(balance);

        IERC20(zoToken).safeTransfer(locker,balance);

        emit Transfer(locker,address(0),balance);
    }


    function balanceOfUnlock(address user) public view returns(uint256) {
        WalletInfo memory info = wallet[user];
        if (info.balance==0){
            return 0;
        }
        uint256  times=block.timestamp.sub(info.lastWithdrawTime).div(info.unlockInterval);
        uint256 amount=times.mul(info.onceUnlock);
        if (amount<info.balance){
            return amount;
        }else{
            return info.balance;
        }
    }

}
////import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
////import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
////import "@openzeppelin/contracts/math/SafeMath.sol";
////import "@openzeppelin/contracts/access/Ownable.sol";
function _unlock(address locker) private {

    uint256 balance= balanceOfUnlock(locker);
    require(balance>0,"unlock amount is zero");

    WalletInfo memory info = wallet[locker];
    uint256  times=block.timestamp.sub(info.lastWithdrawTime).div(info.unlockInterval);
    wallet[locker].lastWithdrawTime=info.lastWithdrawTime + times.mul(info.unlockInterval);
    wallet[locker].balance= info.balance.sub(balance);

    IERC20(zoToken).safeTransfer(locker,balance);

    emit Transfer(locker,address(0),balance);
}

/**
 * @notice withdraw all unlocked ZO Token for locker
 */