// Contract: DaijuKingz
// Target function: function breed(uint256 parent1, uint256 parent2) public DaijuOwner(parent1) DaijuOwner(parent2)
 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}


/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}


/*
 * Interface for the $COLORWASTE erc20 token contract
 */
interface ICW {
    function burn(address _from, uint256 _amount) external;
    function updateBalance(address _from, address _to) external;
}

/*
 * Interface for the $SOS erc20 token contract
 */
interface ISOS {
    function approve(address from, uint256 amount) external returns(bool);
    function transferFrom(address from, address to, uint256 amount) external;
    function balanceOf(address owner) external view returns(uint256);
}

/*
 * Interface for the Doodles contract
 */
interface IDoodles {
    function balanceOf(address owner) external view returns(uint256);
    function ownerOf(uint256 tokenId) external view returns(address);
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns(uint256);
}

/*
 * Interface for the KaijuKingz contract
 */
interface IKaijuKingz {
    function walletOfOwner(address owner) external view returns(uint256[] memory);
    function ownerOf(uint256 tokenId) external view returns(address);
}

/*
 * DAIJUKINGZ
 */
contract DaijuKingz is ERC721, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using Counters for Counters.Counter;

    Counters.Counter private TotalSupplyFix;

    ICW public CW;
    IDoodles public Doodles;
    IKaijuKingz public KaijuKingz;
    ISOS public SOS;
    
    string private baseURI;

    uint256 constant public reserveMax = 50; // the amount of daijus the team will reserve for community events, staff, etc.
    uint256 public maxSupply = 11110; // max genesis + max bred daijus
    uint256 public maxGenCount = 5555; // the max genesis count. if theres remaining supply after sale ends, the genesis daijukingz will be less then this number
    uint256 public maxFreeMints = 124; // the max free mints that can be claimed for kaiju and doodle holders
    uint256 public maxGivewayMint = 676; // the max free mints that are from the whitelisted users
    
    // counters
    uint256 public reserveCount = 0;
    uint256 public freeMintCount = 0;
    uint256 public giveawayMintCount = 0;
    uint256 public genCount = 0;
    uint256 public babyCount = 0;

    // settings for breeding & sales
    uint256 public price = 0.05 ether; 
    uint256 public breedPrice = 7500 ether;
    uint256 public increasePerBreed = 0 ether;
    uint256 public saleStartTimestamp;
    uint256 public freeEndTimestamp;
    address public sosWallet; 
    uint256 public sosPrice = 0.05 ether;
    bool public transfersEnabled = false;
    bool public breedingEnabled = false;

    // tracks all of the wallets that interact with the contract
    mapping(address => uint256) public balanceGenesis;
    mapping(address => uint256) public balanceBaby;
    mapping(address => uint256) public freeMintList;
    mapping(address => uint256) public giveawayMintList;
    
    /*
     * DaijuOwner
     * checks if the token IDs provided are owned by the user interacting with the contract (used for breeding)
     */
    modifier DaijuOwner(uint256 DaijuId) {
        require(ownerOf(DaijuId) == msg.sender, "Cannot interact with a DaijuKingz you do not own");
        _;
    }

    /*
     * IsSaleActive
     * checks if the mint is ready to begin
     */
    modifier IsSaleActive() {
        require(saleStartTimestamp != 0 && block.timestamp > saleStartTimestamp, "Cannot interact because sale has not started");
        _;
    }

    /*
     * IsFreeMintActive
     * checks if the free mint end timestamp isn't met
     */
    modifier IsFreeMintActive {
        require(block.timestamp < freeEndTimestamp, "Free Minting period has ended!");
        _;
    }

    /*
     * AreTransfersEnabled
     * checks if we want to allow opensea to transfer any tokens
     */
    modifier AreTransfersEnabled() {
        require(transfersEnabled, "Transfers aren't allowed yet!");
        _;
    }

    constructor() ERC721("DaijuKingz", "Daiju") {}

    /*
     * mintFreeKaijuList
     * checks the walelt for a list of kaijukingz they hold, used for checking if they
     * can claim a free daijuking
     */
    function mintFreeKaijuList(address _address) external view returns(uint256[] memory) {
        return KaijuKingz.walletOfOwner(_address);
    }

    /*
     * mintFreeDoodleList
     * gets the amount of doodles a wallet holds, used for checking if they are elligble
     * for claiming a free daijuking
     */
    function mintFreeDoodleList(address _address) public view returns(uint256) {
        uint256 count = Doodles.balanceOf(_address);

        return count;
    }

    /*
     * checkIfFreeMintClaimed
     * checks if the address passed claimed a free mint already
     */
    function checkIfFreeMintClaimed(address _address) public view returns(uint256) {
        return freeMintList[_address];
    }

    /*
     * checkIfGiveawayMintClaimed
     * checks if they claimed their giveaway mint
     */
    function checkIfGiveawayMintClaimed(address _address) public view returns(uint256) {
        return giveawayMintList[_address];
    }

    /*
     * addGiveawayWinners
     * adds the wallets to the giveaway list so they can call mintFreeGiveaway()
     */
    function addGiveawayWinners(address[] calldata giveawayAddresses) external onlyOwner {
        for (uint256 i; i < giveawayAddresses.length; i++) {
            giveawayMintList[giveawayAddresses[i]] = 1;
        }
    }

    /*
     * mintFreeDoodle
     * mints a free daijuking using a Doodles token - can only be claimed once per wallet
     */
    function mintFreeDoodle() public payable IsSaleActive IsFreeMintActive {
        uint256 supply = TotalSupplyFix.current();

        require(supply <= maxGenCount, "All Genesis Daijukingz were claimed!");
        require(mintFreeDoodleList(msg.sender) >= 1, "You don't own any Doodles!");
        require((supply + 1) <= maxFreeMints, "All the free mints have been claimed!");
        require(freeMintList[msg.sender] < 1, "You already claimed your free daijuking!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        _safeMint(msg.sender, supply);
        TotalSupplyFix.increment();

        freeMintList[msg.sender] = 1;
        balanceGenesis[msg.sender]++;
        genCount++;
        freeMintCount += 1;
    }

    /*
     * mintFreeKaiju
     * mints a free daijuking using a KaijuKingz token - can only be claimed once per wallet
     */
    function mintFreeKaiju() public payable IsSaleActive IsFreeMintActive {
        uint256 supply = TotalSupplyFix.current();
        uint256[] memory list = KaijuKingz.walletOfOwner(msg.sender);

        require(supply <= maxGenCount, "All Genesis Daijukingz were claimed!");
        require((supply + 1) <= maxFreeMints, "All the free mints have been claimed!");
        require(freeMintList[msg.sender] < 1, "You already claimed your free daijuking!");
        require(list.length >= 1, "You don't own any KaijuKingz!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        _safeMint(msg.sender, supply);
        TotalSupplyFix.increment();

        freeMintList[msg.sender] = 1;
        balanceGenesis[msg.sender]++;
        genCount++;
        freeMintCount += 1;
    }

    /*
     * mintFreeGiveaway
     * this is for the giveaway winners - allows you to mint a free daijuking
     */
    function mintFreeGiveaway() public payable IsSaleActive IsFreeMintActive {
        uint256 supply = TotalSupplyFix.current();

        require(supply <= maxGenCount, "All Genesis Daijukingz were claimed!");
        require((supply + 1) <= maxGivewayMint, "All giveaway mints were claimed!");
        require(giveawayMintList[msg.sender] >= 1, "You don't have any free mints left!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        _safeMint(msg.sender, supply);
        TotalSupplyFix.increment();

        giveawayMintList[msg.sender] = 0;
        balanceGenesis[msg.sender]++;
        genCount++;
        giveawayMintCount += 1;
    }

    /*
     * mint
     * mints the daijuking using ETH as the payment token
     */
    function mint(uint256 numberOfMints) public payable IsSaleActive {
        uint256 supply = TotalSupplyFix.current();
        require(numberOfMints > 0 && numberOfMints < 11, "Invalid purchase amount");

        if (block.timestamp <= freeEndTimestamp) {
            require(supply.add(numberOfMints) <= (maxGenCount - (maxFreeMints + maxGivewayMint)), "Purchase would exceed max supply");
        } else {
            require(supply.add(numberOfMints) <= maxGenCount, "Purchase would exceed max supply");
        }
        
        require(price.mul(numberOfMints) == msg.value, "Ether value sent is not correct");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        for (uint256 i; i < numberOfMints; i++) {
            _safeMint(msg.sender, supply + i);
            balanceGenesis[msg.sender]++;
            genCount++;
            TotalSupplyFix.increment();
        }
    }

    /*
     * mintWithSOS
     * allows the user to mint a daijuking using the $SOS token
     * note: user must approve it before this can be called
     */
    function mintWithSOS(uint256 numberOfMints) public payable IsSaleActive {
         uint256 supply = TotalSupplyFix.current();
        
        require(numberOfMints > 0 && numberOfMints < 11, "Invalid purchase amount");
        require(supply.add(numberOfMints) <= maxGenCount, "Purchase would exceed max supply");
        require(sosPrice.mul(numberOfMints) < SOS.balanceOf(msg.sender), "Not enough SOS to mint!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        SOS.transferFrom(msg.sender, sosWallet, sosPrice.mul(numberOfMints));

        for (uint256 i; i < numberOfMints; i++) {
            _safeMint(msg.sender, supply + i);
            balanceGenesis[msg.sender]++;
            genCount++;
            TotalSupplyFix.increment();
        }
    }