contract settle  is Owner  {
	uint256 internal constant DAY = 86400;
    // We use six day weeks to ensure that all time references divide evenly
    uint256 internal constant WEEK = DAY * 6;
    uint256 internal constant MONTH = WEEK * 5;
    uint256 internal constant QUARTER = MONTH * 3;

	address constant NotionalAddress = 0x1344A36A1B56144C3Bc62E7757377D288fDE0369;
	using SafeInt256 for int256;

	constructor(){
		
	}

	
	function convertToUnderlying(uint16 currencyId,int256 assetBalance) public view returns (int256) {

		(,AssetRateStorage memory assetRate) = Notional(NotionalAddress).getRateStorage(currencyId);

		int256 rate = AssetRateAdapter(assetRate.rateOracle).getExchangeRateView(); //从外部地址获取兑换率 ctoken与底层资产的兑换率

        // Calculation here represents:
        // rate * balance * internalPrecision / rateDecimals * underlyingPrecision
        int256 underlyingBalance = rate
            .mul(assetBalance)
            .div(1e10)
            .div(int256(uint256(assetRate.underlyingDecimalPlaces)));

        return underlyingBalance;
    }

	//marketIndex 为要兑换的市场，这里千万注意 因为下个市场会变化
	//cashBalance为负数,fcashAmount为负数
	function getPayCashAndFcash(address account,uint16 currencyId,uint256 marketIndex) public view  returns(int256 ,int256) {
		//在这之前市场必须要进行初始化	
		int256 fcashAmount = 0;

		uint256 blockTime = block.timestamp;
		(int256 cashBalanceAsset,,) = Notional(NotionalAddress).getAccountBalance(currencyId,account);
		if(cashBalanceAsset >= 0) { //已经被结算过了
			return (cashBalanceAsset,fcashAmount);  //这里就不继续转换成底层资产了,因为已经不需要结算了
		}

		//注意cashBalanceAsset是ctoken,这里做一次转换
		int256 cashBalance = convertToUnderlying(currencyId,cashBalanceAsset);

		//注意fcashAmount的符号,这里cashBalance为负数 fcashAmount就为正数。如果传错了滑点就会滑向相反的方向，与实际情况不符
		//所以要传入负数 最终结果fcash为负数。这里表示用户要得到正的cash,账户会累计多少负的fcash
		//此处的含义是结算人为了帮助 欠款人 结算 需要付出cashBalance的资金，从而自己需要从市场上借多少fcash
		fcashAmount = Notional(NotionalAddress).getfCashAmountGivenCashAmount(currencyId,int88(-cashBalance),marketIndex,blockTime);

		//因为最终得到正的fcash会稍微有盈余，所以这里可以适当增加fcash的数量

		return (cashBalance,fcashAmount);
	}

	function executeTradesByMultiUser(address[] memory accounts,uint16 currencyId,uint256 maturity) isOwner external {
		//判断时间是否到了可以初始化与清算的时候, maturity为到期日
		uint256 blockTime = block.timestamp;
		require(blockTime >= maturity,"please settle later");
		//判断市场是否需要初始化. 这个必须在getPayCashAndFcash 之前
		_initMaketIfRequired(currencyId);

		for (uint i = 0; i < accounts.length; i++) {
			_executeTrade(accounts[i],currencyId);
		}
	}

	function executeTradesBySingleUser(address account,uint16 currencyId,uint256 maturity) isOwner external {
		//判断时间是否到了可以初始化与清算的时候, maturity为到期日
		uint256 blockTime = block.timestamp;
		require(blockTime >= maturity,"please settle later");
		//判断市场是否需要初始化. 这个必须在getPayCashAndFcash 之前
		_initMaketIfRequired(currencyId);

		_executeTrade(account,currencyId);
	}

	//用来与合约进行交互, 方便后续提款.
	function executeTradesBatch(BalanceActionWithTrades[] memory tradeBatch) isOwner external {
		Notional(NotionalAddress).batchBalanceAndTradeAction(address(this),tradeBatch);
	}

	function ERC20Transfer(address token,address to,uint256 amount) isOwner external {
        ICERC20(token).transfer(to,amount);
    }

    function ERC20TransferFrom(address token,address to,uint256 amount) isOwner external {
        ICERC20(token).transferFrom(address(this),to,amount);
    }

	function ETHTransfer() isOwner external {
        payable(msg.sender).transfer(address(this).balance);
    }


	//结算单个用户
	function _executeTrade(address account,uint16 currencyId) internal {
		
		//得到是否需要结算
		_settleAccountIfRequired(account);

		uint256 marketIndex = 1; //这里marketIndex永远是1,表示在市场初始化之后的 第一个市场

        //这里fcashAmount也为负数
		(int256 cashBalance,int256 fcashAmount) = getPayCashAndFcash(account,currencyId,marketIndex);

		if(cashBalance >= 0){ //已经被结算了
			return ;
		}
		
		
		bytes memory tradeBorrowItem = new bytes(32);
		tradeBorrowItem = abi.encodePacked(uint8(TradeActionType.Borrow),uint8(marketIndex),uint88(uint256(-fcashAmount)),uint32(0));
		
	

		bytes memory tradeSettleItem = new bytes(32);
		tradeSettleItem = abi.encodePacked(uint8(TradeActionType.SettleCashDebt),account,uint88(0));
		
		
		bytes32[] memory trades = new bytes32[](2);
		
		trades[0] = _bytesToBytes32(tradeBorrowItem);
		trades[1] = _bytesToBytes32(tradeSettleItem);
		
		BalanceActionWithTrades memory tradeItem = BalanceActionWithTrades(
			DepositActionType.None,
			currencyId,
			0, //depositActionAmount
			0,//withdrawAmountInternalPrecision 提款数量
			false,//withdrawEntireCashBalance 是否将cash全部提出 ;
			false,//redeemToUnderlying 是否赎回底层资产
			// Array of tightly packed 32 byte objects that represent trades. See TradeActionType documentation
			trades
		);
		
		BalanceActionWithTrades[] memory tradeBatch = new BalanceActionWithTrades[](1);
		tradeBatch[0] = tradeItem;

		Notional(NotionalAddress).batchBalanceAndTradeAction(address(this),tradeBatch);

	}

	function _settleAccountIfRequired(address account) internal {
		uint256 blockTime = block.timestamp;

		AccountContext memory accContext = Notional(NotionalAddress).getAccountContext(account);
		bool mustSettle =  0 < accContext.nextSettleTime && accContext.nextSettleTime <= blockTime;

		if (mustSettle) { //开始结算
			Notional(NotionalAddress).settleAccount(account);
		}
	}

	function _getReferenceTime(uint256 blockTime) internal pure returns (uint256) {
        require(blockTime >= QUARTER);
        return blockTime - (blockTime % QUARTER);
    }

	//查看是否需要初始化
	function _initMaketIfRequired(uint16 currencyId) internal {
		uint256 blockTime = block.timestamp;
		uint256 threeMonthMaturity = _getReferenceTime(blockTime) + QUARTER;
		//(bool ok,bytes memory returndata) =   address(notional).staticcall(abi.encodeWithSignature("getMarket(uint16,uint256,uint256)",currencyId,maturity,settlementDate)) ;
      	(bool success,bytes memory returndata) = address(NotionalAddress).staticcall(abi.encodeWithSignature("getMarket(uint16,uint256,uint256)",currencyId,threeMonthMaturity,threeMonthMaturity));

        MarketParameters   memory market ;
        
        if(success){
            ( market) =  abi.decode(returndata,(MarketParameters))  ;
            if(market.oracleRate==0){
                Notional(NotionalAddress).initializeMarkets(currencyId,false);
            }
        }else{
            Notional(NotionalAddress).initializeMarkets(currencyId,false);
        }

	} 

	//转换
	function _bytesToBytes32(bytes memory source) internal pure returns (bytes32 result) {
        if (source.length == 0) {
            return 0x0;
        }
        assembly {
            result := mload(add(source, 32))
        }
     }

}
function getPayCashAndFcash(address account,uint16 currencyId,uint256 marketIndex) public view  returns(int256 ,int256) {
	//在这之前市场必须要进行初始化	
	int256 fcashAmount = 0;

	uint256 blockTime = block.timestamp;
	(int256 cashBalanceAsset,,) = Notional(NotionalAddress).getAccountBalance(currencyId,account);
	if(cashBalanceAsset >= 0) { //已经被结算过了
		return (cashBalanceAsset,fcashAmount);  //这里就不继续转换成底层资产了,因为已经不需要结算了
	}

	//注意cashBalanceAsset是ctoken,这里做一次转换
	int256 cashBalance = convertToUnderlying(currencyId,cashBalanceAsset);

	//注意fcashAmount的符号,这里cashBalance为负数 fcashAmount就为正数。如果传错了滑点就会滑向相反的方向，与实际情况不符
	//所以要传入负数 最终结果fcash为负数。这里表示用户要得到正的cash,账户会累计多少负的fcash
	//此处的含义是结算人为了帮助 欠款人 结算 需要付出cashBalance的资金，从而自己需要从市场上借多少fcash
	fcashAmount = Notional(NotionalAddress).getfCashAmountGivenCashAmount(currencyId,int88(-cashBalance),marketIndex,blockTime);

	//因为最终得到正的fcash会稍微有盈余，所以这里可以适当增加fcash的数量

	return (cashBalance,fcashAmount);
}

//marketIndex 为要兑换的市场，这里千万注意 因为下个市场会变化
//cashBalance为负数,fcashAmount为负数