contract TimeLockedTokenDistribute is Ownable, ReentrancyGuard, Pausable {
    using SafeMath for uint256;

    // represents total distribution for locked balances
    mapping(address => uint256) public distribution;

    // represents first unlock
    mapping(address => uint256) public firstunlock;

    // blocklist
    mapping(address => uint8) public blocklist;

    // Token Address
    IERC20 token;

    // Claimed Token
    mapping(address => uint256) public ClaimedBalances;

    // start of the lockup period
    // Wednesday, April 21, 2021 02:00:00 AM UTC +0
    // uint256 constant LOCK_START = 1618970400;
    uint256 constant LOCK_START = 1620784800;
    // length of time to delay first epoch
    // uint256 constant FIRST_EPOCH_DELAY = 21 days;
    uint256 constant FIRST_EPOCH_DELAY = 9 days;
    // how long does an epoch last
    uint256 constant EPOCH_DURATION = 1 days;
    // number of epochs
    uint256 constant TOTAL_EPOCHS = 365;

    // // Friday, July 24, 2021 4:58:31 PM GMT
    // uint256 constant LOCK_START = 1618840800;
    // // length of time to delay first epoch
    // uint256 constant FIRST_EPOCH_DELAY = 0 minutes;
    // // how long does an epoch last
    // uint256 constant EPOCH_DURATION = 5 minutes;
    // // number of epochs
    // uint256 constant TOTAL_EPOCHS = 20000;

    // registry of locked addresses
    address public timeLockRegistry;

    modifier onlyTimeLockRegistry() {
        require(msg.sender == timeLockRegistry, "only TimeLockRegistry");
        _;
    }

    constructor(address _TimeLockRegistry, IERC20 _token) public {
        require(_TimeLockRegistry != address(0), "cannot be zero address");

        token = _token;
        timeLockRegistry = _TimeLockRegistry;
    }

    function setToken(IERC20 _token) external onlyOwner {
        require(address(token) != address(0), "cannot be zero address");
        require(_token != token, "must be new token");
        token = _token;
    }

    function setTimeLockRegistry(address newTimeLockRegistry)
        external
        onlyOwner
    {
        require(newTimeLockRegistry != address(0), "cannot be zero address");
        require(
            newTimeLockRegistry != timeLockRegistry,
            "must be new TimeLockRegistry"
        );
        timeLockRegistry = newTimeLockRegistry;
    }

    function claim() public nonReentrant() whenNotPaused() {
        require(blocklist[msg.sender] == 0, "Sender has been blocked!");
        uint256 tokenCanClaimed =
            unlockedBalance(msg.sender).sub(ClaimedBalances[msg.sender]);
        require(tokenCanClaimed > 0, "Sender has 0 unclaimed tokens");
        //Log
        ClaimedBalances[msg.sender] = unlockedBalance(msg.sender);
        require(
            token.balanceOf(address(this)) >= tokenCanClaimed,
            "insufficient balance"
        );
        token.transfer(msg.sender, tokenCanClaimed);
    }

    function registerLockupByArr(
        address[] memory _receivers,
        uint256[] memory _amounts,
        uint256[] memory _unlocks
    ) external onlyTimeLockRegistry {
        uint256 totalAmount;
        for (uint256 i = 0; i < _receivers.length; i++) {
            require(distribution[_receivers[i]] == 0, "Only add once.");
            require(firstunlock[_receivers[i]] == 0, "Only add once.");
            require(_unlocks[i] <= _amounts[i], "invalid first unlock amount.");

            // add amount to locked distribution
            distribution[_receivers[i]] = distribution[_receivers[i]].add(
                _amounts[i]
            );

            // add firstunlock
            firstunlock[_receivers[i]] = firstunlock[_receivers[i]].add(
                _unlocks[i]
            );

            totalAmount = totalAmount.add(_amounts[i]);
        }

        // transfer to LockContract
        // require(
        //     token.balanceOf(msg.sender) >= totalAmount,
        //     "insufficient balance"
        // );
        // token.transferFrom(msg.sender, address(this), totalAmount);
    }

    /**
     * @dev Transfer tokens to another account under the lockup schedule
     * Emits a transfer event showing a transfer to the recipient
     * Only the registry can call this function
     * @param receiver Address to receive the tokens
     * @param amount Tokens to be transferred
     */
    function registerLockup(
        address receiver,
        uint256 amount,
        uint256 unlock
    ) external onlyTimeLockRegistry {
        require(token.balanceOf(msg.sender) >= amount, "insufficient balance");

        require(distribution[receiver] == 0, "Only add once.");
        require(firstunlock[receiver] == 0, "Only add once.");
        require(unlock <= amount, "invalid first unlock amount.");

        // add amount to locked distribution
        distribution[receiver] = distribution[receiver].add(amount);

        // add firstunlock
        firstunlock[receiver] = firstunlock[receiver].add(unlock);

        // transfer to LockContract
        // token.transferFrom(msg.sender, address(this), amount);
    }

    function deposit(uint256 amount) external onlyTimeLockRegistry {
        require(token.balanceOf(msg.sender) >= amount, "insufficient balance");
        // transfer to LockContract
        token.transferFrom(msg.sender, address(this), amount);
    }

    function withdraw(address receiver, uint256 amount)
        external
        onlyTimeLockRegistry
    {
        require(
            token.balanceOf(address(this)) >= amount,
            "insufficient balance"
        );
        token.transfer(receiver, amount);
    }

    function balance() public view returns (uint256) {
        return token.balanceOf(address(this));
    }

    function cleanDistribution(address account) external onlyTimeLockRegistry {
        distribution[account] = 0;
    }

    function cleanFirstUnlock(address account) external onlyTimeLockRegistry {
        firstunlock[account] = 0;
    }

    function addToBlockList(address account) external onlyTimeLockRegistry {
        blocklist[account] = 1;
    }

    function removeFromBlockList(address account)
        external
        onlyTimeLockRegistry
    {
        blocklist[account] = 0;
    }

    function lockedBalance(address account) public view returns (uint256) {
        // distribution * 0.7 * (epochsLeft / totalEpochs)
        // return distribution[account].mul(100 - FIRST_UNLOCK).div(100).mul(epochsLeft()).div(TOTAL_EPOCHS);

        // (distribution - firstunlock) * (epochsLeft / totalEpochs)
        return
            distribution[account]
                .sub(firstunlock[account])
                .mul(epochsLeft())
                .div(TOTAL_EPOCHS);
    }

    function unlockedBalance(address account) public view returns (uint256) {
        // totalBalance - lockedBalance
        return distribution[account].sub(lockedBalance(account));
    }

    function claimedBalance(address account) public view returns (uint256) {
        return ClaimedBalances[account];
    }

    function epochsPassed() public view returns (uint256) {
        // return 0 if timestamp is lower than start time
        if (block.timestamp < LOCK_START) {
            return 0;
        }

        // how long it has been since the beginning of lockup period
        uint256 timePassed = block.timestamp.sub(LOCK_START);

        // 1st epoch is FIRST_EPOCH_DELAY longer; we check to prevent subtraction underflow
        if (timePassed < FIRST_EPOCH_DELAY) {
            return 0;
        }

        // subtract the FIRST_EPOCH_DELAY, so that we can count all epochs as lasting EPOCH_DURATION
        uint256 totalEpochsPassed =
            timePassed.sub(FIRST_EPOCH_DELAY).div(EPOCH_DURATION);

        // epochs don't count over TOTAL_EPOCHS
        if (totalEpochsPassed > TOTAL_EPOCHS) {
            return TOTAL_EPOCHS;
        }

        return totalEpochsPassed;
    }

    function epochsLeft() public view returns (uint256) {
        return TOTAL_EPOCHS.sub(epochsPassed());
    }

    function nextEpoch() public view returns (uint256) {
        // get number of epochs passed
        uint256 passed = epochsPassed();

        // if all epochs passed, return
        if (passed == TOTAL_EPOCHS) {
            // return INT_MAX
            return uint256(-1);
        }

        // if no epochs passed, return latest epoch + delay + standard duration
        if (passed == 0) {
            return latestEpoch().add(FIRST_EPOCH_DELAY).add(EPOCH_DURATION);
        }

        // otherwise return latest epoch + epoch duration
        return latestEpoch().add(EPOCH_DURATION);
    }

    function latestEpoch() public view returns (uint256) {
        // get number of epochs passed
        uint256 passed = epochsPassed();

        // if no epochs passed, return lock start time
        if (passed == 0) {
            return LOCK_START;
        }

        // accounts for first epoch being longer
        // lockStart + firstEpochDelay + (epochsPassed * epochDuration)
        return
            LOCK_START.add(FIRST_EPOCH_DELAY).add(passed.mul(EPOCH_DURATION));
    }

    function finalEpoch() public pure returns (uint256) {
        // lockStart + firstEpochDelay + (epochDuration * totalEpochs)
        return
            LOCK_START.add(FIRST_EPOCH_DELAY).add(
                EPOCH_DURATION.mul(TOTAL_EPOCHS)
            );
    }

    function lockStart() public pure returns (uint256) {
        return LOCK_START;
    }
}
function registerLockup(
    address receiver,
    uint256 amount,
    uint256 unlock
) external onlyTimeLockRegistry {
    require(token.balanceOf(msg.sender) >= amount, "insufficient balance");

    require(distribution[receiver] == 0, "Only add once.");
    require(firstunlock[receiver] == 0, "Only add once.");
    require(unlock <= amount, "invalid first unlock amount.");

    // add amount to locked distribution
    distribution[receiver] = distribution[receiver].add(amount);

    // add firstunlock
    firstunlock[receiver] = firstunlock[receiver].add(unlock);

    // transfer to LockContract
    // token.transferFrom(msg.sender, address(this), amount);
}

/**
 * @dev Transfer tokens to another account under the lockup schedule
 * Emits a transfer event showing a transfer to the recipient
 * Only the registry can call this function
 * @param receiver Address to receive the tokens
 * @param amount Tokens to be transferred
 */