contract SperaxToken is ERC20, Pausable, Ownable {

    mapping(address => TimeLock) private _timelock;

    event BlockTransfer(address indexed account);
    event AllowTransfer(address indexed account);

    struct TimeLock {
        uint256 releaseTime;
        uint256 amount;
    }

    /**
     * @dev Initialize the contract give all tokens to the deployer
     */
    constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) public {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        _mint(_msgSender(), _initialSupply * (10 ** uint256(_decimals)));
    }

    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) public {
        uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount, "SperaxToken: burn amount exceeds allowance");

        _approve(account, _msgSender(), decreasedAllowance);
        _burn(account, amount);
    }


    /**
     * @dev View `account` locked information
     */
    function timelockOf(address account) public view returns(uint256 releaseTime, uint256 amount) {
        TimeLock memory timelock = _timelock[account];
        return (timelock.releaseTime, timelock.amount);
    }

    /**
     * @dev Transfer to the "recipient" some specified 'amount' that is locked until "releaseTime"
     * @notice only Owner call
     */
    function transferWithLock(address recipient, uint256 amount, uint256 releaseTime) public onlyOwner returns (bool) {
        require(recipient != address(0), "SperaxToken: transferWithLock to zero address");
        require(releaseTime > block.timestamp, "SperaxToken: release time before lock time");
        require(_timelock[recipient].releaseTime == 0, "SperaxToken: already locked");

        TimeLock memory timelock = TimeLock({
            releaseTime : releaseTime,
            amount      : amount
        });
        _timelock[recipient] = timelock;

        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev Release the specified `amount` of locked amount
     * @notice only Owner call
     */
    function release(address account, uint256 releaseAmount) public onlyOwner {
        require(account != address(0), "SperaxToken: release zero address");

        TimeLock storage timelock = _timelock[account];
        timelock.amount = timelock.amount.sub(releaseAmount);
        if(timelock.amount == 0) {
            timelock.releaseTime = 0;
        }
    }

    /**
     * @dev Triggers stopped state.
     * @notice only Owner call
     */
    function pause() public onlyOwner {
        _pause();
    }

    /**
     * @dev Returns to normal state.
     * @notice only Owner call
     */
    function unpause() public onlyOwner {
        _unpause();
    }

    /**
     * @dev Batch transfer amount to recipient
     * @notice that excessive gas consumption causes transaction revert
     */
    function batchTransfer(address[] memory recipients, uint256[] memory amounts) public {
        require(recipients.length > 0, "SperaxToken: least one recipient address");
        require(recipients.length == amounts.length, "SperaxToken: number of recipient addresses does not match the number of tokens");

        for(uint256 i = 0; i < recipients.length; ++i) {
            _transfer(_msgSender(), recipients[i], amounts[i]);
        }
    }

    /**
     * @dev See {ERC20-_beforeTokenTransfer}.
     *
     * Requirements:
     *
     * - the contract must not be paused.
     * - accounts must not trigger the locked `amount` during the locked period.
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
        require(!paused(), "SperaxToken: token transfer while paused");

        // Check whether the locked amount is triggered
        TimeLock storage timelock = _timelock[from];
        if(timelock.releaseTime != 0 && balanceOf(from).sub(amount) < timelock.amount) {
            require(block.timestamp >= timelock.releaseTime, "SperaxToken: current time is before from account release time");

            // Update the locked `amount` if the current time reaches the release time
            timelock.amount = balanceOf(from).sub(amount);
            if(timelock.amount == 0) {
                timelock.releaseTime = 0;
            }
        }

        super._beforeTokenTransfer(from, to, amount);
    }
}
function timelockOf(address account) public view returns(uint256 releaseTime, uint256 amount) {
    TimeLock memory timelock = _timelock[account];
    return (timelock.releaseTime, timelock.amount);
}

/**
 * @dev View `account` locked information
 */