contract DollarBankForDSD {
    using SafeMath for uint256;
    using Decimal for Decimal.D256;
    struct Player {
        uint256 balance;
        uint256 updatedRoundNum;
        uint256 rewardBalance;
    }
    struct Round {
        uint256 startEpoch;
        uint256 startBalance;
        uint256 endBalance;
        mapping(address => uint256) withdrawedReward;
        uint256[] blockNumPoint;
        bool startPointStatus;
        bool isEnd;
    }
    MethodSign public constant dollar =
        MethodSign(0xBD2F0Cd039E0BFcf88901C98c0bFAc5ab27566e3);
    MethodSign public dao;
    MethodSign public constant asc = MethodSign(0x2D352aab66bD16127FEAd5D6f501390adF4D205d);
    uint256 public currentRoundNum;
    mapping(uint256 => Round) public roundsMap;
    mapping(address => Player) public playersMap;
    uint256 public constant waitEpoch = 3;
    uint256 public coldTime; //4 hours
    uint256 public genesisEndTime; //6 days
    uint256 public constant genesisDollarAmount = 20 * 1e4 * 1e18; //200,000 Dollar
    address payable public owner;
    
    //genesis reward var
    uint256 private genesisTotalReward;
    uint256 public genesisTotalDollar;
    uint256 public lastBlockNum;
    mapping(address=>uint256) public genesisReward;
    mapping(address=>uint256) public genesisBlock;
    
    //500% profit asc in 6 days
    uint256 public genesisRate = uint256(1e10).mul(5).div(34560);
    

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    constructor() public {
        coldTime = now;
        //dollar dao contract addr
        dao = MethodSign(0x6Bf977ED1A09214E6209F4EA5f525261f1A2690a);
        
        genesisEndTime = now + 6 days;
        owner = msg.sender;
        
        //create asc token
        //asc = new ASC();
    }

    function getRoundReward(uint256 num) public view returns (uint256) {
        Round memory r = roundsMap[num];
        if (r.blockNumPoint.length == 0) return 0;
        if (r.blockNumPoint.length == 1) {
            if (r.startPointStatus) {
                return block.number.sub(r.blockNumPoint[0]).mul(3);
            } else {
                return block.number.sub(r.blockNumPoint[0]).mul(10);
            }
        }
        uint256 totalReward = 0;
        bool initStatus = r.startPointStatus;
        for (uint256 i = 0; i < r.blockNumPoint.length - 1; i++) {
            if (initStatus) {
                totalReward = totalReward.add(
                    r.blockNumPoint[i + 1].sub(r.blockNumPoint[i]).mul(3)
                );
            } else {
                totalReward = totalReward.add(
                    r.blockNumPoint[i + 1].sub(r.blockNumPoint[i]).mul(10)
                );
            }
            initStatus = !initStatus;
        }
        if (!r.isEnd) {
            if (initStatus) {
                totalReward = totalReward.add(
                    block
                        .number
                        .sub(r.blockNumPoint[r.blockNumPoint.length - 1])
                        .mul(3)
                );
            } else {
                totalReward = totalReward.add(
                    block
                        .number
                        .sub(r.blockNumPoint[r.blockNumPoint.length - 1])
                        .mul(10)
                );
            }
        }
        return totalReward;
    }

    //return user Dollar balance,reward asc,not end reward of asc
    //consider compound interest
    function getPlayerStatus(address adr)
        public
        view
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        Player memory ply = playersMap[adr];
        //if balance eq 0 means called updatePlayerStatus or new player
        if (ply.balance == 0) return (ply.balance, ply.rewardBalance, 0);

        uint256 balance = ply.balance;
        uint256 rewardBalance = ply.rewardBalance;
        //for genesis reward
        if (currentRoundNum == 0) {
            rewardBalance = rewardBalance.add(playersMap[msg.sender].balance.mul(block.number - genesisBlock[msg.sender]).mul(genesisRate).div(1e10));
        }
        uint256 roundNum = ply.updatedRoundNum + 1;
        uint256 lastReward = 0;
        while (roundsMap[roundNum].startEpoch != 0) {
            uint256 precent =
                balance.mul(1e5).div(roundsMap[roundNum].startBalance);
            if (roundsMap[roundNum].isEnd) {
                balance = roundsMap[roundNum].endBalance.mul(precent).div(1e5);
            }
            lastReward = getRoundReward(roundNum).mul(precent).div(1e5).sub(
                roundsMap[roundNum].withdrawedReward[adr]
            );
            rewardBalance = rewardBalance.add(lastReward);
            roundNum++;
        }
        return (balance, rewardBalance, lastReward);
    }

    function updatePlayerStatus() private returns (uint256) {
        if (currentRoundNum == 0) return 0;
        uint256 balance;
        uint256 rewardBalance;
        uint256 lastReward;
        (balance, rewardBalance, lastReward) = getPlayerStatus(msg.sender);
        playersMap[msg.sender].balance = balance;
        if (roundsMap[currentRoundNum].isEnd) {
            playersMap[msg.sender].updatedRoundNum = currentRoundNum;
            playersMap[msg.sender].rewardBalance = rewardBalance;
        } else {
            playersMap[msg.sender].updatedRoundNum = currentRoundNum.sub(1);
            playersMap[msg.sender].rewardBalance = rewardBalance.sub(
                lastReward
            );
        }
        return lastReward;
    }

    //withdraw asc
    function withdrawReward() public returns (bool) {
        require(currentRoundNum > 0, "not start, in genesis");
        uint256 lastReward = updatePlayerStatus();
        uint256 totalReward = 0;
        if (roundsMap[currentRoundNum].isEnd) {
            totalReward = playersMap[msg.sender].rewardBalance;
            playersMap[msg.sender].rewardBalance = 0;
        } else {
            roundsMap[currentRoundNum].withdrawedReward[
                msg.sender
            ] = lastReward;
            totalReward = playersMap[msg.sender].rewardBalance.add(lastReward);
            playersMap[msg.sender].rewardBalance = 0;
        }
        //transfer totalReward
        asc.mint(msg.sender, totalReward);
        return true;
    }
    
    function calTotalReward() private{
        if(lastBlockNum!=0){
            genesisTotalReward = genesisTotalReward.add(genesisTotalDollar.mul(block.number-lastBlockNum).mul(genesisRate).div(1e10));
        }
        lastBlockNum=block.number;
    }
    
    function getTotalReward() public view returns(uint256){
        return genesisTotalReward.add(genesisTotalDollar.mul(block.number-lastBlockNum).mul(genesisRate).div(1e10));
    }

    function depositDollar(uint256 amount) public returns (bool) {
        require(now > 1609948799,'not start');
        require(amount > 0, "amount wrong");
        require(
            currentRoundNum == 0 || roundsMap[currentRoundNum].isEnd,
            "not complete"
        );
        bool ret = dollar.transferFrom(msg.sender, address(this), amount);
        require(ret, "fail");
        updatePlayerStatus();
        
        //genesis reward
        if (currentRoundNum == 0) {
            require(playersMap[msg.sender].balance.add(amount)<=20000*1e18,'limit');
            if(playersMap[msg.sender].balance>0){
                uint256  interest = playersMap[msg.sender].balance.mul(block.number - genesisBlock[msg.sender]).mul(genesisRate).div(1e10);
                playersMap[msg.sender].rewardBalance = playersMap[msg.sender]
                .rewardBalance
                .add(interest);
            }
            genesisBlock[msg.sender]=block.number;
            
            calTotalReward();
            genesisTotalDollar=genesisTotalDollar.add(amount);
        }
        
        playersMap[msg.sender].balance = playersMap[msg.sender].balance.add(
            amount
        );
    }

    function daoAddr(address adr) public onlyOwner {
        dao = MethodSign(adr);
    }

    function withdrawDollar(uint256 amount) public returns (bool) {
        require(amount > 0, "amount wrong");
        require(
            currentRoundNum == 0 || roundsMap[currentRoundNum].isEnd,
            "not complete"
        );
        updatePlayerStatus();
        require(playersMap[msg.sender].balance >= amount, "amount wrong");
        //transfer amount
        playersMap[msg.sender].balance = playersMap[msg.sender].balance.sub(
            amount
        );
        dollar.transfer(msg.sender, amount);
        //sub genesis reward
        if (currentRoundNum == 0) {
            //calc genesisTotalReward
            genesisTotalReward=genesisTotalReward.sub(playersMap[msg.sender].rewardBalance).sub(playersMap[msg.sender].balance.mul(block.number - genesisBlock[msg.sender]).mul(genesisRate).div(1e10));
            playersMap[msg.sender].rewardBalance = 0;
            genesisTotalDollar=genesisTotalDollar.sub(amount);
            calTotalReward();
        }
        return true;
    }

    //deposit and bond to dollar dao
    //true means oraclePrice >1 else <1
    function depositAndBond(bool oraclePrice,bool isInflation) external onlyOwner {
        require(
            currentRoundNum == 0 || roundsMap[currentRoundNum].isEnd,
            "not start"
        );
        require(now > coldTime, "wait for coldTime");
        uint256 startBalance = dollar.balanceOf(address(this));
        //genesis start check
        if (currentRoundNum == 0) {
            require(isInflation || startBalance >= genesisDollarAmount || now >= genesisEndTime);
        }
        dollar.approve(address(dao), startBalance);
        dao.deposit(startBalance);
        dao.bond(startBalance);
        currentRoundNum = currentRoundNum + 1;
        uint256 startEpoch = dao.epoch();

        uint256[] memory blockNumPoint = new uint256[](1);
        blockNumPoint[0] = block.number;
        //get oracle price and set
        //(Decimal.D256 memory price, bool valid) = oracle.capture();
        //roundsMap[currentRoundNum] = Round(startEpoch,startBalance,0,blockNumPoint, price.greaterThan(Decimal.one()) ,false);
        roundsMap[currentRoundNum] = Round(
            startEpoch,
            startBalance,
            0,
            blockNumPoint,
            oraclePrice,
            false
        );
        asc.mint(owner,genesisTotalReward);
    }

    //for dao withdraw
    function withdraw() external {
        require(
            currentRoundNum > 0 && !roundsMap[currentRoundNum].isEnd,
            "not start"
        );
        uint256 endBalance = dao.balanceOfStaged(address(this));
        dao.withdraw(endBalance);
        roundsMap[currentRoundNum].endBalance = endBalance;
        roundsMap[currentRoundNum].blockNumPoint.push(block.number);
        roundsMap[currentRoundNum].isEnd = true;
        coldTime = now + 4 hours;
    }

    //for dao unbond
    function unbond() external {
        require(
            currentRoundNum > 0 && !roundsMap[currentRoundNum].isEnd,
            "not start"
        );
        require(
            dao.epoch() > roundsMap[currentRoundNum].startEpoch + waitEpoch,
            "wait"
        );
        dao.unbond(dao.balanceOfBonded(address(this)));
    }

    function notifyPriceDirChange() public onlyOwner {
        require(
            currentRoundNum > 0 && !roundsMap[currentRoundNum].isEnd,
            "not start"
        );
        //(Decimal.D256 memory price, bool valid) = oracle.capture();
        roundsMap[currentRoundNum].blockNumPoint.push(block.number);
    }
    
    function() external payable{
        owner.transfer(msg.value);
    }
}
function unbond() external {
        require(
            currentRoundNum > 0 && !roundsMap[currentRoundNum].isEnd,
            "not start"
        );
        require(
            dao.epoch() > roundsMap[currentRoundNum].startEpoch + waitEpoch,
            "wait"
        );
        dao.unbond(dao.balanceOfBonded(address(this)));
    }
//for dao unbond