contract LCXVestingToken is Ownable {
    using SafeMath for uint256;

    address private immutable LCX_TOKEN; // Contract Address of LCX Token
    address private SaleContract; // Contract Address of Sale Tiamond

    struct VestedToken {
        uint256 cliff;
        uint256 start;
        uint256 duration;
        uint256 releasedToken;
        uint256 totalToken;
        bool revoked;
    }

    // mapped with token id
    mapping(address => VestedToken) public vestedUser;

    modifier onlySaleContract(){
        require(SaleContract == _msgSender(), "Only Sale Contract can call");
        _;
    }

    event TokenReleased(address indexed account, uint256 amount);
    event VestingRevoked(address indexed account);
    event SetSaleContractAddress(address);

    constructor(address _lcxAddress){
        require(_lcxAddress != address(0), "Address should not be zero address");
        LCX_TOKEN = _lcxAddress;
    }

    function setSaleAddress(address _saleAddress) external onlyOwner {
        require(_saleAddress != address(0), "Must be an address");
        SaleContract = _saleAddress;
        emit SetSaleContractAddress(_saleAddress);
    }
    

    /**
     * @dev this will set the beneficiary with vesting
     * parameters provided
     * @param account address of the beneficiary for vesting
     * @param amount  totalToken to be vested
     * @param cliff In seconds of one period in vesting
     * @param duration In seconds of total vesting
     * @param startAt UNIX timestamp in seconds from where vesting will start
     */
    function setVesting(
        address account,
        uint256 amount,
        uint256 cliff,
        uint256 duration,
        uint256 startAt
    ) external onlySaleContract returns (bool) {
        VestedToken storage vested = vestedUser[account];
        if (vested.start > 0) {
            require(vested.revoked, "Account vesting is still going on");
            uint256 unclaimedTokens = _vestedAmount(account).sub(
                vested.releasedToken
            );
            require(unclaimedTokens == 0, "Account vesting is still going on");
        }
        IERC20(LCX_TOKEN).transferFrom(_msgSender(), address(this), amount);
        _setVesting(account, amount, cliff, duration, startAt);
        return true;
    }

    function userDetails(address account)
        external
        view
        returns (uint256, uint256)
    {
        return (vestedUser[account].duration, vestedUser[account].totalToken);
    }

    /**
     * @dev Calculates the amount that has already vested.
     * @param account address of the user
     */
    function vestedToken(address account) external view returns (uint256) {
        return _vestedAmount(account);
    }

    /**
     * @dev Calculates the amount that has already vested but hasn't been released yet.
     * @param account address of user
     */
    function releasableToken(address account) external view returns (uint256) {
        return _vestedAmount(account).sub(vestedUser[account].releasedToken);
    }

    /**
     * @dev Internal function to set default vesting parameters
     * @param account address of the beneficiary for vesting
     * @param amount  totalToken to be vested
     * @param cliff In seconds of one period in vestin
     * @param duration In seconds of total vesting duration
     * @param startAt UNIX timestamp in seconds from where vesting will start
     *
     */
    function _setVesting(
        address account,
        uint256 amount,
        uint256 cliff,
        uint256 duration,
        uint256 startAt
    ) internal {
        require(account != address(0), "Address should not be zero address");
        require(startAt >= block.timestamp, "Vesting should start after current block time");
        require(cliff <= duration, "Cliff should be less that duration");
        VestedToken storage vested = vestedUser[account];
        vested.cliff = cliff;
        vested.start = startAt;
        vested.duration = duration;
        vested.totalToken = amount;
        vested.releasedToken = 0;
        vested.revoked = false;
    }

    /**
     * @notice Transfers vested tokens to beneficiary.
     * anyone can release their token
     */
    function releaseMyToken() external returns (bool) {
        releaseToken(msg.sender);
        return true;
    }

    /**
     * @notice Transfers vested tokens to the given account.
     * @param account address of the vested user
     */
    function releaseToken(address account) public {
        require(account != address(0), "Address should not be zero address");
        VestedToken storage vested = vestedUser[account];
        uint256 unreleasedToken = _releasableAmount(account); // total releasable token currently
        require(unreleasedToken > 0, "No unreleased tokens in vesting");
        vested.releasedToken = vested.releasedToken.add(unreleasedToken);
        IERC20(LCX_TOKEN).transfer(account, unreleasedToken);
        emit TokenReleased(account, unreleasedToken);
    }

    /**
     * @dev Calculates the amount that has already vested but hasn't been released yet.
     * @param account address of user
     */
    function _releasableAmount(address account)
        internal
        view
        returns (uint256)
    {
        return _vestedAmount(account).sub(vestedUser[account].releasedToken);
    }

    /**
     * @dev Calculates the amount that has already vested.
     * @param account address of the user
     */
    function _vestedAmount(address account) internal view returns (uint256) {
        VestedToken storage vested = vestedUser[account];
        uint256 totalToken = vested.totalToken;
        if (block.timestamp < vested.start.add(vested.cliff)) {
            return 0;
        } else if (
            block.timestamp >= vested.start.add(vested.duration) ||
            vested.revoked
        ) {
            return totalToken;
        } else {
            uint256 numberOfPeriods = (block.timestamp.sub(vested.start)).div(
                vested.cliff
            );
            return
                totalToken.mul(numberOfPeriods.mul(vested.cliff)).div(
                    vested.duration
                );
        }
    }

    /**
     * @notice Allows the owner to revoke the vesting. Tokens already vested
     * remain in the contract, the rest are returned to the owner.
     * @param account address in which the vesting is revoked
     */
    function revoke(address account) external onlyOwner returns (bool) {
        VestedToken storage vested = vestedUser[account];
        require(!vested.revoked, "Already revoked");
        uint256 balance = vested.totalToken;
        uint256 vestedAmount = _vestedAmount(account);
        uint256 refund = balance.sub(vestedAmount);
        require(refund > 0, "Refund amount should be more than zero");
        vested.revoked = true;
        vested.totalToken = vestedAmount;
        IERC20(LCX_TOKEN).transfer(owner(), refund);
        emit VestingRevoked(account);
        return true;
    }
}
function setVesting(
    address account,
    uint256 amount,
    uint256 cliff,
    uint256 duration,
    uint256 startAt
) external onlySaleContract returns (bool) {
    VestedToken storage vested = vestedUser[account];
    if (vested.start > 0) {
        require(vested.revoked, "Account vesting is still going on");
        uint256 unclaimedTokens = _vestedAmount(account).sub(
            vested.releasedToken
        );
        require(unclaimedTokens == 0, "Account vesting is still going on");
    }
    IERC20(LCX_TOKEN).transferFrom(_msgSender(), address(this), amount);
    _setVesting(account, amount, cliff, duration, startAt);
    return true;
}

/**
 * @dev this will set the beneficiary with vesting
 * parameters provided
 * @param account address of the beneficiary for vesting
 * @param amount  totalToken to be vested
 * @param cliff In seconds of one period in vesting
 * @param duration In seconds of total vesting
 * @param startAt UNIX timestamp in seconds from where vesting will start
 */