contract TokenVesting is Ownable {
    using SafeMath for uint;

    struct TokenHolder {
        uint weiReceived; // amount of ETH contributed
        uint tokensToSend; // amount of tokens  sent  
        bool refunded; // true if user has been refunded       
        uint releasedAmount; // amount released through vesting schedule
        bool revoked; // true if right to continue vesting is revoked
    }

    event Released(uint256 amount, uint256 tokenDecimals);
    event ContractUpdated(bool done);

    uint256 public cliff;  // time in  when vesting should begin
    uint256 public startCountDown;  // time when countdown starts
    uint256 public duration; // duration of period in which vesting takes place   
    Token public token;  // token contract containing tokens
    mapping(address => TokenHolder) public tokenHolders; //tokenHolder list
    WhiteList public whiteList; // whitelist contract
    uint256 public presaleBonus;
    
    // @note constructor 
    /**
    function TokenVesting(uint256 _start, uint256 _cliff, uint256 _duration) public {   
         require(_cliff <= _duration);   
        duration = _duration;
        cliff = _start.add(_cliff);
        startCountDown = _start;         
        ContractUpdated(true);                    
    }
    */
    // @notice Specify address of token contract
    // @param _tokenAddress {address} address of token contract
    // @return res {bool}
    function initilizeVestingAndTokenAndWhiteList(Token _tokenAddress, 
                                        uint256 _start, 
                                        uint256 _cliff, 
                                        uint256 _duration,
                                        uint256 _presaleBonus, 
                                        WhiteList _whiteList) external onlyOwner() returns(bool res) {
        require(_cliff <= _duration);   
        require(_tokenAddress != address(0));
        duration = _duration;
        cliff = _start.add(_cliff);
        startCountDown = _start;  
        token = _tokenAddress; 
        whiteList = _whiteList;
        presaleBonus = _presaleBonus;
        ContractUpdated(true);
        return true;    
    }

    // @notice Specify address of token contract
    // @param _tokenAddress {address} address of token contract
    // @return res {bool}
    function initilizeVestingAndToken(Token _tokenAddress, 
                                        uint256 _start, 
                                        uint256 _cliff, 
                                        uint256 _duration,
                                        uint256 _presaleBonus
                                        ) external onlyOwner() returns(bool res) {
        require(_cliff <= _duration);   
        require(_tokenAddress != address(0));
        duration = _duration;
        cliff = _start.add(_cliff);
        startCountDown = _start;  
        token = _tokenAddress;        
        presaleBonus = _presaleBonus;
        ContractUpdated(true);
        return true;    
    }

    function returnVestingSchedule() external view returns (uint, uint, uint) {

        return (duration, cliff, startCountDown);
    }

    // @note owner can revoke access to continue vesting of tokens
    // @param _user {address} of user to revoke their right to vesting
    function revoke(address _user) public onlyOwner() {

        TokenHolder storage tokenHolder = tokenHolders[_user];
        tokenHolder.revoked = true; 
    }

    function vestedAmountAvailable() public view returns (uint amount, uint decimals) {

        TokenHolder storage tokenHolder = tokenHolders[msg.sender];
        uint tokensToRelease = vestedAmount(tokenHolder.tokensToSend);

     //   if (tokenHolder.releasedAmount + tokensToRelease > tokenHolder.tokensToSend)
      //      return (tokenHolder.tokensToSend - tokenHolder.releasedAmount, token.decimals());
     //   else 
        return (tokensToRelease - tokenHolder.releasedAmount, token.decimals());
    }
    
    // @notice Transfers vested available tokens to beneficiary   
    function release() public {

        TokenHolder storage tokenHolder = tokenHolders[msg.sender];        
        // check if right to vesting is not revoked
        require(!tokenHolder.revoked);                                   
        uint tokensToRelease = vestedAmount(tokenHolder.tokensToSend);      
        uint currentTokenToRelease = tokensToRelease - tokenHolder.releasedAmount;
        tokenHolder.releasedAmount += currentTokenToRelease;            
        token.transfer(msg.sender, currentTokenToRelease);

        Released(currentTokenToRelease, token.decimals());
    }
  
    // @notice this function will determine vested amount
    // @param _totalBalance {uint} total balance of tokens assigned to this user
    // @return {uint} amount of tokens available to transfer
    function vestedAmount(uint _totalBalance) public view returns (uint) {

        if (now < cliff) {
            return 0;
        } else if (now >= startCountDown.add(duration)) {
            return _totalBalance;
        } else {
            return _totalBalance.mul(now.sub(startCountDown)) / duration;
        }
    }
}
// @note this contract can be inherited by Crowdsale and TeamAllocation contracts and
// control release of tokens through even time release based on the inputted duration time interval
function initilizeVestingAndToken(Token _tokenAddress, 
                                    uint256 _start, 
                                    uint256 _cliff, 
                                    uint256 _duration,
                                    uint256 _presaleBonus
                                    ) external onlyOwner() returns(bool res) {
    require(_cliff <= _duration);   
    require(_tokenAddress != address(0));
    duration = _duration;
    cliff = _start.add(_cliff);
    startCountDown = _start;  
    token = _tokenAddress;        
    presaleBonus = _presaleBonus;
    ContractUpdated(true);
    return true;    
}

// @notice Specify address of token contract
// @param _tokenAddress {address} address of token contract
// @return res {bool}