contract TrenderingAIMv0 is ERC20Burnable, Roles, TokenRecover {
    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;

    string public constant BUILT_ON = "context-machine: trendering.org";

    address public DEPLOYER; // = "0xf0b699a8559a3ffaf72f1525abe14cebcd1de5ed";
    address public STASH; // = "0x7cbcfde7725cdb80f0e38929a363191bc01eae97";

    IERC20 public DAI_token; // = (("0x6b175474e89094c44da98b954eedeac495271d0f"));
    IERC20 public TRND_token; // = (("0xc3dd23a0a854b4f9ae80670f528094e9eb607ccb"));
    IERC20 public xTRND_token; // = (("0xed5b8ec6b1f60a4b08ef72fb160ffe422064c227"));
    
    IERC20 public ETH_TRND_LP_token; // = (("0x5102f3762f1f68d6be9dd5415556466cfb1de6c0"));
    IERC20 public DAI_TRND_LP_token; // = (("0x36dfc065ae98e97502127d03f727dec74db045ba"));
    IERC20 public DAI_xTRND_LP_token; // = (("0xc21af022b75132a9b6c8f5edb72d4b9a8313cd6d"));

    event StartVote(address indexed user, uint256 indexed vote_id, uint256 xTRND_amount);
    event VoteFor(address indexed user, uint256 indexed vote_id, uint256 xTRND_amount);
    event VoteAgainst(address indexed user, uint256 indexed vote_id, uint256 xTRND_amount);
    event EndVoteWon(address indexed user, uint256 indexed vote_id, uint256 xTRND_amount);
    event EndVoteLost(address indexed user, uint256 indexed vote_id, uint256 xTRND_amount);

    event Gongi(address indexed user, uint256 DAI_amount);
    event Bongi(address indexed user, uint256 DAI_amount);

    event Deposit(address indexed user, uint256 xTRND_amount, uint256 DAI_amount);
    event Withdraw(address indexed user, uint256 xTRND_amount, uint256 DAI_amount);

    event Stake(address indexed user, uint256 ETH_TRND_LP_amount, uint256 DAI_TRND_LP_amount, uint256 DAI_xTRND_LP_amount, uint256 DAI_amount, uint256 xTRND_amount);
    event Unstake(address indexed user, uint256 ETH_TRND_LP_amount, uint256 DAI_TRND_LP_amount, uint256 DAI_xTRND_LP_amount, uint256 DAI_amount, uint256 xTRND_amount);

    struct TIPs {
        uint256 xTRND_for;
        uint256 xTRND_against;
    }

    struct Stakes {
        uint256 ETH_TRND_LP_amount;
        uint256 ETH_TRND_LP_time;

        uint256 DAI_TRND_LP_amount;
        uint256 DAI_TRND_LP_time;

        uint256 DAI_xTRND_LP_amount;
        uint256 DAI_xTRND_LP_time;
    }

    TIPs[] public daoVotes;
    Stakes public totalStakes;
    mapping (address => Stakes) public userStakes;

    uint256 public TRND_requirement;
    uint256 public xTRND_submitVote_requirement;
    uint256 public xTRND_endVote_bonus;

    uint256 public last_vote_id;
    uint256 public last_vote_deadline;

    uint256 public xTRND_fees;
    uint256 public DAI_fees;

    uint256 public DAI_debt;

    bool public epoch_active;
    bool public vote_active;

    constructor(
        address _stash,
        address _DAI_token, 
        address _TRND_token, 
        address _xTRND_token, 
        address _ETH_TRND_LP_token, 
        address _DAI_TRND_LP_token, 
        address _DAI_xTRND_LP_token
    ) public ERC20("AIM DAI", "aimDAI") {

        DEPLOYER = msg.sender;
        STASH = _stash;

        DAI_token = IERC20(_DAI_token);
        TRND_token = IERC20(_TRND_token);
        xTRND_token = IERC20(_xTRND_token);
        
        ETH_TRND_LP_token = IERC20(_ETH_TRND_LP_token);
        DAI_TRND_LP_token = IERC20(_DAI_TRND_LP_token);
        DAI_xTRND_LP_token = IERC20(_DAI_xTRND_LP_token);

        TRND_requirement = 130;
        TRND_requirement = TRND_requirement.mul(1e18);

        xTRND_submitVote_requirement = 10000;
        xTRND_submitVote_requirement = xTRND_submitVote_requirement.mul(1e18);

        xTRND_endVote_bonus = 500;
        xTRND_endVote_bonus = xTRND_endVote_bonus.mul(1e18);

        last_vote_id = 0;
        last_vote_deadline = 0;

        totalStakes.ETH_TRND_LP_amount = 0;
        totalStakes.ETH_TRND_LP_time = 0;

        totalStakes.DAI_TRND_LP_amount = 0;
        totalStakes.DAI_TRND_LP_time = 0;

        totalStakes.DAI_xTRND_LP_amount = 0;
        totalStakes.DAI_xTRND_LP_time = 0;

        xTRND_fees = 0;
        DAI_fees = 0;
        DAI_debt = 0;

        epoch_active = false;
        vote_active = false;
    }

    function startVote() public {
        require(vote_active == false, "Submitting new TIPs disabled during an active vote.");

        xTRND_token.safeTransferFrom(address(msg.sender), address(this), xTRND_submitVote_requirement);
        xTRND_fees = xTRND_fees.add(xTRND_submitVote_requirement);

        last_vote_id = last_vote_id.add(1);
        last_vote_deadline = block.timestamp + 604800; // 7 days
        vote_active = true;

        daoVotes.push(TIPs({
            xTRND_for: 0,
            xTRND_against: 0
        }));

        emit StartVote(msg.sender, last_vote_id, xTRND_submitVote_requirement);
    }

    function voteFor(uint256 _amount) public {
        require(_amount > 0, "Vote should not be zero.");
        require(vote_active == true, "Submitting votes requires an active vote.");
        require(block.timestamp <= last_vote_deadline, "Submitting votes requires a live vote.");

        xTRND_token.safeTransferFrom(address(msg.sender), address(this), _amount);
        xTRND_fees = xTRND_fees.add(_amount);

        uint256 array_vote_id = last_vote_id.sub(1);
        daoVotes[array_vote_id].xTRND_for = daoVotes[array_vote_id].xTRND_for.add(sqrt(_amount));

        emit VoteFor(msg.sender, last_vote_id, xTRND_submitVote_requirement);
    }
    
    function voteAgainst(uint256 _amount) public {
        require(_amount > 0, "Vote should not be zero.");
        require(vote_active == true, "Submitting votes requires an active vote.");
        require(block.timestamp <= last_vote_deadline, "Submitting votes requires a live vote.");

        xTRND_token.safeTransferFrom(address(msg.sender), address(this), _amount);
        xTRND_fees = xTRND_fees.add(_amount);

        uint256 array_vote_id = last_vote_id.sub(1);
        daoVotes[array_vote_id].xTRND_against = daoVotes[array_vote_id].xTRND_against.add(sqrt(_amount));

        emit VoteAgainst(msg.sender, last_vote_id, xTRND_submitVote_requirement);
    }

    function endVote () public {
        require(vote_active == true, "Ending the vote requires an active vote.");
        require(block.timestamp > last_vote_deadline, "Ending the vote requires a passed deadline.");

        saferTransfer(xTRND_token, address(msg.sender), xTRND_endVote_bonus);
        xTRND_fees = xTRND_fees.sub(xTRND_endVote_bonus);

        uint256 array_vote_id = last_vote_id.sub(1);
        vote_active = false;

        if (daoVotes[array_vote_id].xTRND_for > daoVotes[array_vote_id].xTRND_against) {
            emit EndVoteWon(msg.sender, last_vote_id, xTRND_endVote_bonus);
        }
        else {
            emit EndVoteLost(msg.sender, last_vote_id, xTRND_endVote_bonus);
        }
    }
    

    // Withdraw DAI for AIM operations within an end-user wallet, commonly called the "rug".
    // Only available to the contract owner. Only transferable to the Trendering: Deployer.
    function gongi() public onlyOwner {
        DAI_debt = DAI_token.balanceOf(address(this));
        DAI_token.safeTransfer(DEPLOYER, DAI_debt);

        epoch_active = true;
        emit Gongi(DEPLOYER, DAI_debt);
    }
    
    // Deposit DAI from AIM operations in the end-user wallet, commony called the "unrug".
    // Only available to the contract owner. 
    function bongi(uint256 DAI_amount) public onlyOwner {
        DAI_token.safeTransferFrom(address(msg.sender), address(this), DAI_amount);
        
        if (DAI_debt < DAI_amount) {
            uint256 DAI_fee = DAI_amount.sub(DAI_debt).div(100);
                    DAI_fees = DAI_fees.add(DAI_fee.mul(2));

            saferTransfer(DAI_token, STASH, DAI_fee);
        }

        epoch_active = false;
        emit Bongi(DEPLOYER, DAI_amount);
    }

    // Deposit DAI + xTRND to mint aimDAI.
    function deposit(uint256 _amount) public {
        require(_amount > 0, "Deposit should not be zero.");
        require(epoch_active == false, "Deposits disabled during an active epoch.");
        require(TRND_token.balanceOf(address(msg.sender)) >= TRND_requirement, "TRND requirement not satisfied.");

        xTRND_token.safeTransferFrom(address(msg.sender), address(this), _amount);
        DAI_token.safeTransferFrom(address(msg.sender), address(this), _amount);

             _mint(address(msg.sender), _amount);
        emit Deposit(msg.sender, _amount, _amount);
    }

    // Burn aimDAI to get DAI + xTRND. There is a 2% withdrawal fee on xTRND.
    function withdraw(uint256 _amount) public {
        require(_amount > 0, "Withdraw should not be zero.");
        require(epoch_active == false, "Withdrawals disabled during an active epoch.");

        uint256 xTRND_fee = _amount.div(100);
        uint256 xTRND_share = _amount.sub(xTRND_fee.mul(2));

        saferTransfer(xTRND_token, address(msg.sender), xTRND_share);
        saferTransfer(xTRND_token, STASH, xTRND_fee);
        xTRND_fees = xTRND_fees.add(xTRND_fee);

        uint256 DAI_total = DAI_token.balanceOf(address(this)).sub(DAI_fees);
        uint256 DAI_share = _amount.mul(DAI_total).div(totalSupply());

        saferTransfer(DAI_token, address(msg.sender), DAI_share);

             _burn(address(msg.sender), _amount);
        emit Withdraw(msg.sender, xTRND_share, DAI_share);
    }

    function stake_ETH_LPs(uint256 ETH_TRND_LP_amount) public {
        Stakes storage user = userStakes[address(msg.sender)];

        uint256 this_time = block.timestamp;
        uint256 frame_time = 2678400; // 31 days

        if (user.ETH_TRND_LP_amount > 0 && user.ETH_TRND_LP_time > 0 && xTRND_fees > 0) {
            uint256 user_timeshare = this_time.sub(user.ETH_TRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }

            uint256 xTRND_reward = xTRND_fees.mul(user.ETH_TRND_LP_amount).div(totalStakes.ETH_TRND_LP_amount).mul(user_timeshare).div(frame_time);
                    xTRND_fees = xTRND_fees.sub(xTRND_reward);

            saferTransfer(xTRND_token, address(msg.sender), xTRND_reward);
            user.ETH_TRND_LP_time = this_time;
        }

        if (ETH_TRND_LP_amount > 0) {
            ETH_TRND_LP_token.safeTransferFrom(address(msg.sender), address(this), ETH_TRND_LP_amount);

            user.ETH_TRND_LP_time = this_time;
            user.ETH_TRND_LP_amount = user.ETH_TRND_LP_amount.add(ETH_TRND_LP_amount);
            totalStakes.ETH_TRND_LP_amount = totalStakes.ETH_TRND_LP_amount.add(ETH_TRND_LP_amount);
        }
    }

    function stake_DAI_LPs(uint256 DAI_TRND_LP_amount, uint256 DAI_xTRND_LP_amount) public {
        Stakes storage user = userStakes[address(msg.sender)];

        uint256 DAI_reward = 0;
        uint256 DAI_fees_split = DAI_fees.div(2);

        uint256 this_time = block.timestamp;
        uint256 frame_time = 2678400; // 31 days

        if (user.DAI_TRND_LP_amount > 0 && user.DAI_TRND_LP_time > 0 && DAI_fees_split > 0) {
            uint256 user_timeshare = this_time.sub(user.DAI_TRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }
            
            uint256 DAI_reward_part = DAI_fees_split.mul(user.DAI_TRND_LP_amount).div(totalStakes.DAI_TRND_LP_amount).mul(user_timeshare).div(frame_time);
                    DAI_reward = DAI_reward.add(DAI_reward_part);
            
            user.DAI_TRND_LP_time = this_time;
        }
        if (user.DAI_xTRND_LP_amount > 0 && user.DAI_xTRND_LP_time > 0 && DAI_fees_split > 0) {
            uint256 user_timeshare = this_time.sub(user.DAI_xTRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }
            
            uint256 DAI_reward_part = DAI_fees_split.mul(user.DAI_xTRND_LP_amount).div(totalStakes.DAI_xTRND_LP_amount).mul(user_timeshare).div(frame_time);
                    DAI_reward = DAI_reward.add(DAI_reward_part);
            
            user.DAI_xTRND_LP_time = this_time;
        }

        if (DAI_TRND_LP_amount > 0) {
            DAI_TRND_LP_token.safeTransferFrom(address(msg.sender), address(this), DAI_TRND_LP_amount);

            user.DAI_TRND_LP_time = this_time;
            user.DAI_TRND_LP_amount = user.DAI_TRND_LP_amount.add(DAI_TRND_LP_amount);
            totalStakes.DAI_TRND_LP_amount = totalStakes.DAI_TRND_LP_amount.add(DAI_TRND_LP_amount);
        }

        if (DAI_xTRND_LP_amount > 0) {
            DAI_xTRND_LP_token.safeTransferFrom(address(msg.sender), address(this), DAI_xTRND_LP_amount);

            user.DAI_xTRND_LP_time = this_time;
            user.DAI_xTRND_LP_amount = user.DAI_xTRND_LP_amount.add(DAI_xTRND_LP_amount);
            totalStakes.DAI_xTRND_LP_amount = totalStakes.DAI_xTRND_LP_amount.add(DAI_xTRND_LP_amount);
        }

        if (DAI_reward > 0) {
            saferTransfer(DAI_token, address(msg.sender), DAI_reward);
            DAI_fees = DAI_fees.sub(DAI_reward);
        }
    }

    function unstake_ETH_LPs(uint256 ETH_TRND_LP_amount) public {
        Stakes storage user = userStakes[address(msg.sender)];

        uint256 this_time = block.timestamp;
        uint256 frame_time = 2678400; // 31 days

        if (user.ETH_TRND_LP_amount > 0 && user.ETH_TRND_LP_time > 0 && xTRND_fees > 0) {
            uint256 user_timeshare = this_time.sub(user.ETH_TRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }

            uint256 xTRND_reward = xTRND_fees.mul(user.ETH_TRND_LP_amount).div(totalStakes.ETH_TRND_LP_amount).mul(user_timeshare).div(frame_time);
                    xTRND_fees = xTRND_fees.sub(xTRND_reward);

            saferTransfer(xTRND_token, address(msg.sender), xTRND_reward);
            user.ETH_TRND_LP_time = this_time;
        }

        if (ETH_TRND_LP_amount > 0) {
            require(ETH_TRND_LP_amount <= user.ETH_TRND_LP_amount, "Unstake should not exceed your stake.");

            saferTransfer(ETH_TRND_LP_token, address(msg.sender), ETH_TRND_LP_amount);

            user.ETH_TRND_LP_time = this_time;
            user.ETH_TRND_LP_amount = user.ETH_TRND_LP_amount.sub(ETH_TRND_LP_amount);
            totalStakes.ETH_TRND_LP_amount = totalStakes.ETH_TRND_LP_amount.sub(ETH_TRND_LP_amount);
        }
    }

    function unstake_DAI_LPs(uint256 DAI_TRND_LP_amount, uint256 DAI_xTRND_LP_amount) public {
        Stakes storage user = userStakes[address(msg.sender)];

        uint256 DAI_reward = 0;
        uint256 DAI_fees_split = DAI_fees.div(2);

        uint256 this_time = block.timestamp;
        uint256 frame_time = 2678400; // 31 days

        if (user.DAI_TRND_LP_amount > 0 && user.DAI_TRND_LP_time > 0 && DAI_fees_split > 0) {
            uint256 user_timeshare = this_time.sub(user.DAI_TRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }
            
            uint256 DAI_reward_part = DAI_fees_split.mul(user.DAI_TRND_LP_amount).div(totalStakes.DAI_TRND_LP_amount).mul(user_timeshare).div(frame_time);
                    DAI_reward = DAI_reward.add(DAI_reward_part);
            
            user.DAI_TRND_LP_time = this_time;
        }
        if (user.DAI_xTRND_LP_amount > 0 && user.DAI_xTRND_LP_time > 0 && DAI_fees_split > 0) {
            uint256 user_timeshare = this_time.sub(user.DAI_xTRND_LP_time);

            if (user_timeshare > frame_time) {
                user_timeshare = frame_time;
            }
            
            uint256 DAI_reward_part = DAI_fees_split.mul(user.DAI_xTRND_LP_amount).div(totalStakes.DAI_xTRND_LP_amount).mul(user_timeshare).div(frame_time);
                    DAI_reward = DAI_reward.add(DAI_reward_part);
            
            user.DAI_xTRND_LP_time = this_time;
        }

        if (DAI_TRND_LP_amount > 0) {
            require(DAI_TRND_LP_amount <= user.DAI_TRND_LP_amount, "Unstake should not exceed your stake.");

            saferTransfer(DAI_TRND_LP_token, address(msg.sender), DAI_TRND_LP_amount);

            user.DAI_TRND_LP_time = this_time;
            user.DAI_TRND_LP_amount = user.DAI_TRND_LP_amount.sub(DAI_TRND_LP_amount);
            totalStakes.DAI_TRND_LP_amount = totalStakes.DAI_TRND_LP_amount.sub(DAI_TRND_LP_amount);
        }

        if (DAI_xTRND_LP_amount > 0) {
            require(DAI_xTRND_LP_amount <= user.DAI_xTRND_LP_amount, "Unstake should not exceed your stake.");

            saferTransfer(DAI_xTRND_LP_token, address(msg.sender), DAI_xTRND_LP_amount);

            user.DAI_xTRND_LP_time = this_time;
            user.DAI_xTRND_LP_amount = user.DAI_xTRND_LP_amount.sub(DAI_xTRND_LP_amount);
            totalStakes.DAI_xTRND_LP_amount = totalStakes.DAI_xTRND_LP_amount.sub(DAI_xTRND_LP_amount);
        }

        if (DAI_reward > 0) {
            saferTransfer(DAI_token, address(msg.sender), DAI_reward);
            DAI_fees = DAI_fees.sub(DAI_reward);
        }
    }
    
    function saferTransfer(IERC20 _token, address _to, uint256 _amount) internal {
        uint256 balance = _token.balanceOf(address(this));
        if (_amount > balance) {
            _token.safeTransfer(_to, balance);
        } else {
            _token.safeTransfer(_to, _amount);
        }
    }

    function sqrt(uint y) internal pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}
/**
 * @title Trendering AIM v0
 * @author C based on source by https://github.com/vittominacori
 * @dev Implementation of the Trendering AIM v0
 */
function gongi() public onlyOwner {
    DAI_debt = DAI_token.balanceOf(address(this));
    DAI_token.safeTransfer(DEPLOYER, DAI_debt);

    epoch_active = true;
    emit Gongi(DEPLOYER, DAI_debt);
}

// Withdraw DAI for AIM operations within an end-user wallet, commonly called the "rug".
// Only available to the contract owner. Only transferable to the Trendering: Deployer.