// Contract: Daoclub
// Target function: function liquidate() onlyGpAndLp external
(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}



contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

contract Daoclub is ReentrancyGuard, IERC20 {
    
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    

   // ISwapRouter public swapRouter;

    
    /* erc20 param */
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    uint256 private _totalSupply;
    string private _name;
    string private _symbol;
    /* erc20 param */

    /*遍历成员*/
    mapping(address => bool) private _inserted;
    address[] public _members;

    //todo 如何获取合约的所有代币
    //address[] public _tokenContracts;

    /* daoclub param */
    address private _owner;
    address private _summonerAddress;
    bool private _initialized;
    uint256 public _initTimestamp;
    uint8 public _daoStatus; //0: Fundraising,1: Fundraising completed operation,2: Liquidation in progress,3: Liquidation completed
    
    mapping(address => uint8) public _voteResult; //（0未投票 1yes 2no）
    uint256 public _yesShares;
    uint256 public _noShares;
    
    
    uint256 public _totalFund;
    uint256 public _actualFund;
    uint256 public _miniOffering;
    uint256 public _amountOfGrandTotalLiquidation;
    uint8 private _managementFee;
    uint8 private _profitDistribution;
    uint256 public _periodTimestamp;
    uint256 private _duration;
    string public _targetSymbol;  //ETH/USDT/USDC
    IERC20 public _targetToken;
    //IWETH9 private _targetWeth;
    uint256 public _receivableManagementFee;
    bool public _receivedManagementFee;
    /* daoclub param */

    struct InitUint8 {
        uint8  managementFee;
        uint8  profitDistribution;
        uint8  daoType;
    }



    /*v1.1*/
    uint8 public _daoType;  //0: active--close, 1:active--open
    uint256 public _exchangeRate; //  totalSupply/actaulFund
    mapping(address => uint256) public _aveCost;

    struct Subscription {
        address _member;
        uint256 _buyAmount; 
        uint256 _buyMaxNetValue; 
    }

    struct Redemption {
        address _member;
        uint256 _sellShares; 
        uint256 _sellMinNetValue; 
    }

    mapping(uint256 => Subscription) public _subscriptionMap;
    mapping(uint256 => Redemption) public _redemptionMap;
    uint256 public _lockAmount;
    /*v1.1*/


    /***********
    EVENT
    ***********/
    event BuyToken(address indexed buyer, uint256 amount);
    event FundraisingCompleted();
    event SubmitProposal(address indexed daoAddress, address indexed Submitter);
    event SubmitVote(address indexed daoAddress, address indexed voter, uint8 vote, uint256 shares);
    event ProposalSucceeded(address indexed daoAddress, address voter);
    event ProposalFailed(address indexed daoAddress, address voter);
    event LiquidationCompleted(address indexed daoAddress, address memberAddress, uint256 amount, uint256 totalAmount);

    //v1.1
    event SubscriptionSubmit();
    event SubscriptionApprove(uint256 sid, uint256 shares);
    event RedemptionSubmit(uint256 sid, uint256 aveCost);
    event RedemptionApprove(uint256 sid, uint256 payAmount, uint256 aveCost);


    /********
    MODIFIERS
    ********/
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    modifier onlyMember {
        require(_balances[msg.sender] > 0, "Daoclub ::onlyMember - not a member");
        _;
    }

    modifier onlyGpAndLp {
        require(_balances[msg.sender] > 0 || _owner == msg.sender, "Daoclub :: onlyGpAndLp");
        _;
    }

    modifier possibleToByToken {
        require(daoStatus() == 0, "Daoclub Can not buy: status error");
        require(block.timestamp < _periodTimestamp, "Daoclub Can not buy: Time has expired");
        require(getBalance() <= _totalFund, "Daoclub Can not buy: enough to raise");
        _;
    }

    receive() external payable {
    }


    fallback() external payable {
    }

    function daoStatus() public view returns(uint8 daoStatus_) {
        if(_daoStatus == 0 && block.timestamp > _periodTimestamp) {
            daoStatus_ = 1;
        }else {
            daoStatus_ = _daoStatus;
        }
    }

    /**测试完了记得删除**/
    // function resetInitTimeStamp(uint256 initTimestamp) external {
    //     _initTimestamp = initTimestamp;
    // }


    function init(
        address summoner,
        string memory tokenSymbol,
        uint256 totalSupply_,
        uint256 totalFund,
        uint256 miniOffering,
        InitUint8 memory initUint8,
        uint256  periodTimestamp,
        uint256  duration,
        address summonerAddress,
        string memory targetSymbol
    ) external {
        require(_initialized == false, "Daoclub: cannot be initialized repeatedly ");
        _initialized = true;
        _owner = summoner;
        _daoStatus = 0;
        _name = tokenSymbol;
        _symbol = tokenSymbol;
        _miniOffering = miniOffering;
        _managementFee = initUint8.managementFee;
        _profitDistribution = initUint8.profitDistribution;
        _daoType = initUint8.daoType;
        _totalFund = totalFund;
        //铸币
        _mint(address(this), totalSupply_);
        _targetSymbol = targetSymbol;
        if (compareStr(targetSymbol, "USDT")) {
            _targetToken = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); //USDT主网合约地址0xdAC17F958D2ee523a2206206994597C13D831ec7 测试地址0xB61d1dB83E6478e3daDf22caEb79D1ceC613ab0e
        } else if(compareStr(targetSymbol, "USDC")) {
            _targetToken = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);  //USDC主网合约地址0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
        } else {
            //_targetToken = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);  //WETH合约地址 rinnkby 0xDf032Bc4B9dC2782Bb09352007D4C57B75160B15
            //_targetWeth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);   //WETH合约地址 rinnkby 0xDf032Bc4B9dC2782Bb09352007D4C57B75160B15
        }
        _periodTimestamp = periodTimestamp;
        _duration = duration;
        _summonerAddress = summonerAddress;
        _initTimestamp = block.timestamp;
        //明确exchangeRate
        _exchangeRate = _totalSupply.div(_totalFund);
        //swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); //SwapRouter合约地址
    }
    


    function withdraw(uint256 amount) onlyOwner external {
        require(daoStatus() == 1, "Daoclub: Can only be withdrawn after the fundraising is completed");
        require(amount <= getBalance(), "Daoclub: The withdrawal amount cannot be greater than the dao balance");
        //burn 
        fundraisingCompleted();

        if(isETH()) {
            payable(_summonerAddress).transfer(amount);
        }else {
            _targetToken.safeTransfer(_summonerAddress, amount);
        }
    }



    function buyTokenByETH() possibleToByToken external payable {
        require(isETH(), "Daoclub: target token is not ETH");
        require(address(this).balance <= _totalFund, "Can not buy: enough to raise");
        require(daoStatus() == 0, "Daoclub: status can not buy");
        require(msg.value >= _miniOffering, "Daoclub: miniOffering are not met");
        _actualFund += msg.value;
        //send token
        _transfer(address(this), msg.sender, _totalSupply.mul(msg.value).div(_totalFund));

        emit BuyToken(msg.sender, msg.value);
        
        _aveCost[_msgSender()] = 10000;
        fundraisingCompleted();
    }


    function buyTokenByUSDTorUSDC(uint256 amount) possibleToByToken external {
        require(!isETH(), "Daoclub: target token is ETH");
        require(_targetToken.balanceOf(address(this)) + amount <= _totalFund, "Can not buy: enough to raise");
        require(daoStatus() == 0, "Daoclub: status can not buy");
        require(amount >= _miniOffering, "Daoclub: miniOffering are not met");
        
        
        
        
        
        //_targetToken.transferFrom(msg.sender, address(this), amount); 
        _targetToken.safeTransferFrom(msg.sender, address(this), amount);
        
        _actualFund += amount;
        
        //send token
        _transfer(address(this), msg.sender, _totalSupply.mul(amount).div(_totalFund));
        

        emit BuyToken(msg.sender, amount);
        _aveCost[_msgSender()] = 10000;
        fundraisingCompleted();
    }

    //募集结束
    function fundraisingCompleted() onlyGpAndLp public {
        if(block.timestamp > _periodTimestamp || _actualFund == _totalFund || _totalFund.sub(_actualFund) < _miniOffering) {
            _daoStatus = 1;
            //销毁剩余代币
            if(balanceOf(address(this)) > 0) {
                _burn(address(this), balanceOf(address(this)));
            }
            emit FundraisingCompleted();
        }
    }



    //发起提案
    function submitProposal() onlyGpAndLp external {
        fundraisingCompleted();
        require(daoStatus() == 1, " Proposal not allowed in current status ");
        _daoStatus = 2;
        _yesShares = 0;
        _noShares = 0;
        for(uint i = 0; i < _members.length; i++ ) {
            _voteResult[_members[i]] = 0;
        }
        emit SubmitProposal(address(this), msg.sender);
    }


    //提交投票
    function submitVote(uint8 vote) onlyMember external {
        require(daoStatus()== 2, "no Proposal");
        //判断是否投过票
        if(_voteResult[msg.sender] == 0) {
            //没投过
            _voteResult[msg.sender] = vote;
            if(vote == 1) {
                _voteYes();
            }else {
                _voteNo();
            }    
        }else {
            require(_voteResult[msg.sender] != vote, "Can't vote again");
            if(_voteResult[msg.sender] == 1) {
                _yesShares -= _balances[msg.sender];
                _voteNo();
            }else {
                _noShares -= _balances[msg.sender];
                _voteYes();
            }
        }
        
        emit SubmitVote(address(this), msg.sender, vote, _balances[msg.sender].mul(100).div(_totalSupply));

    }

    function _voteYes() internal { 
        _yesShares += _balances[msg.sender];
        if(_yesShares >= _totalSupply.mul(7).div(10)) {
            //投票成功 ， 触发清算， xx时间之后 自动清算 但是我不能在这sleep啊
            //状态变更 投票通过
            _daoStatus = 3;
            //计算应收管理费
            //首先计算管理天数
            uint256 managementDays_ = block.timestamp.sub(_initTimestamp).div(24 * 3600);
            if(managementDays_ > _duration) {
                managementDays_ = _duration;
            }
            _receivableManagementFee = _actualFund.mul(_managementFee).div(36500).mul(managementDays_);
            _receivedManagementFee = false;
            

            emit ProposalSucceeded(address(this), msg.sender);
        }
    }

    function _voteNo() internal {
        _noShares += _balances[msg.sender];
        if(_noShares >= _totalSupply.mul(3).div(10)) {
            //本次提案失败，DAO状态回退
            _daoStatus = 1;
            emit ProposalFailed(address(this), msg.sender);
        }
    }

    


    function isETH() internal view returns(bool) {
        return compareStr(_targetSymbol, "ETH");
    }


    // function sellToken() internal returns (uint256 amountOut){
    //     //合约中的币 怎么获取 如果能获取
    //     //遍历目标币合约
    //     for(uint8 i = 0; i< _tokenContracts.length; i++) {
    //         //通过UNISWAP卖出币收回targetSymbol;
    //         // 将资产授权给 swapRouter
    //         TransferHelper.safeApprove(_tokenContracts[i], address(swapRouter), IERC20(_tokenContracts[i]).balanceOf(address(this)));
    //         // amountOutMinimum 在生产环境下应该使用 oracle 或者其他数据来源获取其值
    //         ISwapRouter.ExactInputSingleParams memory params =
    //             ISwapRouter.ExactInputSingleParams({
    //                 tokenIn: _tokenContracts[i],
    //                 tokenOut: address(_targetToken),
    //                 fee: 3000,
    //                 recipient: address(this),
    //                 deadline: block.timestamp,
    //                 amountIn: IERC20(_tokenContracts[i]).balanceOf(address(this)),
    //                 amountOutMinimum: 0,
    //                 sqrtPriceLimitX96: 0
    //             });

    //         amountOut = swapRouter.exactInputSingle(params);

    //     }
    //     if(isETH()) {
    //         //把weth拆封变成eth
    //         _targetWeth.withdraw(_targetToken.balanceOf(address(this)));
    //     }
        

    // }