contract Strainz is ERC721Tradable, DNA {
    event Minted(uint tokenId);

    uint public genesisLeft = 100;

    struct Strain {
        uint id;
        string prefix;
        string postfix;
        uint dna;
        uint generation;
        uint growRate; // 0-255
        uint lastHarvest;
        uint breedingCost;
    }

    mapping(uint => Strain) public strainData;
    Budz public budz;

    constructor(address _proxyRegistryAddress) ERC721Tradable("Strainz", "STRAINZ", _proxyRegistryAddress) public {
        _setBaseURI("https://api.strainz.tech/strain/");
        budz = new Budz();
        budz.mintTo(msg.sender, 25000); // Uniswap Pool Allocation
    }




    /**
    * @dev Mints a token to an address with a tokenURI.
    * @param _to address of the future owner of the token
    */
    function mintTo(address _to, string memory prefix, string memory postfix, uint dna, uint generation, uint growRate) private {
        uint newTokenId = _getNextTokenId();
        _mint(_to, newTokenId);
        _incrementTokenId();
        strainData[newTokenId] = Strain(newTokenId, prefix, postfix, dna, generation, growRate, now, 1000);
        emit Minted(newTokenId);
    }


    function breed(uint _first, uint _second) public {
        require(ownerOf(_first) == msg.sender && ownerOf(_second) == msg.sender);
        Strain storage strain1 = strainData[_first];
        Strain storage strain2 = strainData[_second];

        uint cost = max(strain1.breedingCost, strain2.breedingCost);
        require(budz.balanceOf(msg.sender) >= cost && budz.allowance(msg.sender, address(this)) >= cost);
        budz.burnFrom(msg.sender, cost);


        uint newDNA = mixDNA(strain1.dna, strain2.dna);
        uint generation = max(strain1.generation, strain2.generation) + 1;

        bool mix = block.number % 2 == 0;

        strain1.breedingCost = clamp(strain1.breedingCost * 2, 1000, 1024000);
        strain2.breedingCost = clamp(strain2.breedingCost * 2, 1000, 1024000);

        mintTo(
            msg.sender,
            mix ? strain1.prefix : strain2.prefix,
            mix ? strain2.postfix : strain1.postfix,
            newDNA, generation,
            mixStat(strain1.growRate, strain2.growRate)
        );

    }

    function mixStat(uint rate1, uint rate2) private view returns (uint) {
        return clamp(((rate1 + rate2) / 2) - ((block.number % 50) - 1), 0, 255);
    }

    function genesisMint(string memory prefix, string memory postfix, uint dna) public {
        require(genesisLeft > 0);
        uint stats = clamp(genesisLeft * 3, 128, 255); // First strainz have a grow rate between 128-255
        mintTo(msg.sender, prefix, postfix, dna, 0, stats);
        genesisLeft--;
    }

    function max(uint a, uint b) private pure returns (uint) {
        if (a > b) {
            return a;
        } else return b;
    }

    function harvest(uint tokenId) public {
        require(ownerOf(tokenId) == msg.sender);
        Strain storage strain = strainData[tokenId];

        uint numberOfBudz = harvestableAmount(tokenId);
        require(numberOfBudz > 0);
        budz.mintTo(msg.sender, numberOfBudz);
        strain.lastHarvest = now;
    }

    function harvestAll() public {
        uint numberOfTokens = balanceOf(msg.sender);
        require(numberOfTokens > 0);
        uint sum = 0;
        for (uint i = 0; i < numberOfTokens; i++) {
            Strain storage strain = strainData[tokenOfOwnerByIndex(msg.sender, i)];
            sum += harvestableAmount(strain.id);
            strain.lastHarvest = now;
        }
        budz.mintTo(msg.sender, sum);
    }
    // every 6 weeks (growRate * 255) mintable tokens
    function harvestableAmount(uint tokenId) public view returns (uint) {
        Strain storage strain = strainData[tokenId];
        uint timeSinceLastHarvest = now - strain.lastHarvest;
        return (strain.growRate * 255 * timeSinceLastHarvest) / 6 weeks;
    }


}
function harvestableAmount(uint tokenId) public view returns (uint) {
    Strain storage strain = strainData[tokenId];
    uint timeSinceLastHarvest = now - strain.lastHarvest;
    return (strain.growRate * 255 * timeSinceLastHarvest) / 6 weeks;
}

// every 6 weeks (growRate * 255) mintable tokens