contract TokenLock is Ownable {

    event BeneficiaryUpdated(address account);

    event ReleaseInfoCreated(uint256 time, uint256 amount);
    event ReleaseInfoUpdated(uint256 time, uint256 amount);
    event Released(address account, uint256 amount, uint256 indexFrom, uint256 indexTo);

    struct ReleaseInfo {
        uint128 time;
        uint128 amount;
    }

    IERC20 private _token;

    address private _beneficiary;

    uint256 private _releaseIndex;

    uint256 private _totalReleases;

    mapping(uint256 => ReleaseInfo) private _releases; 

    /**
     * @dev Constructor
     */
    constructor(address token, address beneficiary)
    {
        _token = IERC20(token);

        _beneficiary = beneficiary;
    }

    /**
     * @dev Updates beneficiary address
     */
    function updateBeneficiary(address account)
        external
        onlyOwner
    {
        require(account != address(0), "TokenLock: address is invalid");

        _beneficiary = account;

        emit BeneficiaryUpdated(account);
    }

    /**
     * @dev Returns smart contract information
     */
    function getContractInfo()
        external
        view
        returns (address, address, uint256, uint256, uint256)
    {
        return (address(_token), _beneficiary, _totalReleases, _token.balanceOf(address(this)), _releaseIndex);
    }

    /**
     * @dev Creates release information
     */
    function createReleaseInfo(uint128[] memory times, uint128[] memory amounts)
        external
        onlyOwner
    {
        uint256 length = times.length;

        require(length == amounts.length, "TokenLock: array length is invalid");

        uint256 index = _totalReleases;

        uint128 lastTime = index == 0 ? 0 : _releases[index - 1].time;

        for (uint256 i = 0; i < length; i++) {
            uint128 time = times[i];
            uint128 amount = amounts[i];

            require(lastTime < time, "TokenLock: time is invalid");

            _releases[index++] = ReleaseInfo(time, amount);

            lastTime = time;

            emit ReleaseInfoCreated(time, amount);
        }

        if (_totalReleases != index) {
            _totalReleases = index;
        }
    }

    // /**
    //  * @dev Updates release information
    //  */
    // function updateReleaseInfo(uint128 index, uint128 time, uint128 amount)
    //     external
    //     onlyOwner
    // {
    //     require(index >= _releaseIndex, "TokenLock: index is invalid");

    //     ReleaseInfo storage info = _releases[index];

    //     require(info.time > 0, "TokenLock: info does not exist");

    //     uint128 prevTime = index > 0 ? _releases[index - 1].time : 0;

    //     uint128 nextTime = index == _totalReleases - 1 ? type(uint128).max : _releases[index + 1].time;

    //     require(time > prevTime && time < nextTime, "TokenLock: time is invalid");

    //     info.time = time;
    //     info.amount = amount;

    //     emit ReleaseInfoUpdated(time, amount);
    // }

    /**
     * @dev Returns release information
     */
    function getReleaseInfo(uint256 indexFrom, uint256 indexTo)
        external
        view
        returns (ReleaseInfo[] memory)
    {
        uint256 cnt = 0;
        uint256 size = indexTo - indexFrom + 1;

        ReleaseInfo[] memory tmps = new ReleaseInfo[](size);

        for (uint256 i = indexFrom; i <= indexTo; i++) {
            if (_releases[i].time == 0) {
                break;
            }

            tmps[cnt++] = _releases[i];
        }

        ReleaseInfo[] memory releases = new ReleaseInfo[](cnt);

        for (uint256 i = 0; i < cnt; i++) {
            releases[i] = tmps[i];
        }

        return releases;
    }

    /**
     * @dev Returns release balance
     */
    function getReleaseBalance()
        public
        view
        returns (uint256, uint256, uint256)
    {
        uint256 balance;
        uint256 length = _totalReleases;
        uint256 currentTime = block.timestamp;
        uint256 indexFrom = _releaseIndex;
        uint256 indexTo = indexFrom;

        for (uint256 i = indexFrom; i < length; i++) {
            ReleaseInfo memory info = _releases[i];

            if (currentTime < info.time) {
                break;
            }

            balance += info.amount;
            indexTo++;
        }

        return (indexFrom, indexTo, balance);
    }

    /**
     * @dev Releases token
     */
    function release()
        external
    {
        (uint256 indexFrom, uint256 indexTo, uint256 balance) = getReleaseBalance();

        require(indexTo > indexFrom, "TokenLock: can not release");

        _releaseIndex = indexTo;

        if (balance > 0) {
            _token.transfer(_beneficiary, balance);
        }

        emit Released(_beneficiary, balance, indexFrom, indexTo);
    }

}
function updateBeneficiary(address account)
    external
    onlyOwner
{
    require(account != address(0), "TokenLock: address is invalid");

    _beneficiary = account;

    emit BeneficiaryUpdated(account);
}

/**
 * @dev Updates beneficiary address
 */