contract InterestRateBased is IntervalBased {
    // Interest Rate Record
    struct InterestRate {
        uint256 interval;
        uint256 rate; // >= 10 ** 27
    }

    // Interval to interest rate
    InterestRate[] public interestRates;

    // uint256(interval) => uint256(interest index)
    mapping(uint256 => uint256) public intervalToInterestIndex;

    // Balance Records
    struct BalanceRecord {
        uint256 interval;
        uint256 intervalOffset;
        uint256 balance;
    }

    // address(token holder) => uint256(interval) => BalanceChange
    mapping(address => BalanceRecord[]) public balanceRecords;

    // address(tokenOwner) => uint256(balance index)
    mapping(address => uint256) public lastClaimedBalanceIndex;

    // include balance of method, is ERC20 compliant for tokens
    function balanceOf(address tokenOwner) public view returns (uint);

    // VIEW: get current interest rate
    function latestInterestRate() external view returns (uint256 rateAsRay, uint256 asOfInterval) {
        uint256 latestRateIndex = interestRates.length > 0 ? sub(interestRates.length, 1) : 0;

        return (interestRates[latestRateIndex].rate, interestRates[latestRateIndex].interval);
    }

    // getters
    function numInterestRates() public view returns (uint256) {
      return interestRates.length;
    }

    // getters
    function numBalanceRecords(address tokenOwner) public view returns (uint256) {
      return balanceRecords[tokenOwner].length;
    }

    // interest owed
    function interestOwed(address tokenOwner)
        public
        view
        returns (uint256 amountOwed, uint256 balanceIndex, uint256 interval) {

        // check for no balance records..
        if (balanceRecords[tokenOwner].length == 0) {
          return (0, 0, 0);
        }

        // balance index
        amountOwed = 0;
        balanceIndex = lastClaimedBalanceIndex[tokenOwner];
        interval = balanceRecords[tokenOwner][balanceIndex].intervalOffset;

        // current principle and interest rate
        uint256 principle = 0; // current principle value
        uint256 interestRate = 0; // current interest rate

        // interval markers and interval offset
        uint256 nextBalanceInterval = interval; // set to starting interval for setup
        uint256 nextInterestInterval = interval; // set to starting interval for setup

        // enforce interval maximum, last claim offset difference with max
        assert(sub(intervalNumber(block.timestamp), intervalOffset) < intervalMaximum);

        // this for loop should only hit either interest or balance change records, and in theory process only
        // what is required to calculate compound interest with general computaitonal efficiency
        // yes, maybe in the future adding a MIN here would be good..
        while (interval < intervalNumber(block.timestamp)) {

            // set interest rates for given interval
            if (interval == nextInterestInterval) {
                uint256 interestIndex = intervalToInterestIndex[interval];

                // set rate with current interval
                interestRate = interestRates[interestIndex].rate;

                // check if look ahead next index is greater than rates length, if so, go to max interval, otherwise next up
                nextInterestInterval = add(interestIndex, 1) >= interestRates.length
                    ? intervalNumber(block.timestamp)
                    : interestRates[add(interestIndex, 1)].interval;
            }

            // setup principle with whats on record at given interval
            if (interval == nextBalanceInterval) {
                // get current principle at current balance index, add with amount previously owed in interest
                principle = add(balanceRecords[tokenOwner][balanceIndex].balance, amountOwed);

                // increase balance index ahead now that we have the balance
                balanceIndex = add(balanceIndex, 1);

                // check if the new blance index exceeds, set next interval to limit or next interval on record
                nextBalanceInterval = balanceIndex >= balanceRecords[tokenOwner].length
                    ? intervalNumber(block.timestamp)
                    : balanceRecords[tokenOwner][balanceIndex].interval;
            }

            // apply compound interest to principle, subtract original principle, add to amount owed
            amountOwed = add(amountOwed, sub(wmul(principle,
                rpow(interestRate,
                    sub(min(nextBalanceInterval, nextInterestInterval), interval)) / 10 ** 9),
                        principle));

            // set interval to next nearest major balance or interest (or both) change
            interval = min(nextBalanceInterval, nextInterestInterval);
        }

        // return amount owed, adjusted balance index, and the last interval set / used
        return (amountOwed, (balanceIndex > 0 ? sub(balanceIndex, 1) : 0), interval);
    }

    // record users balance (max 2 writes additional per person per transfer)
    function recordBalance(address tokenOwner) internal {
        // todays current interval id
        uint256 todaysInterval = intervalNumber(block.timestamp);

        // last balance index
        uint256 latestBalanceIndex = balanceRecords[tokenOwner].length > 0
            ? sub(balanceRecords[tokenOwner].length, 1) : 0;

        // always update the current record (i.e. todays interval)
        // record balance record (if latest record is for today, add to it, otherwise add a record)
        if (balanceRecords[tokenOwner].length > 0
            && balanceRecords[tokenOwner][latestBalanceIndex].interval == todaysInterval) {
            balanceRecords[tokenOwner][latestBalanceIndex].balance = balanceOf(tokenOwner);
        } else {
            balanceRecords[tokenOwner].push(BalanceRecord({
                interval: todaysInterval,
                intervalOffset: todaysInterval,
                balance: balanceOf(tokenOwner)
            }));
        }

        // if no interval to interest mapping exists, map it (should always be at least a length of one)
        if (intervalToInterestIndex[todaysInterval] <= 0) {
            intervalToInterestIndex[todaysInterval] = sub(interestRates.length, 1); }
    }

    // record a new intrest rate change
    function recordInterestRate(uint256 rate) internal {
        // min number precision for rate.. might need to add a max here.
        assert(rate >= RAY);

        // todays current interval id
        uint256 todaysInterval = intervalNumber(block.timestamp);

        // last balance index
        uint256 latestRateIndex = interestRates.length > 0
            ? sub(interestRates.length, 1) : 0;

        // always update todays interval
        // record balance record (if latest record is for today, add to it, otherwise add a record)
        if (interestRates.length > 0
            && interestRates[latestRateIndex].interval == todaysInterval) {
            interestRates[latestRateIndex].rate = rate;
        } else {
            interestRates.push(InterestRate({
                interval: todaysInterval,
                rate: rate
            }));
        }

        // map the interval to interest index always
        intervalToInterestIndex[todaysInterval] = sub(interestRates.length, 1);
    }
}
/**
 * @title InterestRateBased
 * @author UnityCoin Team
 * @notice A compount interest module which allows for the recording of balance
 *       events and interest rate changes.
 *
 *       Compound Interest Aglo:
 *       compound interest owed = (principle * ( 1 + Rate / 100 ) * N) – principle;
 *
 *       This module uses the interval system IntervalBased.
 *       The module is time based and thus we accept that miners can manipulate
 *       time.
 *
 *       Rate's are specified (as per DSMath imp.) in 10 pow 27 always.
 *       Rates are recorded in an array `interestRates` and thus are indexed.
 *
 *       Everytime a balance is recorded, we also make sure to set an updatable
 *       pointer to the most recent InterestRate index `intervalToInterestIndex`.
 *
 *       This module provides a way to calculate compound interest owed,
 *       given the interest rates and balance records are recoded properly.
 */
function recordInterestRate(uint256 rate) internal {
    // min number precision for rate.. might need to add a max here.
    assert(rate >= RAY);

    // todays current interval id
    uint256 todaysInterval = intervalNumber(block.timestamp);

    // last balance index
    uint256 latestRateIndex = interestRates.length > 0
        ? sub(interestRates.length, 1) : 0;

    // always update todays interval
    // record balance record (if latest record is for today, add to it, otherwise add a record)
    if (interestRates.length > 0
        && interestRates[latestRateIndex].interval == todaysInterval) {
        interestRates[latestRateIndex].rate = rate;
    } else {
        interestRates.push(InterestRate({
            interval: todaysInterval,
            rate: rate
        }));
    }

    // map the interval to interest index always
    intervalToInterestIndex[todaysInterval] = sub(interestRates.length, 1);
}

// record a new intrest rate change