contract ColorToken is LockToken, Ownable {
    //-------------------------------------------------------------------------
    /// @dev Emits when a new colored token is created.
    //-------------------------------------------------------------------------
    event NewColor(address indexed creator, string name);

    //-------------------------------------------------------------------------
    /// @dev Emits when colored tokens are deposited into TOY Tokens. Color
    ///  equivalent to PLAY.transfer().
    //-------------------------------------------------------------------------
    event DepositColor(
        uint indexed to, 
        uint indexed colorIndex, 
        uint tokens
    );

    //-------------------------------------------------------------------------
    /// @dev Emits when colored tokens are spent by any mechanism. Color
    ///  equivalent to PLAY.burn().
    //-------------------------------------------------------------------------
    event SpendColor(
        uint indexed from, 
        uint indexed color, 
        uint amount
    );

    // Colored token data
    struct ColoredToken {
        address creator;
        string name;
        mapping (uint => uint) balances;
    }

    // array containing all colored token data
    ColoredToken[] coloredTokens;
    // required locked tokens needed to register a color (in pWei)
    uint public requiredLockedForColorRegistration = 10000 * 10**18;
    // TOY Token contract to interface with
    ToyTokenOwnership toy;
    // UID value is 7 bytes. Max value is 2**56
    uint constant UID_MAX = 0xFFFFFFFFFFFFFF;

    //-------------------------------------------------------------------------
    /// @notice Set the address of the TOY Token interface to `toyAddress`.
    /// @dev Throws if toyAddress is the zero address.
    /// @param toyAddress The address of the TOY Token interface.
    //-------------------------------------------------------------------------
    function setToyTokenContractAddress (address toyAddress) 
        external 
        notZero(uint(toyAddress)) 
        onlyOwner
    {
        // initialize contract to toyAddress
        toy = ToyTokenOwnership(toyAddress);
    }

    //-------------------------------------------------------------------------
    /// @notice Set required total locked tokens to 
    ///  `(newAmount/1000000000000000000).fixed(0,18)`.
    /// @dev Throws if the sender is not the contract owner. Throws if new
    ///  amount is zero.
    /// @param newAmount The new required locked token amount (in pWei).
    //-------------------------------------------------------------------------
    function setRequiredLockedForColorRegistration(uint newAmount) 
        external 
        onlyOwner
        notZero(newAmount)
    {
        requiredLockedForColorRegistration = newAmount;
    }
    
    //-------------------------------------------------------------------------
    /// @notice Registers `colorName` as a new colored token. Must own
    ///  `requiredLockedForColorReg` locked tokens as a requirement.
    /// @dev Throws if `msg.sender` has insufficient locked tokens. Throws if
    ///  colorName is empty or is longer than 32 characters.
    /// @param colorName The name for the new colored token.
    /// @return Index number for the new colored token.
    //-------------------------------------------------------------------------
    function registerNewColor(string colorName) external returns (uint) {
        // sender must have enough locked tokens
        require (
            getTotalLockedTokens(msg.sender) >= requiredLockedForColorRegistration,
            "Insufficient locked tokens"
        );
        // colorName must be a valid length
        require (
            bytes(colorName).length > 0 && bytes(colorName).length < 32,
            "Invalid color name length"
        );
        // push new colored token to colored token array and store the index
        uint index = coloredTokens.push(ColoredToken(msg.sender, colorName));
        return index;
    }

    //-------------------------------------------------------------------------
    /// @notice Locks `(tokens/1000000000000000000).fixed(0,18)` PLAY tokens
    ///  for 2 years, then deposits the same number of colored tokens with 
    ///  index `colorIndex` into TOY Token #`uid`.
    /// @dev Throws if tokens to deposit is zero. Throws if colorIndex is
    ///  greater than number of colored tokens. Throws if `msg.sender` has
    ///  insufficient balance to lock. Throws if `uid` is greater than
    ///  maximum UID value. Throws if token does not have an owner. Throws if
    ///  sender is not the creator of the colored token.
    /// @param to The Unique Identifier of the TOY Token receiving tokens.
    /// @param colorIndex The index of the color to spend.
    /// @param tokens The number of colored tokens to spend (in pWei).
    //-------------------------------------------------------------------------
    function deposit (uint colorIndex, uint to, uint tokens)
        external 
        notZero(tokens)
    {
        // colorIndex must be valid color
        require (colorIndex < coloredTokens.length, "Invalid color index");
        // sender must be colored token creator
        require (
            msg.sender == coloredTokens[colorIndex].creator,
            "Not authorized to deposit this color"
        );
        // uid must be a valid UID
        require (to < UID_MAX, "Invalid UID");
        // If TOY Token #uid is not owned, it does not exist yet.
        require(toy.ownerOf(to) != address(0), "TOY Token does not exist");
        
        // Initiate lock. Fails if sender's balance is too low.
        lock(2, tokens);

        // add tokens to TOY Token #UID
        coloredTokens[colorIndex].balances[to] += tokens;
        // emit color transfer event
        emit DepositColor(to, colorIndex, tokens);
    }

    //-------------------------------------------------------------------------
    /// @notice Locks `(tokens/1000000000000000000).fixed(0,18)` PLAY tokens
    ///  for 2 years, then deposits the same number of colored tokens with 
    ///  index `colorIndex` into multiple TOY Tokens.
    /// @dev Throws if tokens to deposit is zero. Throws if colorIndex is
    ///  greater than number of colored tokens. Throws if `msg.sender` has
    ///  insufficient balance to lock. Throws if `uid` is greater than
    ///  maximum UID value. Throws if any token does not have an owner. Throws
    ///  if sender is not the creator of the colored token.
    /// @param to The Unique Identifier of the TOY Token receiving tokens.
    /// @param colorIndex The index of the color to spend.
    /// @param tokens The number of colored tokens to spend (in pWei).
    //-------------------------------------------------------------------------
    function depositBulk (uint colorIndex, uint[] to, uint tokens)
        external 
        notZero(tokens)
    {
        // colorIndex must be valid color
        require (colorIndex < coloredTokens.length, "Invalid color index");
        // sender must be colored token creator
        require (
            msg.sender == coloredTokens[colorIndex].creator,
            "Not authorized to deposit this color"
        );

        // Initiate lock. Fails if sender's balance is too low.
        lock(2, tokens * to.length);

        for(uint i = 0; i < to.length; ++i){
            // uid must be a valid UID
            require (to[i] < UID_MAX, "Invalid UID");
            // If TOY Token #uid is not owned, it does not exist yet.
            require(toy.ownerOf(to[i]) != address(0), "TOY Token does not exist");

            // add tokens to TOY Token #UID
            coloredTokens[colorIndex].balances[to[i]] += tokens;
            // emit color transfer event
            emit DepositColor(to[i], colorIndex, tokens);
        }
    }

    //-------------------------------------------------------------------------
    /// @notice Spend `(tokens/1000000000000000000).fixed(0,18)` colored 
    ///  tokens with index `colorIndex`.
    /// @dev Throws if tokens to spend is zero. Throws if colorIndex is
    ///  greater than number of colored tokens. Throws if `msg.sender` has
    ///  insufficient balance to spend.
    /// @param colorIndex The index of the color to spend.
    /// @param from The UID of the TOY Token to spend from.
    /// @param tokens The number of colored tokens to spend (in pWei).
    /// @return True if spend successful. Throw if unsuccessful.
    //-------------------------------------------------------------------------
    function spend (uint colorIndex, uint from, uint tokens) 
        external 
        notZero(tokens)
        returns(bool) 
    {
        // colorIndex must be valid color
        require (colorIndex < coloredTokens.length, "Invalid color index");
        // sender must own TOY Token
        require (
            msg.sender == toy.ownerOf(from), 
            "Sender is not owner of TOY Token"
        );
        // token owner's balance must be enough to spend tokens
        require (
            coloredTokens[colorIndex].balances[from] >= tokens,
            "Insufficient tokens to spend"
        );
        // deduct the tokens from the sender's balance
        coloredTokens[colorIndex].balances[from] -= tokens;
        // emit spend event
        emit SpendColor(from, colorIndex, tokens);
        return true;
    }

    //-------------------------------------------------------------------------
    /// @notice Spend `(tokens/1000000000000000000).fixed(0,18)` colored
    ///  tokens with color index `index` from `from`.
    /// @dev Throws if tokens to spend is zero. Throws if colorIndex is 
    ///  greater than number of colored tokens. Throws if `msg.sender` is not
    ///  the colored token's creator. Throws if `from` has insufficient
    ///  balance to spend.
    /// @param colorIndex The index of the color to spend.
    /// @param from The address whose colored tokens are being spent.
    /// @param tokens The number of tokens to send (in pWei).
    //-------------------------------------------------------------------------
    function spendFrom(uint colorIndex, uint from, uint tokens)
        external 
        notZero(tokens)
        returns (bool) 
    {
        // colorIndex must be valid color
        require (colorIndex < coloredTokens.length, "Invalid color index");
        // sender must be authorized address or operator for TOY Token
        require (
            msg.sender == toy.getApproved(from) ||
            toy.isApprovedForAll(toy.ownerOf(from), msg.sender), 
            "Sender is not authorized operator for TOY Token"
        );
        // token owner's balance must be enough to spend tokens
        require (
            coloredTokens[colorIndex].balances[from] >= tokens,
            "Insufficient balance to spend"
        );
        // deduct the tokens from token owner's balance
        coloredTokens[colorIndex].balances[from] -= tokens;
        // emit spend event
        emit SpendColor(from, colorIndex, tokens);
        return true;
    }

    //-------------------------------------------------------------------------
    /// @notice Get the number of colored tokens with color index `colorIndex`
    ///  owned by TOY Token #`uid`.
    /// @param uid The TOY Token with deposited color tokens.
    /// @param colorIndex Index of the colored token to query.
    /// @return The number of colored tokens with color index `colorIndex`
    ///  owned by TOY Token #`uid`.
    //-------------------------------------------------------------------------
    function getColoredTokenBalance(uint uid, uint colorIndex) 
        external 
        view 
        returns(uint) 
    {
        return coloredTokens[colorIndex].balances[uid];
    }

    //-------------------------------------------------------------------------
    /// @notice Count the number of colored token types
    /// @return Number of colored token types
    //-------------------------------------------------------------------------
    function coloredTokenCount() external view returns (uint) {
        return coloredTokens.length;
    }

    //-------------------------------------------------------------------------
    /// @notice Get the name and creator address of colored token with index
    ///  `colorIndex`
    /// @param colorIndex Index of the colored token to query.
    /// @return The creator address and name of colored token.
    //-------------------------------------------------------------------------
    function getColoredToken(uint colorIndex) 
        external 
        view 
        returns(address, string)
    {
        return (
            coloredTokens[colorIndex].creator, 
            coloredTokens[colorIndex].name
        );
    }
}
//-----------------------------------------------------------------------------
function spendFrom(uint colorIndex, uint from, uint tokens)
    external 
    notZero(tokens)
    returns (bool) 
{
    // colorIndex must be valid color
    require (colorIndex < coloredTokens.length, "Invalid color index");
    // sender must be authorized address or operator for TOY Token
    require (
        msg.sender == toy.getApproved(from) ||
        toy.isApprovedForAll(toy.ownerOf(from), msg.sender), 
        "Sender is not authorized operator for TOY Token"
    );
    // token owner's balance must be enough to spend tokens
    require (
        coloredTokens[colorIndex].balances[from] >= tokens,
        "Insufficient balance to spend"
    );
    // deduct the tokens from token owner's balance
    coloredTokens[colorIndex].balances[from] -= tokens;
    // emit spend event
    emit SpendColor(from, colorIndex, tokens);
    return true;
}

//-------------------------------------------------------------------------