contract WebbLand is ERC721, Ownable, PaymentSplitter {
    using Counters for Counters.Counter;
    
    Counters.Counter private _tokenIdCounterSmallApartments;
    Counters.Counter private _tokenIdCounterMediumApartments;
    Counters.Counter private _tokenIdCounterLargeApartments;
    Counters.Counter private _tokenIdCounterPenthouses;

    bool public onlyWhitelisted = true;
    bool public secondaryWhitelisted = false;
    bool public openMint = false;
    
    bool private initialized = false;

    mapping (address => uint8) whitelist;
    mapping (address => uint8) lateWhitelist;
    mapping (address => bool) mintedPenthouse;
    
    uint256 public tokenPriceSmallApartments = .069 ether;
    uint256 public tokenPriceMediumApartments = .12 ether;
    uint256 public tokenPriceLargeApartments = .24 ether;
    uint256 public tokenPricePenthouses = 1 ether;
    
    uint256 public constant maxTokens = 9069;
    uint256 public devTokenCount = 9069;

    string private _contractURI;
    
    uint256 public maxMintsPerTx = 5;
    
    constructor() public ERC721("WebbLand", "WEBBLAND") {}

    //Set Base URI
    function setBaseURI(string memory _baseURI) external onlyOwner {
        _setBaseURI(_baseURI);
    }

    function flipWhitelistedState() public onlyOwner {
        onlyWhitelisted = !onlyWhitelisted;
    }

    function flipSecondaryWhitelistedState() public onlyOwner {
        secondaryWhitelisted = !secondaryWhitelisted;
    }
    
    function flipMintState() public onlyOwner {
        openMint = !openMint;
    }

    function initializePaymentSplitter (address[] memory payees, uint256[] memory shares_) external onlyOwner {
        require (!initialized, "Payment Split Already Initialized!");
        require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
        require(payees.length > 0, "PaymentSplitter: no payees");

        for (uint256 i = 0; i < payees.length; i++) {
            _addPayee(payees[i], shares_[i]);
        }
        initialized=true;
    }
    
    function mintSpecial(uint256 quantity) external onlyOwner {
        for (uint256 i = 0; i < quantity; i++) {
            _safeMint(msg.sender, devTokenCount++);
        }
    }
    
    function mintSmallApartments(uint256 quantity) external payable {
        require(totalSupplySmallApartments() + quantity <= 5000, "minting this many would exceed supply");
        require(quantity <= maxMintsPerTx, "trying to mint too many at a time");
        require(msg.value >= tokenPriceSmallApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(!onlyWhitelisted, "not open to the public yet");
        require(msg.sender == tx.origin, "no contracts please");
        for (uint256 i = 0; i < quantity; i++) {
            safeMintSmallApartments(msg.sender);
        }
    }
    function mintSmallApartmentsWhitelist(uint8 quantity) external payable {
        require(totalSupplySmallApartments() + quantity <= 5000, "minting this many would exceed supply");
        require(msg.value >= tokenPriceSmallApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(onlyWhitelisted, "whitelist mint has closed");
        if (secondaryWhitelisted) {
            require(quantity <= lateWhitelist[msg.sender] || quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        else {
            require(quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        whitelist[msg.sender] -= quantity;
        for (uint256 i = 0; i < quantity; i++) {
            safeMintSmallApartments(msg.sender);
        }
    }
    function mintMediumApartments(uint256 quantity) external payable {
        require(totalSupplyMediumApartments() + quantity <= 3000, "minting this many would exceed supply");
        require(quantity <= maxMintsPerTx, "trying to mint too many at a time");
        require(msg.value >= tokenPriceMediumApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(!onlyWhitelisted, "not open to the public yet");
        require(msg.sender == tx.origin, "no contracts please");
        for (uint256 i = 0; i < quantity; i++) {
            safeMintMediumApartments(msg.sender);
        }
    }
    function mintMediumApartmentsWhitelist(uint8 quantity) external payable {
        require(totalSupplyMediumApartments() + quantity <= 3000, "minting this many would exceed supply");
        require(msg.value >= tokenPriceMediumApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(onlyWhitelisted, "whitelist mint has closed");
        if (secondaryWhitelisted) {
            require(quantity <= lateWhitelist[msg.sender] || quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        else {
            require(quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        whitelist[msg.sender] -= quantity;
        for (uint256 i = 0; i < quantity; i++) {
            safeMintMediumApartments(msg.sender);
        }
    }
    function mintLargeApartments(uint256 quantity) external payable {
        require(totalSupplyLargeApartments() + quantity <= 1000, "minting this many would exceed supply");
        require(quantity <= maxMintsPerTx, "trying to mint too many at a time");
        require(msg.value >= tokenPriceLargeApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(!onlyWhitelisted, "not open to the public yet");
        require(msg.sender == tx.origin, "no contracts please");
        for (uint256 i = 0; i < quantity; i++) {
            safeMintLargeApartments(msg.sender);
        }
    }
    function mintLargeApartmentsWhitelist(uint8 quantity) external payable {
        require(totalSupplyLargeApartments() + quantity <= 1000, "minting this many would exceed supply");
        require(msg.value >= tokenPriceLargeApartments * quantity, "not enough ether sent");
        require(openMint, "mint not open");
        require(onlyWhitelisted, "whitelist mint has closed");
        if (secondaryWhitelisted) {
            require(quantity <= lateWhitelist[msg.sender] || quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        else {
            require(quantity <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        whitelist[msg.sender] -= quantity;
        for (uint256 i = 0; i < quantity; i++) {
            safeMintLargeApartments(msg.sender);
        }
    }
    function mintPenthouses() external payable {
        require(totalSupplyPenthouses() + 1 <= 69, "minting this many would exceed supply");
        require(msg.value >= tokenPricePenthouses, "not enough ether sent");
        require(openMint, "mint not open");
        require(!onlyWhitelisted, "not open to the public yet");
        require(!mintedPenthouse[msg.sender], "can only mint 1 penthouse per wallet");
        require(msg.sender == tx.origin, "no contracts please");
        safeMintPenthouses(msg.sender);
    }
    function mintPenthousesWhitelist() external payable {
        require(totalSupplyPenthouses() + 1 <= 69, "minting this would exceed supply");
        require(msg.value >= tokenPricePenthouses, "not enough ether sent");
        require(openMint, "mint not open");
        require(onlyWhitelisted, "whitelist mint has closed");
        if (secondaryWhitelisted) {
            require(1 <= lateWhitelist[msg.sender] || 1 <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        else {
            require(1 <= whitelist[msg.sender], "exceeded max available to purchase");
        }
        require(!mintedPenthouse[msg.sender], "can only mint 1 penthouse per wallet");
        require(msg.sender == tx.origin, "no contracts please");
        whitelist[msg.sender] -= 1;
        safeMintPenthouses(msg.sender);
    }

    function safeMintSmallApartments(address to) internal {
        uint256 tokenId = _tokenIdCounterSmallApartments.current();
        _tokenIdCounterSmallApartments.increment();
        _safeMint(to, tokenId);
    }
    function safeMintMediumApartments(address to) internal {
        uint256 tokenId = _tokenIdCounterMediumApartments.current() + 5000;
        _tokenIdCounterMediumApartments.increment();
        _safeMint(to, tokenId);
    }
    function safeMintLargeApartments(address to) internal {
        uint256 tokenId = _tokenIdCounterLargeApartments.current() + 8000;
        _tokenIdCounterLargeApartments.increment();
        _safeMint(to, tokenId);
    }
    function safeMintPenthouses(address to) internal {
        uint256 tokenId = _tokenIdCounterPenthouses.current() + 9000;
        _tokenIdCounterPenthouses.increment();
        mintedPenthouse[to] = true;
        _safeMint(to, tokenId);
    }

    function totalSupply() public view override returns (uint) {
        return totalSupplySmallApartments() + totalSupplyMediumApartments() + totalSupplyLargeApartments() + totalSupplyPenthouses();
    }

    function totalSupplySmallApartments() public view returns (uint) {
        return _tokenIdCounterSmallApartments.current();
    }
    function totalSupplyMediumApartments() public view returns (uint) {
        return _tokenIdCounterMediumApartments.current();
    }
    function totalSupplyLargeApartments() public view returns (uint) {
        return _tokenIdCounterLargeApartments.current();
    }
    function totalSupplyPenthouses() public view returns (uint) {
        return _tokenIdCounterPenthouses.current();
    }

    function setWhitelist(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            whitelist[addresses[i]] = numAllowedToMint;
        }
    }

    function setSecondaryWhitelist(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            lateWhitelist[addresses[i]] = numAllowedToMint;
        }
    }

    function numAvailableToMint(address addr) external view returns (uint8) {
        return whitelist[addr];
    }

    function numAvailableToMintLate(address addr) external view returns (uint8) {
        return lateWhitelist[addr];
    }
}
/**
 * @title WebbLand contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function setBaseURI(string memory _baseURI) external onlyOwner {
    _setBaseURI(_baseURI);
}

//Set Base URI