contract CrystalBase is Acceptable, ERC721Token {

    // Each of Crystal occupies 3 slots.
    struct Crystal {
        /* slot1 */

        uint256 tokenId;

        /* slot2 */
        uint256 gene;

        /* slot3 */

        // kind is between 0 and 100.
        // 2^8 = 256 is adequate.
        uint8 kind;

        // totalWeight of cryptocrystals is 21 * 10**13.
        // 2^128 = 3.4028237e+38 is adequate.
        uint128 weight;

        // unix timestamp
        uint64 mintedAt;
    }

    mapping(uint256 => Crystal) internal tokenIdToCrystal;
    event CrystalBurned(address indexed owner, uint256 tokenId);
    event CrystalMinted(address indexed owner, uint256 tokenId, uint256 gene, uint256 kind, uint256 weight);

    uint256 currentTokenId = 1;

    constructor() ERC721Token("CryptoCrystal", "CC") public {

    }

    function mint(
        address _owner,
        uint256 _gene,
        uint256 _kind,
        uint256 _weight
    ) public onlyAcceptable returns(uint256) {
        require(_gene > 0);
        require(_weight > 0);

        uint256 _tokenId = currentTokenId;
        currentTokenId++;
        super._mint(_owner, _tokenId);
        Crystal memory _crystal = Crystal({
            tokenId: _tokenId,
            gene: _gene,
            kind: uint8(_kind),
            weight: uint128(_weight),
            mintedAt: uint64(now)
            });
        tokenIdToCrystal[_tokenId] = _crystal;
        emit CrystalMinted(_owner, _tokenId, _gene, _kind, _weight);
        return _tokenId;
    }

    function burn(address _owner, uint256 _tokenId) public onlyAcceptable {
        require(ownerOf(_tokenId) == _owner);

        delete tokenIdToCrystal[_tokenId];
        super._burn(_owner, _tokenId);
        emit CrystalBurned(_owner, _tokenId);
    }

    // @dev Transfers the ownership of a given token ID to another address.
    //      _transferFrom is almost the same to openzeppelin-solidity's implementation.
    //      ref. https://github.com/OpenZeppelin/openzeppelin-solidity/blob/ad12381549c4c0711c2f3310e9fb1f65d51c299c/contracts/token/ERC721/ERC721BasicToken.sol#L140
    //      We use onlyAcceptable modifier instead of canTransfer.
    //      _transferFrom is intended to be called only from cryptocrystal contract.
    // @param _from owner of the token
    // @param _to address to receive the ownership of the given token ID
    // @param _tokenId uint256 ID of the token to be transferred
    function _transferFrom(address _from, address _to, uint256 _tokenId) public onlyAcceptable {
        require(ownerOf(_tokenId) == _from);
        require(_to != address(0));

        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);

        emit Transfer(_from, _to, _tokenId);
    }

    function getCrystalKindWeight(uint256 _tokenId) public onlyAcceptable view returns(
        uint256 kind,
        uint256 weight
    ) {
        require(exists(_tokenId));

        Crystal memory _crystal = tokenIdToCrystal[_tokenId];
        kind = _crystal.kind;
        weight = _crystal.weight;
    }

    function getCrystalGeneKindWeight(uint256 _tokenId) public onlyAcceptable view returns(
        uint256 gene,
        uint256 kind,
        uint256 weight
    ) {
        require(exists(_tokenId));

        Crystal memory _crystal = tokenIdToCrystal[_tokenId];
        gene = _crystal.gene;
        kind = _crystal.kind;
        weight = _crystal.weight;
    }

    function getCrystal(uint256 _tokenId) external view returns(
        address owner,
        uint256 gene,
        uint256 kind,
        uint256 weight,
        uint256 mintedAt
    ) {
        require(exists(_tokenId));

        Crystal memory _crystal = tokenIdToCrystal[_tokenId];

        owner = ownerOf(_tokenId);
        gene = _crystal.gene;
        kind = _crystal.kind;
        weight = _crystal.weight;
        mintedAt = _crystal.mintedAt;
    }

    function getCrystalsSummary(address _owner) external view returns(
        uint256[] amounts,
        uint256[] weights
    ) {
        amounts = new uint256[](100);
        weights = new uint256[](100);
        uint256 _tokenCount = ownedTokensCount[_owner];
        for (uint256 i = 0; i < _tokenCount; i++) {
            uint256 _tokenId = ownedTokens[_owner][i];
            Crystal memory _crystal = tokenIdToCrystal[_tokenId];
            amounts[_crystal.kind] = amounts[_crystal.kind].add(1);
            weights[_crystal.kind] = weights[_crystal.kind].add(_crystal.weight);
        }
    }

    function getCrystals(address _owner) external view returns(
        uint256[] tokenIds,
        uint256[] genes,
        uint256[] kinds,
        uint256[] weights,
        uint256[] mintedAts
    ) {
        uint256 _tokenCount = ownedTokensCount[_owner];
        tokenIds = new uint256[](_tokenCount);
        genes = new uint256[](_tokenCount);
        kinds = new uint256[](_tokenCount);
        weights = new uint256[](_tokenCount);
        mintedAts = new uint256[](_tokenCount);
        for (uint256 i = 0; i < _tokenCount; i++) {
            uint256 _tokenId = ownedTokens[_owner][i];
            Crystal memory _crystal = tokenIdToCrystal[_tokenId];
            tokenIds[i] = _tokenId;
            genes[i] = _crystal.gene;
            kinds[i] = _crystal.kind;
            weights[i] = _crystal.weight;
            mintedAts[i] = _crystal.mintedAt;
        }
    }

    function getCrystalsByKind(address _owner, uint256 _kind) external view returns(
        uint256[] tokenIds,
        uint256[] genes,
        uint256[] weights,
        uint256[] mintedAts
    ) {
        require(_kind < 100);

        uint256 _tokenCount = ownedTokensCount[_owner];
        tokenIds = new uint256[](_tokenCount);
        genes = new uint256[](_tokenCount);
        weights = new uint256[](_tokenCount);
        mintedAts = new uint256[](_tokenCount);
        uint256 index;
        for (uint256 i = 0; i < _tokenCount; i++) {
            uint256 _tokenId = ownedTokens[_owner][i];
            Crystal memory _crystal = tokenIdToCrystal[_tokenId];
            if (_crystal.kind == _kind) {
                tokenIds[index] = _tokenId;
                genes[index] = _crystal.gene;
                weights[index] = _crystal.weight;
                mintedAts[i] = _crystal.mintedAt;
                index = index.add(1);
            }
        }
    }
}
// @title CrystalBase
// @author Takayuki Jimba
// @dev ERC721 token.
//      mint, burn and transferFrom are supposed to be called from CryptoCrystal contract only.
function _transferFrom(address _from, address _to, uint256 _tokenId) public onlyAcceptable {
    require(ownerOf(_tokenId) == _from);
    require(_to != address(0));

    clearApproval(_from, _tokenId);
    removeTokenFrom(_from, _tokenId);
    addTokenTo(_to, _tokenId);

    emit Transfer(_from, _to, _tokenId);
}

// @dev Transfers the ownership of a given token ID to another address.
//      _transferFrom is almost the same to openzeppelin-solidity's implementation.
//      ref. https://github.com/OpenZeppelin/openzeppelin-solidity/blob/ad12381549c4c0711c2f3310e9fb1f65d51c299c/contracts/token/ERC721/ERC721BasicToken.sol#L140
//      We use onlyAcceptable modifier instead of canTransfer.
//      _transferFrom is intended to be called only from cryptocrystal contract.
// @param _from owner of the token
// @param _to address to receive the ownership of the given token ID
// @param _tokenId uint256 ID of the token to be transferred