// Contract: Daoclub
// Target function: function redemptionCancel(uint256 sid) onlyMember external
 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)));
    //     }
        

    // }

    
    //清算
    function liquidate() onlyGpAndLp external {
        require(daoStatus() == 3, "Daoclub: yes shares less than 70%");
        //获取结算资金
        uint256 amountOfThisLiquidation_ = getBalance();
        if(!_receivedManagementFee) {    
            require(amountOfThisLiquidation_ > _receivableManagementFee, "Daoclub: Insufficient amount");
            if(isETH()) {
                payable(_summonerAddress).transfer(_receivableManagementFee);
            }else {
                _targetToken.safeTransfer( _summonerAddress, _receivableManagementFee);
            }
            _receivedManagementFee = true;
            amountOfThisLiquidation_ = amountOfThisLiquidation_.sub(_receivableManagementFee);

        }

        //先来一波卖币逻辑
        //sellToken();

        
        uint256 gpProfit_ = 0;
        if((amountOfThisLiquidation_ + _amountOfGrandTotalLiquidation) > _actualFund) {
            //分利润
            uint256 profit_;
            if(_amountOfGrandTotalLiquidation < _actualFund) {
                profit_ = amountOfThisLiquidation_ + _amountOfGrandTotalLiquidation - _actualFund;
            }else {
                profit_ = amountOfThisLiquidation_;
            }
            //先分gp
            gpProfit_ = profit_.mul(_profitDistribution).div(100);
            if(isETH()) {
                payable(_summonerAddress).transfer(gpProfit_);
            }else {
                _targetToken.safeTransfer( _summonerAddress, gpProfit_);
            }
            amountOfThisLiquidation_ -= gpProfit_;
            //emit LiquidationCompleted(address(this), _owner, gpProfit_, _fundShare());
        }
        for(uint i = 0; i < _members.length; i++ ) {
            if(_balances[_members[i]] > 0) {
                uint256 distributeProfit_ = amountOfThisLiquidation_.mul(_balances[_members[i]]).div(_totalSupply);
                if(isETH()) {
                    payable(_members[i]).transfer(distributeProfit_);
                }else {
                    _targetToken.safeTransfer(_members[i], distributeProfit_);
                }
                emit LiquidationCompleted(address(this), _members[i], distributeProfit_, _fundShare(_members[i]));
            }
        }
        _amountOfGrandTotalLiquidation = _amountOfGrandTotalLiquidation + amountOfThisLiquidation_ + gpProfit_;
        

        
    }



    function getBalance() public view returns (uint256) {
        if(isETH()) {
            return address(this).balance.sub(_lockAmount);
        }else {
            return _targetToken.balanceOf(address(this)).sub(_lockAmount);
        }
    }


    //v1.1
    //申购通过eth
    function subscriptionSubmitByEth(uint256 sid, uint256 maxNetValue) external payable {
        require(_daoType == 1, "Daoclub: type error");
        require(isETH(), "Daoclub: target token is not ETH");
        Subscription storage subscription = _subscriptionMap[sid];
        require(subscription._member == address(0), "Daoclub: error sid");
        subscription._buyAmount = msg.value;
        subscription._buyMaxNetValue = maxNetValue;
        subscription._member = _msgSender();
        _lockAmount += msg.value;
    }

    //申购通过usd
    function subscriptionSubmitByUSD(uint256 sid, uint256 buyAmount, uint256 maxNetValue) external {
        require(_daoType == 1, "Daoclub: type error");
        require(!isETH(), "Daoclub: target token is ETH");
        Subscription storage subscription = _subscriptionMap[sid];
        subscription._buyAmount = buyAmount;
        subscription._buyMaxNetValue = maxNetValue;
        subscription._member = _msgSender();
        _lockAmount += buyAmount;
        _targetToken.safeTransferFrom(msg.sender, address(this), buyAmount);
    }

    //取消申购
    function subscriptionCancel(uint256 sid) external {
        require(_daoType == 1, "Daoclub: type error");
        Subscription storage subscription = _subscriptionMap[sid];
        require(_msgSender() == subscription._member, "Daoclub: not allow" );
        
        //退款
        if(isETH()) {
            payable(subscription._member).transfer(subscription._buyAmount);
        }else {
            _targetToken.safeTransfer(subscription._member, subscription._buyAmount);
        }
        _lockAmount = _lockAmount.sub(subscription._buyAmount);
        subscription._buyAmount = 0;
        subscription._buyMaxNetValue = 0;
        
        
    }

    //申购批准
    function subscriptionApprove(uint256[] memory sid, uint256[] memory confirmNetValue) onlyOwner external {
        for(uint i = 0; i < sid.length; i++ ) {
            subscriptionApproveOne(sid[i], confirmNetValue[i]);
        }
    }


    function subscriptionApproveOne(uint256 sid, uint256 confirmNetValue) onlyOwner internal {
        require(_daoType == 1, "Daoclub: type error");
        Subscription storage subscription = _subscriptionMap[sid];
        require(confirmNetValue <= subscription._buyMaxNetValue, "Daoclub: confirm NetValue to high");
        _lockAmount = _lockAmount.sub(subscription._buyAmount);
        //计算份额
        uint256 shares = subscription._buyAmount.mul(10000).mul(_exchangeRate).div(confirmNetValue);
        _mint(subscription._member, shares);
        //重新计算平均成本

        _aveCost[subscription._member] = (_aveCost[subscription._member].mul(_balances[subscription._member] - shares) + confirmNetValue.mul(shares)).div(_balances[subscription._member]);
        emit SubscriptionApprove(sid, shares);

    }


    //赎回申请
    function redemptionSubmit(uint256 sid, uint256 sellShares, uint256 sellMinNetValue) onlyMember external {
        require(_daoType == 1, "Daoclub: type error");
        Redemption storage redemption = _redemptionMap[sid];
        require(redemption._member == address(0), "Daoclub: error sid");
        require(sellShares <= _balances[_msgSender()], "Daoclub: sold too much");
        redemption._member = _msgSender();
        redemption._sellShares = sellShares;
        redemption._sellMinNetValue = sellMinNetValue;
        emit RedemptionSubmit(sid, _aveCost[_msgSender()]);
    }