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

    event AdvisorsAdded(
        address[] advisors,
        uint256[] tokenAllocations,
        address caller
    );

    event AdvisorAdded(
        address indexed advisor,
        address indexed caller,
        uint256 allocation
    );

    event AdvisorRemoved(
        address indexed advisor,
        address indexed caller,
        uint256 allocation
    );

    event WithdrawnTokens(address indexed advisor, uint256 value);

    event DepositInvestment(address indexed advisor, uint256 value);

    event TransferInvestment(address indexed owner, uint256 value);

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

    uint256 private _totalAllocatedAmount;
    uint256 private _initialTimestamp;
    IERC20 private _fmtToken;
    address[] public advisors;

    uint256 private constant _remainingDistroPercentage = 95;
    uint256 private constant _noOfRemaingDays = 300;

    struct Advisor {
        bool exists;
        uint256 withdrawnTokens;
        uint256 tokensAllotment;
    }

    mapping(address => Advisor) public advisorsInfo;

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

    /// @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");
        _;
    }

    modifier onlyAdvisor() {
        require(advisorsInfo[_msgSender()].exists, "Only advisors allowed");
        _;
    }

    constructor(address _token) {
        _fmtToken = IERC20(_token);
    }

    function getInitialTimestamp() public view returns (uint256 timestamp) {
        return _initialTimestamp;
    }

    /// @dev release tokens to all the advisors
    function releaseTokens() external onlyOwner initialized() {
        for (uint8 i = 0; i < advisors.length; i++) {
            Advisor storage advisor = advisorsInfo[advisors[i]];
            uint256 tokensAvailable = withdrawableTokens(advisors[i]);
            if (tokensAvailable > 0) {
                advisor.withdrawnTokens = advisor.withdrawnTokens.add(
                    tokensAvailable
                );
                _fmtToken.safeTransfer(advisors[i], tokensAvailable);
            }
        }
    }

    /// @dev Adds advisors. This function doesn't limit max gas consumption,
    /// so adding too many advisors can cause it to reach the out-of-gas error.
    /// @param _advisors The addresses of new advisors.
    /// @param _tokenAllocations The amounts of the tokens that belong to each advisor.
    function addAdvisors(
        address[] calldata _advisors,
        uint256[] calldata _tokenAllocations
    ) external onlyOwner {
        require(
            _advisors.length == _tokenAllocations.length,
            "different arrays sizes"
        );
        for (uint256 i = 0; i < _advisors.length; i++) {
            _addAdvisor(_advisors[i], _tokenAllocations[i]);
        }
        emit AdvisorsAdded(_advisors, _tokenAllocations, msg.sender);
    }

    // 5% at TGE, 95% released daily over 300 Days, no Cliff
    function withdrawTokens() external onlyAdvisor() initialized() {
        Advisor storage advisor = advisorsInfo[_msgSender()];

        uint256 tokensAvailable = withdrawableTokens(_msgSender());

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

        advisor.withdrawnTokens = advisor.withdrawnTokens.add(tokensAvailable);
        _fmtToken.safeTransfer(_msgSender(), 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;
    }

    /// @dev withdrawble tokens for an address
    /// @param _advisor whitelisted advisor address
    function withdrawableTokens(address _advisor)
        public
        view
        returns (uint256 tokens)
    {
        if (!isInitialized) {
            return 0;
        }
        Advisor storage advisor = advisorsInfo[_advisor];
        uint256 availablePercentage = _calculateAvailablePercentage();
        uint256 noOfTokens =
            _calculatePercentage(advisor.tokensAllotment, availablePercentage);
        uint256 tokensAvailable = noOfTokens.sub(advisor.withdrawnTokens);

        return tokensAvailable;
    }

    /// @dev Adds advisor. This function doesn't limit max gas consumption,
    /// so adding too many advisors can cause it to reach the out-of-gas error.
    /// @param _advisor The addresses of new advisors.
    /// @param _tokensAllotment The amounts of the tokens that belong to each advisor.
    function _addAdvisor(address _advisor, uint256 _tokensAllotment)
        internal
        onlyOwner
    {
        require(_advisor != address(0), "Invalid address");
        require(
            _tokensAllotment > 0,
            "the advisor allocation must be more than 0"
        );
        Advisor storage advisor = advisorsInfo[_advisor];

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

        advisor.tokensAllotment = _tokensAllotment;
        advisor.exists = true;
        advisors.push(_advisor);
        _totalAllocatedAmount = _totalAllocatedAmount.add(_tokensAllotment);
        emit AdvisorAdded(_advisor, _msgSender(), _tokensAllotment);
    }

    /// @dev calculate percentage value from amount
    /// @param _amount amount input to find the percentage
    /// @param _percentage percentage for an amount
    function _calculatePercentage(uint256 _amount, uint256 _percentage)
        private
        pure
        returns (uint256 percentage)
    {
        return _amount.mul(_percentage).div(100).div(1e18);
    }

    function _calculateAvailablePercentage()
        private
        view
        returns (uint256 availablePercentage)
    {
        // 500000 FMT assigned
        // 25000 tokens on TGE - 5% on TGE
        // 475000 tokens distributed for 300 days - 95% remaining
        // 475000/300 = 1583 tokens per day
        // 95/300 = 0.3167% every day released
        uint256 oneDays = _initialTimestamp + 1 days;
        uint256 vestingDuration = _initialTimestamp + 300 days;

        uint256 everyDayReleasePercentage =
            _remainingDistroPercentage.mul(1e18).div(_noOfRemaingDays);

        uint256 currentTimeStamp = block.timestamp;

        if (currentTimeStamp > _initialTimestamp) {
            if (currentTimeStamp <= oneDays) {
                return uint256(5).mul(1e18);
            } else if (
                currentTimeStamp > oneDays && currentTimeStamp < vestingDuration
            ) {
                uint256 noOfDays =
                    BokkyPooBahsDateTimeLibrary.diffDays(
                        _initialTimestamp,
                        currentTimeStamp
                    );
                uint256 currentUnlockedPercentage =
                    noOfDays.mul(everyDayReleasePercentage);

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

    function recoverToken(address _token, uint256 amount) external onlyOwner {
        IERC20(_token).safeTransfer(_msgSender(), amount);
        emit RecoverToken(_token, amount);
    }
}
// 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 releaseTokens() external onlyOwner initialized() {
    for (uint8 i = 0; i < advisors.length; i++) {
        Advisor storage advisor = advisorsInfo[advisors[i]];
        uint256 tokensAvailable = withdrawableTokens(advisors[i]);
        if (tokensAvailable > 0) {
            advisor.withdrawnTokens = advisor.withdrawnTokens.add(
                tokensAvailable
            );
            _fmtToken.safeTransfer(advisors[i], tokensAvailable);
        }
    }
}

/// @dev release tokens to all the advisors