// Contract: CryptoRanch
// Target function: function determineForwardSheepPrice(uint start) private
00];

    address private cryptoRanchAddress;

    struct Generation {
        uint[] ancestorList; //up-line
        uint[] inviteesList; //down-line
    }

    mapping (uint => Generation) generations;

    event OnSetCryptoRanchAddress(address cryptoRanchAddress, uint timestamp);

    constructor (address cryptoRanchAddr) public {
        cryptoRanchAddress = cryptoRanchAddr;
    }

    modifier onlyForCryptoRanch() {
        require(cryptoRanchAddress == msg.sender, "Only for cryptoRanch contract!");
        _;
    }

    // link to the reference contract
    function setCryptoRanchAddress(address cryptoRanchAddr) public onlyOwner{
        cryptoRanchAddress = cryptoRanchAddr;
        emit OnSetCryptoRanchAddress(cryptoRanchAddress, now);
    }

    function joinGame(uint pID, uint inviterID) public onlyForCryptoRanch{

        uint len = generations[inviterID].ancestorList.length;
        if (len == 0) {
            generations[pID].ancestorList.push(inviterID);
        } else if (len < 10) {
            generations[pID].ancestorList.push(inviterID);
            for(uint i = 0; i < len; i++) {
                generations[pID].ancestorList.push(generations[inviterID].ancestorList[i]);
            }
        } else if (len >= 10) {
            generations[pID].ancestorList.push(inviterID);
            for(uint i = 0; i < 9; i++) {
                generations[pID].ancestorList.push(generations[inviterID].ancestorList[i]);
            }
        }
    }

    function inviteNewUser(uint inviterID, uint inviterRanchSize, bool inviterIsAlive, uint invitee, uint inviteeRanchGrade)
    public onlyForCryptoRanch returns(uint){
        generations[inviterID].inviteesList.push(invitee);
        if (inviterIsAlive) {
            uint newRanchSize = inviterRanchSize.add(ranchGrow[inviteeRanchGrade]);
            return newRanchSize;
        } else {
            return inviterRanchSize;
        }
    }

    // your upLine
    function getMotherGeneration(uint pID) external view onlyForCryptoRanch returns(uint) {
        require(generations[pID].ancestorList.length != 0, "You are the first generation!");
        return generations[pID].ancestorList[0];
    }

    function getAncestorList(uint pID) external view onlyForCryptoRanch returns(uint[] memory){
        require(generations[pID].ancestorList.length != 0, "You are the first generation!");

        uint[] memory ancestorList = new uint[](generations[pID].ancestorList.length);
        for(uint i = 0; i < generations[pID].ancestorList.length; i++){
            ancestorList[i] = generations[pID].ancestorList[i];
        }
        return ancestorList;
    }

    function getInviteesList(uint pID) external view onlyForCryptoRanch returns(uint[] memory){
        require(generations[pID].inviteesList.length != 0, "You don't have downline!");

        uint[] memory inviteesList = new uint[](generations[pID].inviteesList.length);
        for(uint i = 0; i < generations[pID].inviteesList.length; i++){
            inviteesList[i] = generations[pID].inviteesList[i];
        }
        return inviteesList;
    }

    function getInviteesCount(uint pID) external view returns(uint){
        return generations[pID].inviteesList.length;
    }

    function getAncestorCount(uint pID) external view returns(uint){
        return generations[pID].ancestorList.length;
    }
}


contract TransactionSystem is Ownable{
    using SafeMath for uint;

    uint constant SHEEP_INIT_NUMBER = 10000;
    uint constant SHEEP_INIT_PRICE = 50 finney;

    event OnPriceChange(uint indexed price, uint timestamp);
    event OnFleshUp(uint indexed price, uint fleshUpCount, uint timestamp);
    event OnOrderAdd(address indexed from, uint number, uint timestamp);
    event OnOrderCancel(address indexed from, uint number, uint timestamp);
    event OnSetCryptoRanchAddress(address cryptoRanchAddress,  uint timestamp);


    struct GlobalData {
        uint sheepNum;
        uint sheepPrice;
        uint sysInSaleNum;
        uint sysSoldNum;
        uint[7] priceInterval;
    }
    struct OrderQueue {
        uint[] idList;
        uint front;
    }

    struct Order {
        uint ownerID;
        uint sheepNum;
        uint sheepPrice;
        uint round;
    }

    GlobalData global;
    Order[] sellOrders;
    uint[] private reproductionBlkNums;

    CryptoRanch cryptoRanch = CryptoRanch(0x0);
    address payable cryptoRanchAddress;

    bool isFleshUp = false;
    uint fleshUpCount = 0;
    uint priceCumulativeCount;


    mapping (uint => uint) orderIDByPID;               // pID => orderID
    mapping (uint => OrderQueue) priceOrderQueue;      // price => orders queue on the price level
    mapping (uint => uint) sysPriceSheepNum;           // price => total sheep number of sys on the price level
    mapping (uint => uint) usrPriceSheepNum;           // price => total sheep number of usr orders on the price level

    constructor () public{
        global = GlobalData({
            sheepNum: SHEEP_INIT_NUMBER,
            sheepPrice: SHEEP_INIT_PRICE,
            sysInSaleNum: SHEEP_INIT_NUMBER,
            sysSoldNum: 0,
            priceInterval: [uint256(50 finney), uint256(51 finney), uint256(52 finney), uint256(53 finney), uint256(54 finney), uint256(55 finney), uint256(56 finney)]
            });

        uint initSheepNum = global.sheepNum.div(50);

        for (uint i = 50 finney; i <= 56 finney; i = i.add(1 finney)) {
            sysPriceSheepNum[i] = initSheepNum;
        }

        reproductionBlkNums.push(0);

        priceCumulativeCount = 56 finney;
    }

    modifier onlyForCryptoRanch() {
        require(cryptoRanchAddress == msg.sender, "Only for cryptoRanch contract!");
        _;
    }

    function() payable external{
        cryptoRanchAddress.transfer(msg.value);
    }

    // link to the reference contract
    function setCryptoRanchAddress(address payable cryptoRanchAddr) external onlyOwner {
        cryptoRanchAddress = cryptoRanchAddr;
        cryptoRanch = CryptoRanch(cryptoRanchAddress);

        emit OnSetCryptoRanchAddress(cryptoRanchAddress, now);
    }

    function getCryptoRanchAddress() public view returns (address) {
        return cryptoRanchAddress;
    }


    // purchase sheep, only call from CryptoRanch contract
    function buySheepFromOrders(uint buyerID, uint balance, uint sheepNum, bool isRebuy) public onlyForCryptoRanch {

        uint addSheepNum = 0; // accumulated sold sheep in this tx
        uint sheepPrice = global.sheepPrice;
        uint totalCost = 0;
        uint cost = 0;
        uint tmpRound = cryptoRanch.getReproductionRound();
        OrderQueue storage Q = priceOrderQueue[sheepPrice];

        while (sheepNum > 0) {
            /* process the system order */
            if (sysPriceSheepNum[sheepPrice] > 0) {
                // enough sheep to meet the demand on current price
                if (sysPriceSheepNum[sheepPrice] >= sheepNum) {
                    // total sheep to be sold
                    addSheepNum = addSheepNum.add(sheepNum);
                    // deduct the cost
                    cost = sheepNum.mul(sheepPrice);  // cost in this turn
                    totalCost = totalCost.add(cost);  // forage fee, cumulated
                    balance = balance.sub(cost);

                    // renew the global data
                    global.sysSoldNum = global.sysSoldNum.add(sheepNum);
                    global.sysInSaleNum = global.sysInSaleNum.sub(sheepNum);
                    // renew the sheep data on cur price
                    sysPriceSheepNum[sheepPrice] = sysPriceSheepNum[sheepPrice].sub(sheepNum);
                    sheepNum = 0;

                    /* end the tx */
                    // renew the revenue of seller
                    cryptoRanch.processSellerProfit(0, cost);
                    // renew the sheep number of buyer and distribute the commission
                    cryptoRanch.addSheepNumber(buyerID, addSheepNum, totalCost, true);

                    // refund the overage of balance
                    if (balance > 0 && isRebuy == false) {
                        uint tmpBalance = balance;
                        balance = 0;
                        cryptoRanch.buyOrderRefund(buyerID, tmpBalance);
                    }

                    // price change
                    if (sysPriceSheepNum[sheepPrice] == 0 && usrPriceSheepNum[sheepPrice] == 0) {
                        changePriceInterval(sheepPrice);
                    }
                    break;

                } else {
                    addSheepNum = addSheepNum.add(sysPriceSheepNum[sheepPrice]);
                    cost = sysPriceSheepNum[sheepPrice].mul(sheepPrice);
                    totalCost = totalCost.add(cost);
                    balance = balance.sub(cost);

                    global.sysSoldNum = global.sysSoldNum.add(sysPriceSheepNum[sheepPrice]);
                    global.sysInSaleNum = global.sysInSaleNum.sub(sysPriceSheepNum[sheepPrice]);
                    sheepNum = sheepNum.sub(sysPriceSheepNum[sheepPrice]);

                    sysPriceSheepNum[sheepPrice] = 0;

                    cryptoRanch.processSellerProfit(0, cost);
                }
            }

            /* process the user orders */
            // if no user order, price change
            if (usrPriceSheepNum[sheepPrice] == 0) {
                changePriceInterval(sheepPrice);
                sheepPrice = global.sheepPrice;
                Q = priceOrderQueue[sheepPrice]; //point to the orders queue of new price
                // normalize the number of sheep bought, if the reproduction triggered
                if (tmpRound < cryptoRanch.getReproductionRound()) {
                    addSheepNum = addSheepNum.mul(2);
                    tmpRound = cryptoRanch.getReproductionRound();
                }
                continue; // start with system order on new price
            }

            // check the order on list
            while(sellOrders[Q.idList[Q.front]].sheepNum <= 0) {
                Q.front ++ ;
            }

            // get the order
            uint orderID = Q.idList[Q.front];
            Order storage order = sellOrders[orderID];

            // get the normalized number of sheep on order
            uint realNum = order.sheepNum.mul(2**(cryptoRanch.getReproductionRound().sub(order.round)));

            // the sheep of the order can be sold out
            if (realNum <= sheepNum) {
                addSheepNum = addSheepNum.add(realNum);
                cost = realNum.mul(sheepPrice);
                totalCost = totalCost.add(cost);
                balance = balance.sub(cost);

                // renew the global data
                global.sysSoldNum = global.sysSoldNum.add(realNum);
                usrPriceSheepNum[sheepPrice] = usrPriceSheepNum[sheepPrice].sub(realNum);

                sheepNum = sheepNum.sub(realNum);

                // renew the revenue of order owner
                cryptoRanch.processSellerProfit(order.ownerID, cost);

                // delete order
                orderIDByPID[order.ownerID] = 0;
                delete sellOrders[orderID];
                delete Q.idList[Q.front];
                // next order
                Q.front++;

                // if the sheep on cur price sold out and meet the purchased demand
                if(usrPriceSheepNum[sheepPrice] == 0 && sheepNum == 0) {
                    /* end the tx */
                    // renew the sheep number of buyer and distribute the commission
                    cryptoRanch.addSheepNumber(buyerID, addSheepNum, totalCost, true);
                    // price change
                    changePriceInterval(sheepPrice);

                    // refund the overage of balance
                    if (balance > 0 && isRebuy == false) {
                        uint tmpBalance = balance;
                        balance = 0;
                        cryptoRanch.buyOrderRefund(buyerID, tmpBalance);
                    }
                    break;
                }
                // the sheep of the order over the purchased demand
            } else {
                addSheepNum = addSheepNum.add(sheepNum);
                cost = sheepNum.mul(sheepPrice);
                totalCost = totalCost.add(cost);
                balance = balance.sub(cost);

                // renew the global data
                global.sysSoldNum = global.sysSoldNum.add(sheepNum);
                usrPriceSheepNum[sheepPrice] = usrPriceSheepNum[sheepPrice].sub(sheepNum);

                // renew and normalize the order data
                sellOrders[orderID].sheepNum = realNum.sub(sheepNum);
                sellOrders[orderID].round = cryptoRanch.getReproductionRound();

                // clear the sheep number of demand
                sheepNum = 0;
                // renew the revenue of order owner
                cryptoRanch.processSellerProfit(order.ownerID, cost);

                /* end the tx */
                // renew the sheep number of buyer and distribute the commission
                cryptoRanch.addSheepNumber(buyerID, addSheepNum, totalCost, true);
                // refund the overage of balance
                if (balance > 0 && isRebuy == false) {
                    uint tmpBalance = balance;
                    balance = 0;
                    cryptoRanch.buyOrderRefund(buyerID, tmpBalance);
                }
                break;
            }
        }
    }

    // Add new order to the contract
    function addNewSellOrder(uint sellerID,  uint sheepNum, uint sheepPrice) public onlyForCryptoRanch {
        // can't add order on "Growing" stage
        require(isFleshUp == false, "isFleshUp");
        // check price on the legal price interval
        require(sheepPrice != global.sheepPrice && sheepPrice > global.sheepPrice && sheepPrice <= global.priceInterval[6], "out of range");

        // only price = 99 then allow price = 100 order
        if (global.sheepPrice < 99 finney && sheepPrice > 99 finney) {
            revert("out of range");
        }
        // if global price >= 99 only 0.1eth orders is allowed
        if (global.sheepPrice >= 99 finney && sheepPrice != 100 finney) {
            revert("0.099 only allowed 0.1 eth");
        }

        // legal total number of sheep on current price ( 2% of global sheep )
        if (sysPriceSheepNum[sheepPrice].add(usrPriceSheepNum[sheepPrice]).add(sheepNum).mul(50) > global.sheepNum) {
            revert("no more than 2% global sheepNumber");
        }
        // price=99, legal number limitation
        if (global.sheepPrice == 99 finney && sysPriceSheepNum[50 finney].add(usrPriceSheepNum[50 finney]).add(sheepNum).mul(25) > global.sheepNum){
            revert("no more than 2% global sheepNumber");
        }

        // check whether has order in sale
        require(sellOrders.length == 0 || sellOrders[orderIDByPID[sellerID]].ownerID != sellerID, "already exist");

        // reduce the sheep of ranch
        cryptoRanch.minusSheepNum(sellerID, sheepNum); //notice

        // add order
        uint orderID = sellOrders.length;
        if (sheepPrice == 100 finney) {
            sheepNum = sheepNum.mul(2);
            sheepPrice = 50 finney;
            sellOrders.push(Order({
                ownerID: sellerID,
                sheepNum: sheepNum,
                sheepPrice: sheepPrice,
                round: cryptoRanch.getReproductionRound().add(1)
                }));
        } else {
            sellOrders.push(Order({
                ownerID: sellerID,
                sheepNum: sheepNum,
                sheepPrice: sheepPrice,
                round: cryptoRanch.getReproductionRound()
                }));
        }
        orderIDByPID[sellerID] = orderID;
        priceOrderQueue[sheepPrice].idList.push(orderID);
        // renew the global sheep data
        usrPriceSheepNum[sheepPrice] = usrPriceSheepNum[sheepPrice].add(sheepNum);

        emit OnOrderAdd(cryptoRanch.getAddrByPID(sellerID), sheepNum, now);
    }

    // Cancel sell order
    function cancelSellOrder(uint sellerID) public onlyForCryptoRanch {
        require(isFleshUp == false, "isFleshUp");
        // id check
        uint orderID = orderIDByPID[sellerID];
        require(orderID > 0 && sellOrders.length >= orderID, "Id error!");
        // owner check
        Order storage order = sellOrders[orderID];
        require(order.ownerID == sellerID, "no exist");

        if (global.sheepPrice == 99 finney && order.sheepPrice == 50 finney) {
            revert("0.099 not allowed cancel 0.1 eth order");
        }

        uint tmpPrice = order.sheepPrice;
        // normalize sheep number
        uint tmpSheepNum = order.sheepNum.mul(2**(cryptoRanch.getReproductionRound().sub(order.round)));
        delete sellOrders[orderID];

        cryptoRanch.addSheepNumber(sellerID, tmpSheepNum, 0, false);
        usrPriceSheepNum[tmpPrice] = usrPriceSheepNum[tmpPrice].sub(tmpSheepNum);

        orderIDByPID[sellerID] = 0;

        emit OnOrderCancel(cryptoRanch.getAddrByPID(sellerID), tmpSheepNum, now);
    }

    // burn the overloaded sheep
    function burnSheepGlobal(uint sheepNum) external onlyForCryptoRanch{
        global.sheepNum = global.sheepNum.sub(sheepNum);
    }