contract NguyeningDAO is Ownable, ERC20, ERC20Burnable {
    NodeManager public nodeManager;
    address public distributionPool;
    uint256 public rewardTimer = 1 days;
    bool isPaused;
    mapping(address => bool) private blackList;

    constructor(address _distributionPool) ERC20("NguyeningDAO", "WIN") {
        _mint(msg.sender, 10000000 * (10**18));
        distributionPool = _distributionPool;
        isPaused = false;
    }

    modifier contractPaused() {
        require(isPaused == false);
        _;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override contractPaused {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(!blackList[from] && !blackList[to], "Blacklisted address");

        super._transfer(from, to, amount);
    }

    function claimAllRewards() public contractPaused {
        address account = msg.sender;
        uint256 time = block.timestamp;
        require(
            nodeManager.getNodeNumberOf(account) > 0,
            "CLAIM ERROR: YOU DONT HAVE ANY NODES."
        );
        require(
            account != address(0),
            "CLAIM ERROR: ADDRESS CANNOT BE A ZERO ADDRESS."
        );
        require(!blackList[account], "CLAIM ERROR: BLACKLISTED ADDRESS.");
        uint256 rewardAmount = nodeManager.totalRewards(account, time);
        require(rewardAmount > 0, "CLAIM ERROR: NOT ENOUGH REWARDS.");
        require(
            balanceOf(distributionPool) >= rewardAmount,
            "CLAIM ERROR: POOL DOES NOT HAVE ENOUGH REWARDS. WAIT TILL REPLENISH."
        );
        nodeManager.claimAll(account, time);
        _transfer(distributionPool, account, rewardAmount);
        updateRewardRate();
    }

    function makeNodes(uint256 _amount, uint256 _level) public payable contractPaused {
        address account = msg.sender;
        require(
            nodeManager.getNodeNumberOf(account) + _amount <=
                nodeManager.nodeLimit(),
            "NODE CREATION: YOU WILL EXCEEDED THE NODE LIMIT."
        );
        require(
            account != address(0),
            "NODE CREATION: ADDRESS CANNOT BE A ZERO ADDRESS."
        );
        require(_level > 0, "NODE CREATION: LEVEL MUST BE GREATER THAN ZERO.");
        require(!blackList[account], "NODE CREATION: BLACKLISTED ADDRESS.");
        uint256 nodePrice = nodeManager.nodeFee() *
            _amount *
            _level;
        uint256 setupFee = nodeManager.setupFee() *
            _amount *
            _level;
        require(msg.value >= setupFee, "NODE CREATION: User did not send enough ETH.");
        require(
            balanceOf(account) >= nodePrice,
            "NODE CREATION: Not enough tokens to create node(s)."
        );
        super._transfer(account, distributionPool, nodePrice);
        nodeManager.createNode(_amount, account, _level);
        uint256 burnAmount = (nodeManager.burnAmount() * (10**18)) * _amount * _level;
        if (burnAmount >= 0 && balanceOf(distributionPool) > burnAmount) {
            super._burn(distributionPool, burnAmount);
            nodeManager.updateBurnCount(nodeManager.burnAmount() * _amount * _level);
        }
        updateRewardRate();
    }

    function levelUpNode(uint256 _index, uint256 _level) public payable contractPaused {
        address account = msg.sender;
        uint256 myNodes = nodeManager.getNodeNumberOf(account);
        require(
            myNodes > 0,
            "LEVEL UP ERROR: YOU DONT HAVE ANY NODES."
        );
        require((_index+1) <= myNodes, "LEVEL UP ERROR: THIS NODE INDEX DOES NOT EXIST.");
        require(
            account != address(0),
            "LEVEL UP ERROR: ADDRESS CANNOT BE A ZERO ADDRESS."
        );
        require(_level > 0, "LEVEL UP ERROR: LEVEL MUST BE GREATER THAN ZERO.");
        require(!blackList[account], "CLAIM ERROR: BLACKLISTED ADDRESS.");
        uint256 nodePrice = nodeManager.nodeFee() * _level;
        uint256 setupFee = nodeManager.setupFee() * _level;
        require(msg.value >= setupFee, "LEVEL UP ERROR: User did not send enough ETH.");
        require(
            balanceOf(account) >= nodePrice,
            "LEVEL UP ERROR: Not enough tokens to create node(s)."
        );
        super._transfer(account, distributionPool, nodePrice);
        nodeManager.levelUp(account, _index, _level);
        uint256 burnAmount = (nodeManager.burnAmount() * (10**18)) * _level;
        if (burnAmount >= 0 && balanceOf(distributionPool) > burnAmount) {
            super._burn(distributionPool, burnAmount);
            nodeManager.updateBurnCount(nodeManager.burnAmount() * _level);
        }
        updateRewardRate();
    }

    function compoundRewardsToLevel(uint256 _index) public payable contractPaused {
        address account = msg.sender;
        uint256 myNodes = nodeManager.getNodeNumberOf(account);
        require(
             myNodes > 0,
            "COMPOUND ERROR: YOU DONT HAVE ANY NODES."
        );
        require(
            account != address(0),
            "COMPOUND ERROR: ADDRESS CANNOT BE A ZERO ADDRESS."
        );
        require(!blackList[account], "COMPOUND ERROR: BLACKLISTED ADDRESS.");
        uint256 totalReward = nodeManager.totalRewards(account,block.timestamp);
        uint256 level =  totalReward / nodeManager.nodeFee();
        require(
            level > 0,
            "COMPOUND ERROR: NOT ENOUGH REWARDS TO COMPUND TO LEVEL"
        );
        require((_index+1) <= myNodes, "COMPOUND ERROR: THIS NODE INDEX DOES NOT EXIST.");
        uint256 setupFee = nodeManager.setupFee() * level;
        require(msg.value >= setupFee, "COMPOUNED ERROR: DID NOT SEND ENOUGH ETH FOR SETUP FEE.");
        nodeManager.claimAll(account, block.timestamp);
        nodeManager.levelUp(account, _index, level);
        nodeManager.updatePendingRewards(account, totalReward % nodeManager.nodeFee());
        uint256 burnAmount = (nodeManager.burnAmount() * (10**18)) *
            level;
        if (burnAmount >= 0 && balanceOf(distributionPool) > burnAmount) {
            super._burn(distributionPool, burnAmount);
            nodeManager.updateBurnCount(
                nodeManager.burnAmount() * level
            );
        }
        updateRewardRate();
    }

    function compoundRewards(uint256 _level) public payable contractPaused{
        address account = msg.sender;
        require(
            nodeManager.getNodeNumberOf(account) > 0,
            "COMPOUND ERROR: YOU DONT HAVE ANY NODES."
        );
        require(_level > 0, "COMPOUND ERROR: THE LEVEL MUST BE GREATER THAN ZERO.");
        require(
            account != address(0),
            "COMPOUND ERROR: ADDRESS CANNOT BE A ZERO ADDRESS."
        );
        require(!blackList[account], "COMPOUND ERROR: BLACKLISTED ADDRESS.");
        uint256 totalReward = nodeManager.totalRewards(account, block.timestamp);
        uint256 amountOfNodes =  totalReward / (nodeManager.nodeFee() * _level);
        require(
            amountOfNodes > 0,
            "COMPOUND ERROR: NOT ENOUGH REWARDS TO COMPUND TO NODE"
        );
        require(
            nodeManager.getNodeNumberOf(account) + amountOfNodes <=
                nodeManager.nodeLimit(),
            "COMPOUND ERROR: YOU WILL EXCEEDED THE NODE LIMIT."
        );
        uint256 setupFee = nodeManager.setupFee() * amountOfNodes * _level;
        require(msg.value >= setupFee, "COMPOUNED ERROR: DID NOT SEND ENOUGH ETH FOR SETUP FEE.");
        nodeManager.claimAll(account, block.timestamp);
        nodeManager.createNode(amountOfNodes, account, _level);
        nodeManager.updatePendingRewards(account, totalReward % nodeManager.nodeFee());
        uint256 burnAmount = (nodeManager.burnAmount() * (10**18)) *
            amountOfNodes * _level;
        if (burnAmount >= 0 && balanceOf(distributionPool) > burnAmount) {
            super._burn(distributionPool, burnAmount);
            nodeManager.updateBurnCount(
                nodeManager.burnAmount() * amountOfNodes * _level
            );
        }
        updateRewardRate();
    }

    function getNodeRewards(address _account) public view contractPaused returns (uint256) {
        return nodeManager.totalRewards(_account, block.timestamp);
    }

    function updateRewardRate() private {
        uint256 lastUpdated = nodeManager.getLastRewardRateUpdate();
        if(block.timestamp >= (lastUpdated + rewardTimer)){
            uint256 poolBalance = balanceOf(distributionPool);
            uint256 nodeReward = nodeManager.nodeReward();
            uint256 totalDailyRewards = getTotalNodeCount() * nodeReward;
            uint256 newReward;
            if(poolBalance/totalDailyRewards >= 75){
                newReward = nodeReward;
            } else if(poolBalance/totalDailyRewards >= 60){
                newReward = (nodeReward / 10) * 8;
            } else if(poolBalance/totalDailyRewards >= 40){
                newReward = (nodeReward / 10) * 6;
            } else if(poolBalance/totalDailyRewards >= 25){
                newReward = (nodeReward / 10) * 4;
            } else {
                newReward = (nodeReward / 10) * 2;
            }
            nodeManager.setRewardRate(newReward);
        }
    }

    function getNodeCount(address _account) public view contractPaused returns (uint256) {
        return nodeManager.getNodeNumberOf(_account);
    }

    function getTotalNodeCount() public view contractPaused returns (uint256) {
        return nodeManager.totalNodes();
    }

    function getNodeSetupFee() public view contractPaused returns (uint256) {
        return nodeManager.setupFee();
    }

    function getNodeFee() public view contractPaused returns (uint256) {
        return nodeManager.nodeFee();
    }

    function getRewardAmountPerNode() public view contractPaused returns (uint256) {
        return nodeManager.getRewardRate();
    }

    function getBurnAmount() public view contractPaused returns (uint256) {
        return nodeManager.burnAmount();
    }

    function getAllUserNodes(address _account) public view contractPaused returns(string memory){
        return nodeManager.getAllUserNodes(_account);
    }

    function getNodeByIndex(address _account, uint256 _index) public view contractPaused returns(string memory){
        return nodeManager.getNodeByIndex(_account, _index);
    }

    function getRangeUserNodes(address _account, uint256 _start, uint256 _end) public view contractPaused returns(string memory){
        return nodeManager.getRangeUserNodes(_account, _start, _end);
    }

    function getRewardByIndex(address _account, uint256 _index) public view contractPaused returns(uint256){
        return nodeManager.getRewardsByIndex(_account, block.timestamp, _index);
    }

    function getBurnCount() public view contractPaused returns(uint256){
        return nodeManager.burnCount();
    }

    function getNodeLimit() public view contractPaused returns(uint256){
        return nodeManager.nodeLimit();
    }

    function getBalanceOfDistPool() public view contractPaused returns(uint256){
        return balanceOf(distributionPool);
    }

    function getBaseReward() public view contractPaused returns(uint256){
        return nodeManager.nodeReward();
    }

    function getMinAgeReduction() public view contractPaused returns(uint256 age){
        return nodeManager.minAgeReduction();
    }

    //Owner Functions
    function pauseContract(bool _pause) public onlyOwner {
        isPaused = _pause;
    }

    function setRewardRate(uint256 _amount) public onlyOwner{
        nodeManager.setRewardRate(_amount);
    }

    function setDistributionPool(address _contract) public onlyOwner {
        distributionPool = _contract;
    }

    function setNodeManager(address _contract) external onlyOwner {
        nodeManager = NodeManager(_contract);
    }

    function claimETH() public payable onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }
    function setSetupFee(uint256 _amount) public onlyOwner {
        nodeManager.setSetupFee(_amount);
    }

    function setNodeFee(uint256 _amount) public onlyOwner {
        nodeManager.setNodeFee(_amount);
    }

    function setBaseReward(uint256 _amount) public onlyOwner {
        nodeManager.setReward(_amount);
    }

    function setBurnAmount(uint256 _amount) public onlyOwner {
        nodeManager.setBurnAmount(_amount);
    }

    function setNodeLimit(uint256 _amount) public onlyOwner {
        nodeManager.setNodeLimit(_amount);
    }

    function setGateKeeper(address _account) public onlyOwner {
        nodeManager.setGateKeeper(_account);
    }

    function setToken(address _account) public onlyOwner {
        nodeManager.setToken(_account);
    }

    function setBurnCount(uint256 _amount) public onlyOwner {
        nodeManager.setBurnCount(_amount);
    }

    function setRewardTimer(uint256 _amount) public onlyOwner {
        rewardTimer = _amount;
    }

    function ageReduction(uint256 _amount) public onlyOwner {
        nodeManager.setMinAgeReduction(_amount);
    }
}
function pauseContract(bool _pause) public onlyOwner {
    isPaused = _pause;
}

//Owner Functions