contract ReconRams is ERC721Enum, Ownable, PaymentSplitter {
    using Strings for uint256;

    //provenance hash using keccak256
    string public constant RR_PROVENANCE_HASH = "f427a5d18f88f321d7675ab5d46b267a87a17d004ce11743b578004f3eb079cc";

    uint256 public constant MAX_SUPPLY = 5990;
    uint256 public constant PUBLIC_MAX_SUPPLY = 5891;
    uint256 public constant MAX_PRESALE_MINT = 2;
    uint256 public constant MAX_PUBLIC_PER_TX = 7;
    uint256 public constant MAX_PUBLIC_PER_WALLET = 14;
    uint256 public constant AUCTION_LEGENDARIES = 6;
    uint256 public constant COST = 0.077 ether;
    bytes32 private merkleRoot;
    bool public presaleOnlyActive = true;
    bool public paused = true;
    bool public revealed = false;
    string baseURI;
    string notRevealedURI;
    
    mapping(address => uint256) public whitelistAddressMintedBalance;
    mapping(address => uint256) public publicAddressMintedBalance;

    address[] private _splitterAddressList = [
        0xe0e14D42d26243C5BDE44c9a37C884d0c7f26ea8, //aug
        0x62CAb33C1eA73E3D6Df09a60a7923Ff5258e7f76, //proj
        0x948CD796Db88b64a55f7070cBF3db77017314873, //f1-i
        0x296e26622932564Fb834feBDF0606ef11c27a84f, //f2-t
        0x320aB9fC119cC7Bb9c0240F97100f571ca9E418d, //f3-a
        0x86566821ac036F1bD686C60ce788ACC782225058, //f4-m
        0x8B1507236662c79Fb799DD6275Cc947ca45F86e4, //ar1-w
        0xf3C20E1faf8Ee55bf4b44C7c865E208f31fdD530  //ar2-d
    ];

    uint256[] private _shareList = [20, 12, 15, 15, 15, 15, 4, 4];

    constructor(string memory _initBaseURI, string memory _unRevealedURI, bytes32 _merkleRoot) ERC721P("ReconRams", "RR") PaymentSplitter(_splitterAddressList, _shareList) {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_unRevealedURI);
        merkleRoot = _merkleRoot;
    }

    modifier mintCheck(uint256 _mintAmount) {
        uint256 supply = totalSupply();
        require(_mintAmount > 0, "Mint amt must be greater than 0");
        require(supply + _mintAmount <= PUBLIC_MAX_SUPPLY, "Mint amt exceeds max supply");
        require(msg.value >= COST * _mintAmount, "Insufficient funds");
        _;
    }

    //---------------- Internal ----------------

    function _baseURI() internal view virtual returns (string memory) {
        return baseURI;
    }

    function _leaf(address _account) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(_account));
    }

    function _verifyLeaf(bytes32 _leafNode, bytes32[] memory _proof) internal view returns (bool) {
        return MerkleProof.verify(_proof, merkleRoot, _leafNode);
    }

    function _doMint(address _receiver, uint256 _mintAmount) internal {
        uint256 supply = totalSupply();
        for (uint256 i = 0; i < _mintAmount; i++) {
            _safeMint(_receiver, supply + i);
        }
    }

    //---------------- Public/External ----------------

    function whitelistMint(uint256 _mintAmount, bytes32[] calldata proof) external payable mintCheck(_mintAmount) {
        require(presaleOnlyActive && !paused, "Presale is currently not active");
        require(_verifyLeaf(_leaf(msg.sender), proof), "Invalid proof");

        uint256 senderMintedCount = whitelistAddressMintedBalance[msg.sender];
        require(senderMintedCount + _mintAmount <= MAX_PRESALE_MINT, "Total mints after tx exceeds max mints for this address. Max 2 per WL wallet.");
        whitelistAddressMintedBalance[msg.sender] += _mintAmount;
        
        _doMint(msg.sender, _mintAmount);
    }

    function publicMint(uint256 _mintAmount) external payable mintCheck(_mintAmount) {
        require(!presaleOnlyActive && !paused, "Public sale is not active");
        require(!Address.isContract(msg.sender), "Contract to contract minting not allowed");
        require(msg.sender == tx.origin, "Sender must be origin wallet");
        require(_mintAmount <= MAX_PUBLIC_PER_TX, "Mint amt greater than max per tx");

        uint256 senderMintedCount = publicAddressMintedBalance[msg.sender];
        require(senderMintedCount + _mintAmount <= MAX_PUBLIC_PER_WALLET, "Total mints after tx exceeds max mints for this address. Max 14 per public wallet.");
        publicAddressMintedBalance[msg.sender] += _mintAmount;
        
        _doMint(msg.sender, _mintAmount);
    }

    function walletOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 1;
        uint256 ownedTokenIndex = 0;

        while (ownedTokenIndex < ownerTokenCount && currentTokenId <= MAX_SUPPLY) {
            address currentTokenOwner = ownerOf(currentTokenId);
            if (currentTokenOwner == _owner) {
                ownedTokenIds[ownedTokenIndex] = currentTokenId;
                ownedTokenIndex++;
            }
            currentTokenId++;
        }
        return ownedTokenIds;
    }

    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId), "ERC721Metadata: TokenID does not exist.");
        if (revealed == false) {
            return notRevealedURI;
        }
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0 ? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), ".json")) : "";
    }

    //---------------- Only Owner ----------------

    function teamLegendariesMint(address _receiver) public onlyOwner {
        require(balanceOf(owner()) == 0, "Owner balance greater than 0");
        uint256 supply = totalSupply();
        for (uint256 i = 0; i < AUCTION_LEGENDARIES; i++) {
            _safeMint(_receiver, supply + i);
        }
    }

    function teamReserveMint(uint256 _mintAmount, address _receiver) public onlyOwner {
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= MAX_SUPPLY, "Mint amt exceeds max supply");
        _doMint(_receiver, _mintAmount);
    }

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedURI = _notRevealedURI;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function pause(bool _state) public onlyOwner {
        paused = _state;
    }

    function reveal() public onlyOwner {
        revealed = true;
    }

    function whitelistMintOnly(bool _state) public onlyOwner {
        presaleOnlyActive = _state;
    }

    function setMerkleRoot(bytes32 _root) public onlyOwner {
        merkleRoot = _root;
    }
}
function _baseURI() internal view virtual returns (string memory) {
    return baseURI;
}

//---------------- Internal ----------------