contract CryptoPunkAttributes is Ownable {

    address internal constant CRYPTO_PUNKS_DATA_ADDR = 0x16F5A35647D6F03D5D3da7b35409D65ba03aF3B2;

    struct CryptoPunk {
        string species;
        string ears;
        string topHead;
        string eyes;
        string neck;
        string face;
        string mouth;
        string mouthAccessory;
        string facialHair;
    }

    mapping(uint256 => CryptoPunk) internal _punkIdToAttributes;

    mapping(uint256 => address) private _creators;

    mapping(string => uint) private _attributesToCategoryIndex;

    /**************************/
    /*** Internal Functions ***/
    /**************************/

    /**
     * Strings Library (author James Lockhart: james at n3tw0rk.co.uk)
     *
     * Index Of
     *
     * Locates and returns the position of a character within a string starting
     * from a defined offset
     * 
     * @param _base When being used for a data type this is the extended object
     *              otherwise this is the string acting as the haystack to be
     *              searched
     * @param _value The needle to search for, at present this is currently
     *               limited to one character
     * @param _offset The starting point to start searching from which can start
     *                from 0, but must not exceed the length of the string
     * @return int The position of the needle starting from 0 and returning -1
     *             in the case of no matches found
     */
        function _indexOf(string memory _base, string memory _value, uint _offset)
        internal
        pure
        returns (int) {
        bytes memory _baseBytes = bytes(_base);
        bytes memory _valueBytes = bytes(_value);

        assert(_valueBytes.length == 1);

        for (uint i = _offset; i < _baseBytes.length; i++) {
            if (_baseBytes[i] == _valueBytes[0]) {
                return int(i);
            }
        }

        return -1;
    }
    
    /**
     * Strings Library (author James Lockhart: james at n3tw0rk.co.uk)
     * 
     * Modified String Split:
     * String splitByComaAndSpace
     *
     * Splits a string into an array of strings based off the delimiter value, in this case ",".
     *
     * @param _base When being used for a data type this is the extended object
     *               otherwise this is the string value to be split.
     * 
     * @return splitArr
     */
        function _splitByComaAndSpace(string memory _base) internal pure returns (string[] memory splitArr) {
            bytes memory _baseBytes = bytes(_base);
            string memory _value = ",";
            
            uint _offset = 0;
        uint _splitsCount = 1;
        while (_offset < _baseBytes.length - 1) {
            int _limit = _indexOf(_base, _value, _offset);
            if (_limit == -1)
                break;
            else {
                _splitsCount++;
                _offset = uint(_limit) + 1;
            }
        }

        splitArr = new string[](_splitsCount);
        
        _offset = 0;
        _splitsCount = 0;
        
        while (_offset < _baseBytes.length - 1) {
            int _limit = _indexOf(_base, _value, _offset);
            if (_limit == -1) {
                _limit = int(_baseBytes.length);
            }
            
            string memory _tmp = new string(uint(_limit) - _offset);
            bytes memory _tmpBytes = bytes(_tmp);
            
            uint j = 0;
            for (uint i = _offset; i < uint(_limit); i++) {
                _tmpBytes[j++] = _baseBytes[i];
            }
            
            _offset = uint(_limit) + 2;
            splitArr[_splitsCount++] = string(_tmpBytes);
        }
        return splitArr;
        }


    /**************************/
    /***  Public Functions  ***/
    /**************************/

    function exists(uint256 tokenId) public view returns (bool) {
        return _creators[tokenId] != address(0);
    }

    function createStructForPunk(uint punkIndex) public {
        require(!exists(punkIndex), "Crypto Punks Attributes: There are already attributes stored for the Punk you are requesting");
        require(punkIndex >= 0 && punkIndex < 10000, "Crypto Punks Attributes: You have to enter a Punk Index between 0 and 9999");
        string memory punkAttributes = ICryptoPunksData(CRYPTO_PUNKS_DATA_ADDR).punkAttributes(uint16(punkIndex));

        string[] memory punkAttributesArray = _splitByComaAndSpace(punkAttributes);

        CryptoPunk memory thisCryptoPunk;

        thisCryptoPunk.topHead;

        thisCryptoPunk.species = punkAttributesArray[0];

        for (uint i = 1; i < punkAttributesArray.length; i++) {

            uint attributeCategoryIndex = _attributesToCategoryIndex[punkAttributesArray[i]];
            
            if (attributeCategoryIndex == 1) {
                thisCryptoPunk.ears = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 2) {
                thisCryptoPunk.topHead = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 3) {
                thisCryptoPunk.eyes = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 4) {
                thisCryptoPunk.neck = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 5) {
                thisCryptoPunk.face = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 6) {
                thisCryptoPunk.mouth = punkAttributesArray[i];
            } 
            else if (attributeCategoryIndex == 7) {
                thisCryptoPunk.mouthAccessory = punkAttributesArray[i];
            }
            else if (attributeCategoryIndex == 8) {
                thisCryptoPunk.facialHair = punkAttributesArray[i];
            }
            }

        _punkIdToAttributes[punkIndex] = thisCryptoPunk;
        _creators[punkIndex] = msg.sender;
    }

    function getAttributeSpeciesFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].species;
    }

    function getAttributeTopHeadFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].topHead;
    }

    function getAttributeEyesFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].eyes;
    }

    function getAttributeEarsFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].ears;
    }

    function getAttributeNeckFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].neck;
    }

    function getAttributeFaceFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].face;
    }

    function getAttributeMouthFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].mouth;
    }

    function getAttributeMouthAccessoryFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].mouthAccessory;
    }

    function getAttributeFacialHairFrom(uint punkIndex) public view returns (string memory attribute) {
        require(exists(punkIndex), "Crypto Punks Attributes: There aren't any attributes stored for the Punk you are requesting");
        return _punkIdToAttributes[punkIndex].facialHair;
    }

    function setCategoryIndex(string memory attribute, uint categoryIndex) public onlyOwner {
        _attributesToCategoryIndex[attribute] = categoryIndex;
    }

    constructor() Ownable() {}
}
function _splitByComaAndSpace(string memory _base) internal pure returns (string[] memory splitArr) {
    bytes memory _baseBytes = bytes(_base);
    string memory _value = ",";
            
    uint _offset = 0;
uint _splitsCount = 1;
while (_offset < _baseBytes.length - 1) {
    int _limit = _indexOf(_base, _value, _offset);
    if (_limit == -1)
        break;
    else {
        _splitsCount++;
        _offset = uint(_limit) + 1;
    }
}

splitArr = new string[](_splitsCount);
        
_offset = 0;
_splitsCount = 0;
        
while (_offset < _baseBytes.length - 1) {
    int _limit = _indexOf(_base, _value, _offset);
    if (_limit == -1) {
        _limit = int(_baseBytes.length);
    }
            
    string memory _tmp = new string(uint(_limit) - _offset);
    bytes memory _tmpBytes = bytes(_tmp);
            
    uint j = 0;
    for (uint i = _offset; i < uint(_limit); i++) {
        _tmpBytes[j++] = _baseBytes[i];
    }
            
    _offset = uint(_limit) + 2;
    splitArr[_splitsCount++] = string(_tmpBytes);
}
return splitArr;
}

/**
 * Strings Library (author James Lockhart: james at n3tw0rk.co.uk)
 * 
 * Modified String Split:
 * String splitByComaAndSpace
 *
 * Splits a string into an array of strings based off the delimiter value, in this case ",".
 *
 * @param _base When being used for a data type this is the extended object
 *               otherwise this is the string value to be split.
 * 
 * @return splitArr
 */