// Contract: CryptoRanch
// Target function: function reproductionStage() private
    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);
    }

    // find the next price which has sheep
    function determineForwardSheepPrice(uint start) private {
        for (uint i =  start; i < 100 finney; i = i.add(1 finney)) {
            if (sysPriceSheepNum[i] > 0 || usrPriceSheepNum[i] > 0) {
                global.sheepPrice = i;
                return;
            }
        }
    }

    // renew the price interval
    function determinePriceInterval(uint sheepPrice) private {
        if (isFleshUp == false && sheepPrice.add(3 finney) > priceCumulativeCount && priceCumulativeCount < 99 finney) {
            uint newPrice = sheepPrice.add(3 finney);
            uint addSheepNum = global.sysInSaleNum.div(50);
            //renew the sysPriceSheepNum
            for (uint i = sheepPrice; i <= newPrice; i = i.add(1 finney)) {
                if (sysPriceSheepNum[i] == 0) sysPriceSheepNum[i] = addSheepNum;
            }
            priceCumulativeCount = newPrice;

            global.priceInterval[0] = newPrice.sub(6 finney);
            global.priceInterval[1] = newPrice.sub(5 finney);
            global.priceInterval[2] = newPrice.sub(4 finney);
            global.priceInterval[3] = newPrice.sub(3 finney);
            global.priceInterval[4] = newPrice.sub(2 finney);
            global.priceInterval[5] = newPrice.sub(1 finney);
            global.priceInterval[6] = newPrice;
        } else if (isFleshUp == false && sheepPrice.sub(3 finney) >= 50 finney && sheepPrice <= 96 finney)  {
            global.priceInterval[0] = sheepPrice.sub(3 finney);
            global.priceInterval[1] = sheepPrice.sub(2 finney);
            global.priceInterval[2] = sheepPrice.sub(1 finney);
            global.priceInterval[3] = sheepPrice;
            global.priceInterval[4] = sheepPrice.add(1 finney);
            global.priceInterval[5] = sheepPrice.add(2 finney);
            global.priceInterval[6] = sheepPrice.add(3 finney);
        } else if (sheepPrice == 99 finney) {
            //preprocess the sheep number on 50 price after the reproduction
            uint addSheepNum = global.sysInSaleNum.sub(sysPriceSheepNum[99 finney]).div(25);
            sysPriceSheepNum[50 finney] = sysPriceSheepNum[50 finney].mul(2).add(addSheepNum);
            usrPriceSheepNum[50 finney] = usrPriceSheepNum[50 finney].mul(2);

            global.priceInterval[0] = 94 finney;
            global.priceInterval[1] = 95 finney;
            global.priceInterval[2] = 96 finney;
            global.priceInterval[3] = 97 finney;
            global.priceInterval[4] = 98 finney;
            global.priceInterval[5] = 99 finney;
            global.priceInterval[6] = 100 finney;
        }

        emit OnPriceChange(global.sheepPrice, now);
    }

    function changePriceInterval(uint sheepPrice) private {
        // check whether on "Growing" stage
        // the accumulated sold sheep beyond the 2% of global sheep
        if (isFleshUp || global.sysSoldNum.mul(50) >= global.sheepNum) {
            // first time enter, the locked sheep reset to 0
            if (isFleshUp == false) fleshUpCount = 0;

            bool haveSheep = false;
            for (uint i = global.priceInterval[0]; i <= global.priceInterval[6]; i = i.add(1 finney)) {
                if (sysPriceSheepNum[i] > 0 || usrPriceSheepNum[i] > 0) {
                    if (haveSheep == false) {
                        haveSheep = true;
                        global.sheepPrice = i; // determine the next price
                        sheepPrice = i;
                    }
                    // Count the remaining sheep on price interval
                    if (isFleshUp == false) fleshUpCount = fleshUpCount.add(sysPriceSheepNum[i]).add(usrPriceSheepNum[i]);
                    else break;
                }
            }
            isFleshUp = true;      // "Growing" stage start
            global.sysSoldNum = 0; // clear the accumulated sold sheep

            if (fleshUpCount == 0) {
                isFleshUp = false;
                // determine the next price
                determineForwardSheepPrice(global.priceInterval[0]);
                sheepPrice = global.sheepPrice;
            } else if (haveSheep == false) { // end of the "Growing" stage
                isFleshUp = false;
                global.sysSoldNum = 0;
                if (sheepPrice == 99 finney) { //preprocess for the reproduction
                    global.sheepPrice = 100 finney;
                    sheepPrice = 100 finney;
                } else { // price jump
                    uint addPrice = (fleshUpCount.mul(100).div(global.sheepNum)).mul(1 finney);
                    if ( fleshUpCount.mul(100) % global.sheepNum != 0) {
                        addPrice = addPrice.add(1 finney);
                    }
                    sheepPrice =  sheepPrice.add(addPrice);

                    if (sheepPrice <= priceCumulativeCount) {  // jump to the old price
                        determineForwardSheepPrice(sheepPrice);// 99 price has sheep
                        sheepPrice = global.sheepPrice;
                    } else {                                   // jump to the new price,
                        if (sheepPrice > 99 finney) {
                            global.sheepPrice = 99 finney;     // will be assigned value later
                            sheepPrice = 99 finney;
                        } else {
                            global.sheepPrice = sheepPrice;
                        }
                    }
                }
            }
        } else { // general price rise
            if (sheepPrice == 99 finney) { //99 price sold out, preprocess for the reproduction
                global.sheepPrice = 100 finney;
                sheepPrice = 100 finney;
            } else {
                // determine the next price
                determineForwardSheepPrice(global.priceInterval[0]);
                sheepPrice = global.sheepPrice;
            }
        }

        // reproduce
        if (sheepPrice > 99 finney) {
            reproductionStage();
            return;
        }

        // determine the price interval
        determinePriceInterval(sheepPrice);
    }