contract TokenVestBD{
    using SafeMath for uint256;
    IERC20 public vestedToken;
    uint256 public releaseInterval;
    uint256 public lockingPeriod;
    uint256 public releasePer;
    IERC20 public otherToken;

    address payable public owner;

    uint256 public totalAddedToken; // total no of tokens vested in contract
    uint256 public totalReleasedToken; // total released tokens 
    uint256 public RemainToken;
    uint256 public totalVestor; // total no. of vestors
    uint256 public percentDivider;
    uint256 public minimumLimit;

    struct VestToken {
        uint256 lockedtilltime;
        uint256 vesttime;
        uint256 amount;
        uint256 completewithdrawtill;
        uint256 persecondLimit;
        uint256 lastWithdrawalTime;
        uint256 totalWithdrawal;
        uint256 remainWithdrawal;
        uint256 releaseinterval;
        uint256 releaseperperinterval;
        bool withdrawan;
    }

    struct User {
        uint256 totalVestedTokenUser;
        uint256 totalWithdrawedTokenUser;
        uint256 vestCount;
        bool alreadyExists;
    }

    mapping(address => User) public Vestors;
    mapping(uint256 => address) public VesterID;
    mapping(address => mapping(uint256 => VestToken)) public vestorRecord;

    event VEST(address Vestors, uint256 amount);
    event RELEASE(address Vestors, uint256 amount);


    modifier onlyowner() {
        require(owner == msg.sender, "only owner");
        _;
    }
    constructor(address payable _owner, address mainToken) {
        owner = _owner;
        vestedToken = IERC20(mainToken); // address of token that we will stake or vest
        lockingPeriod = 200 days; // locking period in days or no of seconds like 3600 for 1 hour
        releaseInterval = 30 days; // release interval in days or seconds
        releasePer = 50; // release percentage per interval values will be multiplied with 10
        percentDivider = 1000;
        minimumLimit = 1e20; // minimum limit for the vesting in wei
    }

    /** This method is used to deposit amount in contract 
	* amount will be in wei */
    function vest(uint256 amount) public {
       
        require(amount >= minimumLimit, "vest more than minimum amount");
    
        if (!Vestors[msg.sender].alreadyExists) {
            Vestors[msg.sender].alreadyExists = true;
            VesterID[totalVestor] = msg.sender;
            totalVestor++;
        }

        vestedToken.transferFrom(msg.sender, address(this), amount);

        uint256 index = Vestors[msg.sender].vestCount;
        Vestors[msg.sender].totalVestedTokenUser = Vestors[msg.sender]
            .totalVestedTokenUser
            .add(amount);
        totalAddedToken = totalAddedToken.add(amount);
        RemainToken = RemainToken.add(amount);
        vestorRecord[msg.sender][index].lockedtilltime = block.timestamp.add(
            lockingPeriod
        );
        vestorRecord[msg.sender][index].vesttime = block.timestamp;
        vestorRecord[msg.sender][index].amount = amount;
        vestorRecord[msg.sender][index].completewithdrawtill = vestorRecord[msg.sender][index].lockedtilltime.add((percentDivider.div(releasePer)).mul(releaseInterval));
        vestorRecord[msg.sender][index].lastWithdrawalTime = 0;
        vestorRecord[msg.sender][index].totalWithdrawal = 0;
        vestorRecord[msg.sender][index].remainWithdrawal = amount;

        vestorRecord[msg.sender][index].releaseinterval = releaseInterval;
        vestorRecord[msg.sender][index].releaseperperinterval = releasePer;

        vestorRecord[msg.sender][index].persecondLimit = amount.div((percentDivider.div(releasePer)).mul(releaseInterval));

        Vestors[msg.sender].vestCount++;

        emit VEST(msg.sender, amount);
    }

   /** This method will used to withdraw vested token 
	* before release, the token will be locked for the locking duration, and then it will release the set percentage for the set period */
    function releaseToken(uint256 index) public {
        require(
            !vestorRecord[msg.sender][index].withdrawan,
            "already withdrawan"
        );
        require(
            vestorRecord[msg.sender][index].lockedtilltime < block.timestamp,
            "cannot release token before locked duration"
        );

        uint256 releaseLimitTillNow;
        uint256 commontimestamp;
        (releaseLimitTillNow,commontimestamp) = realtimeReleasePerBlock(msg.sender , index);
        
        vestedToken.transfer(
            msg.sender,
            releaseLimitTillNow
        );

        totalReleasedToken = totalReleasedToken.add(
            releaseLimitTillNow
        );
        RemainToken = RemainToken.sub(releaseLimitTillNow);
        
        vestorRecord[msg.sender][index].lastWithdrawalTime =  commontimestamp;
        
        vestorRecord[msg.sender][index].totalWithdrawal = vestorRecord[msg.sender][index].totalWithdrawal.add(releaseLimitTillNow);

        vestorRecord[msg.sender][index].remainWithdrawal = vestorRecord[msg.sender][index].remainWithdrawal.sub(releaseLimitTillNow);

        Vestors[msg.sender].totalWithdrawedTokenUser = Vestors[msg.sender].totalWithdrawedTokenUser.add(releaseLimitTillNow);

        if(vestorRecord[msg.sender][index].totalWithdrawal == vestorRecord[msg.sender][index].amount){
            vestorRecord[msg.sender][index].withdrawan = true;

        }

        emit RELEASE(
            msg.sender,
            releaseLimitTillNow
        );
    }

    /** This method will return realtime release amount for particular user's block */
    function realtimeReleasePerBlock(address user, uint256 blockno) public view returns (uint256,uint256) {

        uint256 ret;
        uint256 commontimestamp;
            if (
                !vestorRecord[user][blockno].withdrawan &&
                vestorRecord[user][blockno].lockedtilltime < block.timestamp
            ) {
                uint256 val;
                uint256 tempwithdrawaltime = vestorRecord[user][blockno].lastWithdrawalTime;
                commontimestamp = block.timestamp;
                if(tempwithdrawaltime == 0){
                    tempwithdrawaltime = vestorRecord[user][blockno].lockedtilltime;
                }
                val = commontimestamp - tempwithdrawaltime;
                val = val.mul(vestorRecord[user][blockno].persecondLimit);
                if (val < vestorRecord[user][blockno].remainWithdrawal) {
                    ret += val;
                } else {
                    ret += vestorRecord[user][blockno].remainWithdrawal;
                }
            }
        return (ret,commontimestamp);
    }

    /** This method can only be invoked by the owner's address and is used to adjust the Release Interval; the argument will be in seconds */

    function SetReleaseInterval(uint256 val) external onlyowner {
        releaseInterval = val;
    }

    
    function SetReleasePercentage(uint256 val) external onlyowner {
        releasePer = val;
    }

    /** This method can only be invoked by the owner's address and is used to adjust the vesting duration (locking period), the argument will be in seconds */
    function SetLockingPeriod(uint256 val) external onlyowner {
        lockingPeriod = val;
    }

    /** The base currency is withdrawn using this method */

    function withdrawBaseCurrency() public onlyowner {
        uint256 balance = address(this).balance;
        require(balance > 0, "does not have any balance");
        payable(msg.sender).transfer(balance);
    }

    /** These two methods will enable the contract owner in withdrawing any incorrectly deposited tokens
    * first call initToken method with passing token contract address as an argument 
    * then, as an argument, call withdrawToken with the value in wei */

    function initToken(address addr) public onlyowner{
        otherToken = IERC20(addr);
    }
    function withdrawToken(uint256 amount) public onlyowner {
        otherToken.transfer(msg.sender
        , amount);
    }

}
function SetLockingPeriod(uint256 val) external onlyowner {
    lockingPeriod = val;
}

/** This method can only be invoked by the owner's address and is used to adjust the vesting duration (locking period), the argument will be in seconds */