contract EnchantedShop is Shop {
    using SafeMath for *;

    mapping(address => uint) public balanceOwed; // balances owed to individual addresses
    mapping(address => uint) public latestBalanceCheck; // latest balance check of individual addresses
    mapping(address => uint) public itemsOwned;
    //mapping(address => uint) public totalWithdrawn; // used in calculating total earnings
    mapping(address => uint) public excessEth; // excess eth sent by individual addresses
    /*
    Using itemsOwned in place of object.balanceOf(msg.sender) prevents users who did not purchase tokens from the contract but who were instead transferred tokens from receiving earnings on them (which would require extra contract and token functionality to account for when those items were acquired). Using itemsOwned also means that users can transfer their tokens out but will still earn returns on them if they were purchased from the shop. We can also perform a check against the user's balanceOf to prevent this if desired.
    */
    uint public itemReturn;
    uint public maxDebt; // maximum possible debt owed by the shop if no funds were claimed
    uint public runningDebt; // total of individually amortized debts owed by this shop
    uint public additionalDebt; // general debt not yet accounted for due to amortization
    uint public debtPaid; // total debt paid by this shop
    uint public constant devFee = 125; // 125 represents 12.5%
    uint public originalPrice;

    uint public totalExcessEth; // total of individually amortized excess eth transfers, analogous to runningDebt

    bool public lock;
    uint public unlockDate;

    event ShopDeployed(address wallet, uint rate, uint itemReturn, uint32 endTime);
    //event EnchantedObjectMinted(uint totalSupply);
    event PriceUpdate(uint price);

    event FundsMoved(uint amount);
    event SafeLocked(uint date);
    event StartedSafeUnlock(uint date);

    event WillWithdraw(uint amount);

    modifier onlyContributors {
        require(itemsOwned[msg.sender] > 0);
        _;
    }

    modifier onlyValidPurchase() { // override onlyValidPurchase so that buyObject requires >= enough for 1 token instead of whole numbers only
        require(msg.value >= shopSettings.price); // at least enough for 1
        require((now >= shopSettings.startTime && now <= shopSettings.endTime) && msg.value != 0);
        _;
    }

    function EnchantedShop(address _bank, string _name, string _symbol, uint _rate, uint32 _endTime, uint _itemReturn)
    Shop(_bank, _name, _symbol, _rate, _endTime) public
    {
        require(_itemReturn == shopSettings.price.div(100)); // safety check; ensure we're using 1% returns and that we're using the correct price
        itemReturn = _itemReturn; // return should be in given wei
        originalPrice = shopSettings.price;
        ShopDeployed(_bank, _rate, _itemReturn, _endTime);
        unlockDate = 0;
        lock = true;
        SafeLocked(now);
    }

    function calculateTokens() internal returns(uint) {
        // rate is literally tokens per eth in wei;
        // passing in a rate of 10 ETH (10*10^18) equates to 10 tokens per ETH, or a price of 0.1 ETH per token
        calculatePrice(); // update price based on current rate
        return (1 ether);
    }

    function forwardFunds() internal {
        uint fee = shopSettings.price.mul(devFee).div(1000); // used to be msg.value.mul(devFee).div(1000); but we have refactored to only ever issue 1 token and the msg.value may exceed the price of one token
        uint supply = object.totalSupply();

        if (msg.value > shopSettings.price) { // if sender sent extra eth, account for it so we can send it back later
            excessEth[msg.sender] = excessEth[msg.sender].add(msg.value.sub(shopSettings.price));
            totalExcessEth = totalExcessEth.add(msg.value.sub(shopSettings.price));
        }
        
        shopSettings.bank.transfer(fee);
        itemsOwned[msg.sender] = itemsOwned[msg.sender].add(1 ether);
                
        // update caller's balance and our debt
        uint earnings = (itemsOwned[msg.sender].div(1 ether).sub(1)).mul(supply.sub(latestBalanceCheck[msg.sender])).div(1 ether).mul(itemReturn);
        if (latestBalanceCheck[msg.sender] != 0) { // if this isn't the first time we've checked buyer's balance owed...
            balanceOwed[msg.sender] = balanceOwed[msg.sender].add(earnings);
            runningDebt = runningDebt.add(earnings);
        }
        latestBalanceCheck[msg.sender] = supply;
        maxDebt = maxDebt.add((supply.sub(1 ether)).div(1 ether).mul(itemReturn)); // update maxDebt given the new item total

        additionalDebt = maxDebt.sub(runningDebt).sub(debtPaid); // update total debt not yet accounted for due to amoritzation
        
        if (additionalDebt < 0) { // this check may be unnecessary but may have been needed for the prototype
            additionalDebt = 0;
        }
        
        // update price of item (using rate for scalability) so that we can always cover fee + returns
        if (supply.div(1 ether).mul(itemReturn).add(runningDebt).add(additionalDebt) > (this.balance.sub(totalExcessEth))) {
            shopSettings.rate = (1 ether).mul(1 ether).div(supply.div(1 ether).mul(itemReturn).mul(1000).div((uint(1000).sub(devFee))));
            calculatePrice(); // update price
            PriceUpdate(shopSettings.price);
        }

        //EnchantedObjectMinted(supply); // FIX THIS
    }

    /*
    changes needed for refactoring

    // "enchanted items have a recommended bid which increases your likelihood of obtaining the item. However, you will still pay the best possible price—any ETH sent in excess of the lowest available price of the item is automatically added to your account balance and can be withdrawn from the contract at any time."

    // add recommendedBid which is real price rounded up to the next .01 - use round/truncate: https://ethereum.stackexchange.com/questions/5836/what-is-the-cheapest-way-to-roundup-or-ceil-to-multiple-of-1000
    // add price paid - real price to balance owed
    // mint exactly one token (calculateTokens)

    // we don't seem to actually use whenClosed, whenOpen

    */

    function claimFunds() onlyContributors public {
        // must use onlyContributors (itemsOwned > 0) as a check here!
        uint latest = latestBalanceCheck[msg.sender];
        uint supply = object.totalSupply();
        uint balance = balanceOwed[msg.sender];
        uint earnings = itemsOwned[msg.sender].div(1 ether).mul(supply.sub(latest)).div(1 ether).mul(itemReturn);
        
        uint excess = excessEth[msg.sender];

        // update latestBalanceCheck, reset balance owed to caller, and reset excess eth owed to caller
        // do all of these before calling transfer function or incrementing balance mappings so as to circumvent reentrancy attacks
        latestBalanceCheck[msg.sender] = supply;
        balanceOwed[msg.sender] = 0;
        excessEth[msg.sender] = 0;

        balance = balance.add(earnings); // account for user's earnings since lastBalanceCheck, but don't add it to balanceOwed to prevent reentrancy attacks
        // next, update our debt:
        runningDebt = runningDebt.add(earnings);
        runningDebt = runningDebt.sub(balance); // might be going negative due to not adding the excess eth send to runningDebt
        debtPaid = debtPaid.add(balance);

        // account for excess Eth
        balance = balance.add(excess);
        totalExcessEth = totalExcessEth.sub(excess);

        WillWithdraw(balance);

        // finally, send balance owed to msg.sender
        require(balance > 0);
        msg.sender.transfer(balance);
        //totalWithdrawn[msg.sender] = totalWithdrawn[msg.sender].add(balance.sub(excess));

        // might want to return bool
    }

    function startUnlock()
    onlyOwner public
    {
        require(lock && now.sub(unlockDate) > 2 weeks);
        unlockDate = now + 2 weeks;
        lock = false;
        StartedSafeUnlock(now);
    }

    function emergencyWithdraw(uint amount, bool relock)
    onlyOwner public
    {
        require(!lock && now > unlockDate);
        shopSettings.bank.transfer(amount);
        if (relock) {
            lock = relock;
            SafeLocked(now);
        }
    }

}
function claimFunds() onlyContributors public {
    // must use onlyContributors (itemsOwned > 0) as a check here!
    uint latest = latestBalanceCheck[msg.sender];
    uint supply = object.totalSupply();
    uint balance = balanceOwed[msg.sender];
    uint earnings = itemsOwned[msg.sender].div(1 ether).mul(supply.sub(latest)).div(1 ether).mul(itemReturn);
        
    uint excess = excessEth[msg.sender];

    // update latestBalanceCheck, reset balance owed to caller, and reset excess eth owed to caller
    // do all of these before calling transfer function or incrementing balance mappings so as to circumvent reentrancy attacks
    latestBalanceCheck[msg.sender] = supply;
    balanceOwed[msg.sender] = 0;
    excessEth[msg.sender] = 0;

    balance = balance.add(earnings); // account for user's earnings since lastBalanceCheck, but don't add it to balanceOwed to prevent reentrancy attacks
    // next, update our debt:
    runningDebt = runningDebt.add(earnings);
    runningDebt = runningDebt.sub(balance); // might be going negative due to not adding the excess eth send to runningDebt
    debtPaid = debtPaid.add(balance);

    // account for excess Eth
    balance = balance.add(excess);
    totalExcessEth = totalExcessEth.sub(excess);

    WillWithdraw(balance);

    // finally, send balance owed to msg.sender
    require(balance > 0);
    msg.sender.transfer(balance);
    //totalWithdrawn[msg.sender] = totalWithdrawn[msg.sender].add(balance.sub(excess));

    // might want to return bool
}

/*
 changes needed for refactoring
 
 // "enchanted items have a recommended bid which increases your likelihood of obtaining the item. However, you will still pay the best possible price—any ETH sent in excess of the lowest available price of the item is automatically added to your account balance and can be withdrawn from the contract at any time."
 
 // add recommendedBid which is real price rounded up to the next .01 - use round/truncate: https://ethereum.stackexchange.com/questions/5836/what-is-the-cheapest-way-to-roundup-or-ceil-to-multiple-of-1000
 // add price paid - real price to balance owed
 // mint exactly one token (calculateTokens)
 
 // we don't seem to actually use whenClosed, whenOpen
 
 */