contract Project is ERC721Namable, Ownable {
    
	uint256 constant public BREED_PRICE = 200 ether;
	uint256 constant public MAX_SUPPLY = 10000;
	uint256 constant public MAX_GENESIS_SUPPLY = 3333;
	uint256 constant private SEQ_GENESIS_SUPPLY = 3303;
	uint256 constant private DEV_SUPPLY_LIMIT = 30;
    uint256 public cost = 0.08 ether;
    uint256 public maxMintPerTxn = 10; // maximum number of mint per transaction
    uint256 public nftPerAddressLimitPresale = 3; // maximum number of mint per wallet for presale
    uint256 private sequenceSupply; // from 0
    uint256 private countOGFreeForOwners; // 30 Genesis tokens mintable by contract owner.
    uint256 private countOGFreeLastForOwners; // Last 30 Genesis tokens mintable by contract owner.
	uint256 private bebeCount; // baby count
    uint256[] private _occupiedList;
    uint256[] private _airdropList;

	YieldToken public yieldToken;
    // manual toggle for pause, presale and public sale //
    bool public paused = false;
    bool public presaleOpen = false;
    bool public publicSaleOpen = false;
	mapping(address => uint256) public balanceOG;
	mapping(address => uint256) public balanceOW;
    mapping(address => bool) public whitelistedAddresses; // all address of whitelisted OGs
    mapping(address => uint256) private presaleAddressMintedAmount; // number of NFT minted for each wallet during presale

	// Events
	event OWBorn (uint256 tokenId, uint256 matron, uint256 sire);

	constructor(string memory _name, string memory _symbol, string memory baseUri_) ERC721Namable(_name, _symbol) {
		_setBaseURI(baseUri_); // e.g. https://kongz.herokuapp.com/api/metadata/
	}

	function setBaseURI(string memory newURI) external onlyOwner {
		_setBaseURI(newURI);
	}

	function setYieldToken(address _yield) external onlyOwner {
		yieldToken = YieldToken(_yield);
	}

	function changeNamePrice(uint256 _price) external onlyOwner {
		nameChangePrice = _price;
	}

    function setPause(bool _state) external onlyOwner {
        paused = _state;
    }
    
    function whitelistUsers(address[] calldata _users) external onlyOwner {
        for (uint256 i = 0; i < _users.length; i++) {
          whitelistedAddresses[_users[i]] = true;
        }
    }
    
    function setPresaleOpen(bool _presaleOpen) external onlyOwner {
        presaleOpen = _presaleOpen;
    }
    
    function setPublicSaleOpen(bool _publicSaleOpen) external onlyOwner {
        publicSaleOpen = _publicSaleOpen;
    }
    
    // owner mint for free for airdrop
    function devMint(uint256 _mintAmount) external onlyOwner onlyStart {
        // require(_mintAmount <= maxMintPerTxn, "maxPerTx!");
        require(sequenceSupply + _mintAmount <= SEQ_GENESIS_SUPPLY, "exceed totalSupply");
        require(countOGFreeForOwners + _mintAmount <= DEV_SUPPLY_LIMIT, "exceed countOGFreeMax");
		yieldToken.updateRewardOnMint(msg.sender);

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, sequenceSupply + i);
            _airdropList.push(sequenceSupply + i);
        }
        sequenceSupply += _mintAmount;
        countOGFreeForOwners += _mintAmount;
        balanceOG[msg.sender] += _mintAmount;
    }
    
    // owner mint for free last 30
    function devLastMint(uint256 _mintAmount) external onlyOwner onlyStart {
        // require(_mintAmount <= maxMintPerTxn, "maxPerTx!");
        require(countOGFreeLastForOwners + _mintAmount <= DEV_SUPPLY_LIMIT, "exceed countOGFreeLastMax");
		yieldToken.updateRewardOnMint(msg.sender);
        
        uint256 id = SEQ_GENESIS_SUPPLY + countOGFreeLastForOwners;
        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, id + i);
        }
        countOGFreeLastForOwners += _mintAmount;
        balanceOG[msg.sender] += _mintAmount;
    }
    
    function giveaways(address[] memory giveawayList) external onlyOwner onlyStart {
        require(balanceOG[msg.sender] >= giveawayList.length, "balance not enough");
        require(_airdropList.length >= giveawayList.length, "list not enough");
        uint256[] memory ownerAirWallet = new uint256[](_airdropList.length);
        uint256 k;
        for (uint256 j = 0; j <_airdropList.length; j++) {
            if (ownerOf(_airdropList[j]) == msg.sender) {
                ownerAirWallet[k] = _airdropList[j];
                k++;
            }
        }
        require(k >= giveawayList.length, "ownerair not enough");

    
        for (uint256 i = 0; i < giveawayList.length; i++) {
          safeTransferFrom(msg.sender, giveawayList[i], ownerAirWallet[i], "0x00");
        }
    }

	function breed(uint256 _sire, uint256 _matron, uint256 kidId) external {
		require(ownerOf(_sire) == msg.sender && ownerOf(_matron) == msg.sender);
		require(_sire <= MAX_GENESIS_SUPPLY && _matron <= MAX_GENESIS_SUPPLY);
		require(MAX_GENESIS_SUPPLY < kidId && kidId <= MAX_SUPPLY);
		require(bebeCount <= 6667);
		yieldToken.updateRewardOnMint(msg.sender);
		yieldToken.burn(msg.sender, BREED_PRICE);

        _occupiedList.push(kidId);
		_mint(msg.sender, kidId);
        balanceOW[msg.sender]++;
		bebeCount++;
		emit OWBorn(kidId, _matron, _sire);
	}

    function occupiedList() public view returns (uint256[] memory) {
      return _occupiedList;
    }

    // public sale
    function publicMint(uint256 _mintAmount) external payable onlyStart {
        require(publicSaleOpen, "pubsale not started");
        require(_mintAmount <= maxMintPerTxn, "maxPerTx!");
        require(sequenceSupply + _mintAmount + ( DEV_SUPPLY_LIMIT - countOGFreeForOwners) <= SEQ_GENESIS_SUPPLY, "exceed totalSupply");
        require(msg.value >= cost * _mintAmount, "not enough ether");
        yieldToken.updateRewardOnMint(msg.sender);
    
        for (uint256 i = 1; i <= _mintAmount; i++) {
          _safeMint(msg.sender, sequenceSupply + i);
        }
        sequenceSupply += _mintAmount;
    }
    
    // presale mint
    function presaleMint(uint256 _mintAmount) external payable onlyStart {
        require(presaleOpen, "presale not started");
        require(_mintAmount <= maxMintPerTxn, "maxPerTx!");
        require(whitelistedAddresses[msg.sender], "not in the whitelist");
        require(presaleAddressMintedAmount[msg.sender] + _mintAmount <= nftPerAddressLimitPresale, "a max of three during presale");
        require(msg.value >= cost * _mintAmount, "not enough ether");
        yieldToken.updateRewardOnMint(msg.sender);

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, sequenceSupply + i);
        }
        presaleAddressMintedAmount[msg.sender] += _mintAmount;
        balanceOG[msg.sender] += _mintAmount;
        sequenceSupply += _mintAmount;
    }

	function getReward() external {
		yieldToken.updateReward(msg.sender, address(0));
		yieldToken.getReward(msg.sender);
	}

	function burnGenesis(uint256 id) external {
        require(ownerOf(id) == msg.sender);
		require(1 <= id && id <= MAX_GENESIS_SUPPLY, "not Genesis");
		yieldToken.updateReward(msg.sender, address(0));
        
        _burn(id);
        balanceOG[msg.sender]--;

		yieldToken.burnReward(msg.sender);
	}
    
	function walletOfOwner(address _owner) external view returns (uint256[] memory) {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory tokenIds = new uint256[](ownerTokenCount);
        for (uint256 i; i < ownerTokenCount; i++) {
          tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokenIds;
    }
    
	function changeName(uint256 tokenId, string memory newName) public override {
		yieldToken.burn(msg.sender, nameChangePrice);
		super.changeName(tokenId, newName);
	}

	function transferFrom(address from, address to, uint256 tokenId) public override {
		yieldToken.updateReward(from, to);
		if (tokenId <= MAX_GENESIS_SUPPLY) {
			balanceOG[from]--;
			balanceOG[to]++;
		} else {
			balanceOW[from]--;
			balanceOW[to]++;
		}
		ERC721.transferFrom(from, to, tokenId);
	}

	function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override {
		yieldToken.updateReward(from, to);
		if (tokenId <= MAX_GENESIS_SUPPLY) {
			balanceOG[from]--;
			balanceOG[to]++;
		} else {
			balanceOW[from]--;
			balanceOW[to]++;
		}
		ERC721.safeTransferFrom(from, to, tokenId, _data);
	}
    
    function withdraw() public onlyOwner {
        uint256 amount = address(this).balance;
        require(amount > 0);
        _widthdraw(owner(), amount);
    }

    function _widthdraw(address _address, uint256 _amount) private {
        (bool success, ) = _address.call{value: _amount}("");
        require(success);
    }
    
    modifier onlyStart() {
        require(!paused, "paused");
        _;
    }
}
function publicMint(uint256 _mintAmount) external payable onlyStart {
    require(publicSaleOpen, "pubsale not started");
    require(_mintAmount <= maxMintPerTxn, "maxPerTx!");
    require(sequenceSupply + _mintAmount + ( DEV_SUPPLY_LIMIT - countOGFreeForOwners) <= SEQ_GENESIS_SUPPLY, "exceed totalSupply");
    require(msg.value >= cost * _mintAmount, "not enough ether");
    yieldToken.updateRewardOnMint(msg.sender);
    
    for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(msg.sender, sequenceSupply + i);
    }
    sequenceSupply += _mintAmount;
}

// public sale