contract FMTTeamTokenVesting is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    event DistributionAdded(
        address indexed investor,
        address indexed caller,
        uint256 allocation
    );

    event DistributionRemoved(
        address indexed investor,
        address indexed caller,
        uint256 allocation
    );

    event WithdrawnTokens(address indexed investor, uint256 value);

    event RecoverToken(address indexed token, uint256 indexed amount);

    enum DistributionType {FOUNDATION, TEAM}

    uint256 private _initialTimestamp;
    IERC20 private _fmtToken;

    struct Distribution {
        address beneficiary;
        uint256 withdrawnTokens;
        uint256 tokensAllotment;
        DistributionType distributionType;
    }

    mapping(DistributionType => Distribution) public distributionInfo;

    /// @dev Boolean variable that indicates whether the contract was initialized.
    bool public isInitialized = false;
    /// @dev Boolean variable that indicates whether the investors set was finalized.
    bool public isFinalized = false;

    address FOUNDATION_ADDRESS = 0x982e39e5B4e56fE66BA4A19E9F5CFb0A057A5AEa;
    address TEAM_ADDRESS = 0xbb0d72070791193b5C87A0e8f24E392657e0ecd5;

    uint256 constant _SCALING_FACTOR = 10**18; // decimals

    /// @dev Checks that the contract is initialized.
    modifier initialized() {
        require(isInitialized, "not initialized");
        _;
    }

    /// @dev Checks that the contract is initialized.
    modifier notInitialized() {
        require(!isInitialized, "initialized");
        _;
    }

    constructor(address _token) {
        require(
            address(_token) != address(0x0),
            "Finminity token address is not valid"
        );
        _fmtToken = IERC20(_token);

        _addDistribution(
            FOUNDATION_ADDRESS,
            DistributionType.FOUNDATION,
            2920000 * _SCALING_FACTOR
        );
        _addDistribution(
            TEAM_ADDRESS,
            DistributionType.TEAM,
            750000 * _SCALING_FACTOR
        );
    }

    /// @dev Returns initial timestamp
    function getInitialTimestamp() public view returns (uint256 timestamp) {
        return _initialTimestamp;
    }

    /// @dev Adds Distribution. This function doesn't limit max gas consumption,
    /// so adding too many investors can cause it to reach the out-of-gas error.
    /// @param _beneficiary The address of distribution.
    /// @param _tokensAllotment The amounts of the tokens that belong to each investor.
    function _addDistribution(
        address _beneficiary,
        DistributionType _distributionType,
        uint256 _tokensAllotment
    ) internal {
        require(_beneficiary != address(0), "Invalid address");
        require(
            _tokensAllotment > 0,
            "the investor allocation must be more than 0"
        );
        Distribution storage distribution = distributionInfo[_distributionType];

        require(distribution.tokensAllotment == 0, "investor already added");

        distribution.beneficiary = _beneficiary;
        distribution.tokensAllotment = _tokensAllotment;
        distribution.distributionType = _distributionType;

        emit DistributionAdded(_beneficiary, _msgSender(), _tokensAllotment);
    }

    function withdrawTokens(uint256 _distributionType)
        external
        onlyOwner()
        initialized()
    {
        Distribution storage distribution =
            distributionInfo[DistributionType(_distributionType)];

        uint256 tokensAvailable =
            withdrawableTokens(DistributionType(_distributionType));

        require(tokensAvailable > 0, "no tokens available for withdrawl");

        distribution.withdrawnTokens = distribution.withdrawnTokens.add(
            tokensAvailable
        );
        _fmtToken.safeTransfer(distribution.beneficiary, tokensAvailable);

        emit WithdrawnTokens(_msgSender(), tokensAvailable);
    }

    /// @dev The starting time of TGE
    /// @param _timestamp The initial timestamp, this timestap should be used for vesting
    function setInitialTimestamp(uint256 _timestamp)
        external
        onlyOwner()
        notInitialized()
    {
        isInitialized = true;
        _initialTimestamp = _timestamp;
    }

    function withdrawableTokens(DistributionType distributionType)
        public
        view
        returns (uint256 tokens)
    {
        Distribution storage distribution = distributionInfo[distributionType];
        uint256 availablePercentage =
            _calculateAvailablePercentage(distributionType);
        uint256 noOfTokens =
            _calculatePercentage(
                distribution.tokensAllotment,
                availablePercentage
            );
        uint256 tokensAvailable = noOfTokens.sub(distribution.withdrawnTokens);
        return tokensAvailable;
    }

    function _calculatePercentage(uint256 _amount, uint256 _percentage)
        private
        pure
        returns (uint256 percentage)
    {
        return _amount.mul(_percentage).div(100).div(1e18);
    }

    function _getTeamPercentage(uint256 _currentTimeStamp)
        private
        view
        returns (uint256 _availablePercentage)
    {
        // TEAM 60 Days Lock from TGE, Released daily over 300 Days after 60 days cliff
        uint256 cliffDuration = _initialTimestamp + 60 days;
        uint256 oneYear = _initialTimestamp + 300 days + 60 days;
        uint256 remainingDistroPercentage = 100;
        uint256 noOfRemaingDays = 300;
        uint256 everyDayReleasePercentage =
            remainingDistroPercentage.mul(1e18).div(noOfRemaingDays);

        if (_currentTimeStamp <= cliffDuration) {
            return uint256(0);
        }
        if (
            _currentTimeStamp > cliffDuration && _currentTimeStamp < oneYear
        ) {
            uint256 noOfDays =
                BokkyPooBahsDateTimeLibrary.diffDays(
                    cliffDuration,
                    _currentTimeStamp
                );
            uint256 currentUnlockedPercentage =
                noOfDays.mul(everyDayReleasePercentage);
            return currentUnlockedPercentage;
        } else {
            return uint256(100).mul(1e18);
        }
    }

    function _getFoundationPercentage(uint256 _currentTimeStamp)
        private
        view
        returns (uint256 _availablePercentage)
    {
        // FOUNDATION 60 Days Lock from TGE, Released daily over 300 Days after 60 days cliff
        uint256 cliffDuration = _initialTimestamp + 60 days;
        uint256 oneYear = _initialTimestamp + 300 days + 60 days;
        uint256 remainingDistroPercentage = 100;
        uint256 noOfRemaingDays = 300;
        uint256 everyDayReleasePercentage =
            remainingDistroPercentage.mul(1e18).div(noOfRemaingDays);

        if (_currentTimeStamp <= cliffDuration) {
            return uint256(0);
        } else if (
            _currentTimeStamp > cliffDuration && _currentTimeStamp < oneYear
        ) {
            uint256 noOfDays =
                BokkyPooBahsDateTimeLibrary.diffDays(
                    cliffDuration,
                    _currentTimeStamp
                );
            uint256 currentUnlockedPercentage =
                noOfDays.mul(everyDayReleasePercentage);

            return currentUnlockedPercentage;
        } else {
            return uint256(100).mul(1e18);
        }
    }

    function _calculateAvailablePercentage(DistributionType distributionType)
        private
        view
        returns (uint256 _availablePercentage)
    {
        uint256 currentTimeStamp = block.timestamp;

        if (currentTimeStamp > _initialTimestamp) {
            if (distributionType == DistributionType.FOUNDATION) {
                return _getFoundationPercentage(currentTimeStamp);
            } else if (distributionType == DistributionType.TEAM) {
                return _getTeamPercentage(currentTimeStamp);
            }
        }
    }
}
// import "@openzeppelin/contracts/access/Ownable.sol";
// import "@openzeppelin/contracts/utils/math/SafeMath.sol";
// import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// import "contracts/BokkyPooBahsDateTimeLibrary.sol";
function _addDistribution(
        address _beneficiary,
        DistributionType _distributionType,
        uint256 _tokensAllotment
    ) internal {
        require(_beneficiary != address(0), "Invalid address");
        require(
            _tokensAllotment > 0,
            "the investor allocation must be more than 0"
        );
        Distribution storage distribution = distributionInfo[_distributionType];

        require(distribution.tokensAllotment == 0, "investor already added");

        distribution.beneficiary = _beneficiary;
        distribution.tokensAllotment = _tokensAllotment;
        distribution.distributionType = _distributionType;

        emit DistributionAdded(_beneficiary, _msgSender(), _tokensAllotment);
    }
/// @dev Adds Distribution. This function doesn't limit max gas consumption,
/// so adding too many investors can cause it to reach the out-of-gas error.
/// @param _beneficiary The address of distribution.
/// @param _tokensAllotment The amounts of the tokens that belong to each investor.