contract BabyHipsterBuilder is Ownable {

    address internal cryptoPunksAttributesAddr;
    address internal babyHipstersAddr;

    struct Attribute {
        uint256 id;
        string name;
        string svg;
    }

    Attribute[] internal attributes;

    mapping(string => uint256) internal _attributeNameToId;
    
    mapping (string => uint) internal _speciesToCode;

    string[] private speciesList = [
        "Human Albino",
        "Human Light",
        "Human Mid",
        "Human Dark",
        "Human Ape Albino",
        "Human Ape Light",
        "Human Ape Mid",
        "Human Ape Dark",
        "Ape",
        "Human Alien Albino",
        "Human Alien Light",
        "Human Alien Mid",
        "Human Alien Dark",
        "Alien Ape",
        "Human Zombie Albino",
        "Human Zombie Light",
        "Human Zombie Mid",
        "Human Zombie Dark",
        "Zombie Ape",
        "Zombie",
        "Alien",
        "Wildcard 1",
        "Wildcard 2",
        "Zombie Alien" 
    ];

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

    function setCPAttributesAddr(address contractAddr) public onlyOwner{
        cryptoPunksAttributesAddr = contractAddr;
    }

    function setBabyHipstersAddr(address contractAddr) public onlyOwner{
        babyHipstersAddr = contractAddr;
    }
    
    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    function random(string memory input) 
    internal 
    pure 
    returns (uint256) 
    {
        return uint256(keccak256(abi.encodePacked(input)));
    }

    /**
     * @notice Create an Attribute
     * @param _name the name of the attribute
     * @param _svg svg representation of the attribute
     * @return attributeId the token ID of the newly created attribute
     */
    function createAttribute(
        string calldata _name,
        string calldata _svg
    )
    external 
    onlyOwner
    returns (uint256 attributeId)
    {
        require(bytes(_name).length > 0, "BabyHipsterBuilder: Name can't be empty");

        attributeId = attributes.length;

        attributes.push(
            Attribute(attributeId, _name, _svg)
        );

        _attributeNameToId[_name] = attributeId;

        return attributeId;
    }

    function calculateBabySpecies(
        uint256 tokenId, 
        string memory parent1Species, 
        string memory parent2Species
    ) 
        internal 
        view 
        returns (string memory) 
    {
        uint256 rand = random(string(abi.encodePacked(tokenId, parent1Species, parent2Species)));
        
        uint256 babyHipsterSpeciesIndex;

        uint256 parent1SpeciesCode = _speciesToCode[parent1Species];
        uint256 parent2SpeciesCode = _speciesToCode[parent2Species];
        
        uint256 tieBreak = rand % 2;

        if (parent1SpeciesCode < 7 && parent2SpeciesCode < 7) {
            if ((parent1SpeciesCode + parent2SpeciesCode) % 4 != 0) {
                uint256 temp = (parent1SpeciesCode + parent2SpeciesCode) / 4;
                babyHipsterSpeciesIndex = temp + tieBreak;
            } else {
                babyHipsterSpeciesIndex = (parent1SpeciesCode + parent2SpeciesCode) / 4;
            }
        } else {
            babyHipsterSpeciesIndex = (parent1SpeciesCode + parent2SpeciesCode) / 2;
        }
        
        return speciesList[babyHipsterSpeciesIndex];
    }
    
    function getGenes(uint256 tokenId, string memory categoryName) internal pure returns (uint256) {
        uint256 rand = random(string(abi.encodePacked(uint2str(tokenId), categoryName)));
        uint256 randomOption = rand % 100;
        if (randomOption < 50) {
            return 0; // For making the gene recessive
        } else {
            return 1; // For making the gene recessive
        }
    }
    
    function pickAttribute(
        uint256 tokenId, 
        string memory categoryName, 
        string memory attribute1, 
        string memory attribute2
    ) 
        internal 
        pure 
        returns (string memory) 
    {
            uint256 rand = getGenes(tokenId, categoryName);
            if (keccak256(abi.encodePacked(attribute1)) == keccak256(abi.encodePacked(attribute2))) {
                return attribute1;
            } else {
                if (rand == 0) {
                    return attribute1;
                } else {
                    return attribute2;
                }
            }
    }

    function buildBabyHipster(uint256 tokenId, uint256 parent1, uint256 parent2) 
        public 
        returns (string[9] memory thisBabyHipsterAttributes) 
    {
        if (!ICryptoPunksAttributes(cryptoPunksAttributesAddr).exists(parent1)) {
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).createStructForPunk(parent1);
        }

        if (!ICryptoPunksAttributes(cryptoPunksAttributesAddr).exists(parent2)) {
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).createStructForPunk(parent2);
        }

        string memory parent1Species = ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeSpeciesFrom(parent1);
        string memory parent2Species = ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeSpeciesFrom(parent2);

        thisBabyHipsterAttributes[0] = calculateBabySpecies(tokenId, parent1Species, parent2Species);

        thisBabyHipsterAttributes[1] = pickAttribute(
            tokenId,
            "Ears",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeEarsFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeEarsFrom(parent2));

        thisBabyHipsterAttributes[2] = pickAttribute(
            tokenId,
            "Top Head",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeTopHeadFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeTopHeadFrom(parent2));
        
        thisBabyHipsterAttributes[3] = pickAttribute(
            tokenId,
            "Eyes",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeEyesFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeEyesFrom(parent2));

        thisBabyHipsterAttributes[4] = pickAttribute(
            tokenId,
            "Neck",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeNeckFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeNeckFrom(parent2));

        thisBabyHipsterAttributes[5] = pickAttribute(
            tokenId,
            "Face",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeFaceFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeFaceFrom(parent2));

        thisBabyHipsterAttributes[6] = pickAttribute(
            tokenId,
            "Mouth",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeMouthFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeMouthFrom(parent2));

        thisBabyHipsterAttributes[7] = pickAttribute(
            tokenId,
            "Mouth Accessory",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeMouthAccessoryFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeMouthAccessoryFrom(parent2));

        thisBabyHipsterAttributes[8] = pickAttribute(
            tokenId,
            "Facial Hair",
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeFacialHairFrom(parent1),
            ICryptoPunksAttributes(cryptoPunksAttributesAddr).getAttributeFacialHairFrom(parent2));

        return thisBabyHipsterAttributes;
    }

    
    function buildBabyHipsterImage(string[9] memory thisBabyHipsterAttributes) public view returns (string memory svg) {
        string[10] memory parts;
        parts[0] = "<svg xmlns='http://www.w3.org/2000/svg' version='1.2' viewBox='0 0 24 24' shape-rendering='crispEdges'>";
        parts[1] = attributes[_attributeNameToId[thisBabyHipsterAttributes[0]]].svg;
        parts[2] = attributes[_attributeNameToId[thisBabyHipsterAttributes[1]]].svg;
        parts[3] = attributes[_attributeNameToId[thisBabyHipsterAttributes[2]]].svg;
        parts[4] = attributes[_attributeNameToId[thisBabyHipsterAttributes[3]]].svg;
        parts[5] = attributes[_attributeNameToId[thisBabyHipsterAttributes[4]]].svg;
        parts[6] = attributes[_attributeNameToId[thisBabyHipsterAttributes[5]]].svg;
        parts[7] = attributes[_attributeNameToId[thisBabyHipsterAttributes[6]]].svg;
        parts[8] = attributes[_attributeNameToId[thisBabyHipsterAttributes[7]]].svg;     
        parts[9] = "</svg>";

        svg = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8], parts[9]));

        return svg;
    }
    
    /**************************/
    /***  Public Functions  ***/
    /**************************/

    function setSpeciesToCode(string memory thisSpecies, uint thisCode) public onlyOwner {
        _speciesToCode[thisSpecies] = thisCode;
    }

    constructor() Ownable() {}
}
function createAttribute(
    string calldata _name,
    string calldata _svg
)
external 
onlyOwner
returns (uint256 attributeId)
{
    require(bytes(_name).length > 0, "BabyHipsterBuilder: Name can't be empty");

    attributeId = attributes.length;

    attributes.push(
        Attribute(attributeId, _name, _svg)
    );

    _attributeNameToId[_name] = attributeId;

    return attributeId;
}

/**
 * @notice Create an Attribute
 * @param _name the name of the attribute
 * @param _svg svg representation of the attribute
 * @return attributeId the token ID of the newly created attribute
 */