contract Etherep {

    bool internal debug;
    address internal manager;
    uint internal fee;
    address internal storageAddress;
    uint internal waitTime;
    mapping (address => uint) internal lastRating;

    /// Events
    event Error(
        address sender,
        string message
    );
    event Debug(string message);
    event DebugInt(int message);
    event DebugUint(uint message);
    event Rating(
        address by, 
        address who, 
        int rating
    );
    event FeeChanged(uint f);
    event DelayChanged(uint d);

    /**
     * Only a certain address can use this modified method
     * @param by The address that can use the method
     */
    modifier onlyBy(address by) { 
        require(msg.sender == by);
        _; 
    }

    /**
     * Delay ratings to be at least waitTime apart
     */
    modifier delay() {
        if (debug == false && lastRating[msg.sender] > now - waitTime) {
            revert();
        }
        _;
    }

    /**
     * Require the minimum fee to be met
     */
    modifier requireFee() {
        require(msg.value >= fee);
        _;
    }

    /** 
     * Constructor
     * @param _manager The key that can make changes to this contract
     * @param _fee The variable fee that will be charged per rating
     * @param _storageAddress The address to the storage contract
     * @param _wait The minimum time in seconds a user has to wait between ratings
     */
    function Etherep(address _manager, uint _fee, address _storageAddress, uint _wait) {
        manager = _manager;
        fee = _fee;
        storageAddress = _storageAddress;
        waitTime = _wait;
        debug = false;
    }

    /**
     * Set debug
     * @param d The debug value that should be set
     */
    function setDebug(bool d) external onlyBy(manager) {
        debug = d;
    }

    /**
     * Get debug
     * @return debug
     */
    function getDebug() external constant returns (bool) {
        return debug;
    }

    /**
     * Change the fee
     * @param newFee New rating fee in Wei
     */
    function setFee(uint newFee) external onlyBy(manager) {
        fee = newFee;
        FeeChanged(fee);
    }

    /**
     * Get the fee
     * @return fee The current fee in Wei
     */
    function getFee() external constant returns (uint) {
        return fee;
    }

    /**
     * Change the rating delay
     * @param _delay Delay in seconds
     */
    function setDelay(uint _delay) external onlyBy(manager) {
        waitTime = _delay;
        DelayChanged(waitTime);
    }

    /**
     * Get the delay time
     * @return delay The current rating delay time in seconds
     */
    function getDelay() external constant returns (uint) {
        return waitTime;
    }

    /**
     * Change the manager
     * @param who The address of the new manager
     */
    function setManager(address who) external onlyBy(manager) {
        manager = who;
    }

    /**
     * Get the manager
     * @return manager The address of this contract's manager
     */
    function getManager() external constant returns (address) {
        return manager;
    }

    /**
     * Drain fees
     */
    function drain() external onlyBy(manager) {
        require(this.balance > 0);
        manager.transfer(this.balance);
    }

    /** 
     * Adds a rating to an address' cumulative score
     * @param who The address that is being rated
     * @param rating The rating(-5 to 5)
     * @return success If the rating was processed successfully
     */
    function rate(address who, int rating) external payable delay requireFee {

        // Check rating for sanity
        require(rating <= 5 && rating >= -5);

        // A rater can not rate himself
        require(who != msg.sender);

        // Get an instance of the RatingStore contract
        RatingStore store = RatingStore(storageAddress);
        
        // Standard weight
        int weight = 0;

        // Convert rating into a fake-float
        int workRating = rating * 100;

        // We need the absolute value
        int absRating;
        if (rating >= 0) {
            absRating = workRating;
        } else {
            absRating = -workRating;
        }

        // Get details on sender if available
        int senderScore;
        uint senderRatings;
        int senderCumulative = 0;
        (senderScore, senderRatings) = store.get(msg.sender);

        // Calculate cumulative score if available for use in weighting. We're 
        // acting as-if the two right-most places are decimals
        if (senderScore != 0) {
            senderCumulative = (senderScore / (int(senderRatings) * 100)) * 100;
        }

        // Calculate the weight if the sender has a positive rating
        if (senderCumulative > 0 && absRating != 0) {

            // Calculate a weight to add to the final rating calculation.  Only 
            // raters who have a positive cumulative score will have any extra 
            // weight.  Final weight should be between 40 and 100 and scale down
            // depending on how strong the rating is.
            weight = (senderCumulative + absRating) / 10;

            // We need the final weight to be signed the same as the rating
            if (rating < 0) {
                weight = -weight;
            }

        }
        
        // Add the weight to the rating
        workRating += weight;

        // Set last rating timestamp
        lastRating[msg.sender] = now;

        // Send event of the rating
        Rating(msg.sender, who, workRating);

        // Add the new rating to their score
        store.add(who, workRating);

    }

    /**
     * Returns the cumulative score for an address
     * @param who The address to lookup
     * @return score The cumulative score
     */
    function getScore(address who) external constant returns (int score) {

        // Get an instance of our storage contract: RatingStore
        RatingStore store = RatingStore(storageAddress);
        
        int cumulative;
        uint ratings;

        // Get the raw scores from RatingStore
        (cumulative, ratings) = store.get(who);
        
        // Calculate the score as a false-float as an average of all ratings
        score = cumulative / int(ratings);

        // We only want to display a maximum of 500 or minimum of -500, even 
        // if it's weighted outside of that range
        if (score > 500) {
            score = 500;
        } else if (score < -500) {
            score = -500;
        }

    }

    /**
     * Returns the cumulative score and count of ratings for an address
     * @param who The address to lookup
     * @return score The cumulative score
     * @return count How many ratings have been made
     */
    function getScoreAndCount(address who) external constant returns (int score, uint ratings) {

        // Get an instance of our storage contract: RatingStore
        RatingStore store = RatingStore(storageAddress);
        
        int cumulative;

        // Get the raw scores from RatingStore
        (cumulative, ratings) = store.get(who);
        
        // The score should have room for 2 decimal places, but ratings is a 
        // single count
        score = cumulative / int(ratings);

    }

}
/** Etherep - Simple Ethereum reputation by address
 
 Contract that takes ratings and calculates a reputation score.  It uses the 
 RatingStore contract as its data storage.
 
 Ratings can be from -5(worst) to 5(best) and are weighted according to the 
 score of the rater. This weight can have a significant skewing towards the
 positive or negative but the representative score can not be below -5 or 
 above 5.  Raters can not rate more often than waitTime, nor can they rate 
 themselves.
 
 Scores are returned as a false-float, where 425 = 4.25 on the Etherep scale.
 */
function setManager(address who) external onlyBy(manager) {
    manager = who;
}

/**
 * Change the manager
 * @param who The address of the new manager
 */