// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract Ogihci is ERC721URIStorage, Ownable, ERC721Enumerable {
    using Counters for Counters.Counter;
    using Strings for uint256;

    //Counters.Counter private _tokenIds;

    uint256 private _tokenIds;

    bool private _isPaused;
    
    uint256 private _maxSupply;
    uint256 private _maxFreeSupply;
    uint256 private _tokensAvailable;
    uint256 private _freeTokensRemaining;
    uint256 private _tokensMinted;
    uint256 private _maxFreeNFTsPerWallet;
    uint256 private _maxNftsPerWallet;

    //Metadata
    string private _customUriPrefix;
    string private _customUriExtension;

    //Events
    event NewMint(uint256 nftId, address nftOwner, uint256 balance, uint256 _tokensMinted);
    event ContractState(bool _isPaused);

    //Pricing
    uint256 private _mintPrice;

    //Funding
    address private _primaryColdStorage;

    //Limits
    mapping (address => uint256) private _addressFreeMintCounts;
    mapping (address => uint256) private _addressTotalMintCounts;

    constructor(address payable coldStoragePrimary) ERC721("Ogihci", "OGC") 
    {
        _tokenIds = 0;
        _maxSupply = 2000;
        _maxFreeSupply = 400;
        _freeTokensRemaining = _maxFreeSupply;
        _tokensAvailable = _maxSupply;
        _tokensMinted = 0;
        _maxFreeNFTsPerWallet = 1;
        _maxNftsPerWallet = 100;
        _isPaused = false;

        _mintPrice = 2500000000000000; //0.005 eth
        _primaryColdStorage = coldStoragePrimary;
    }

    function mint(uint256 quantity)
    public
    payable
    {
        require(!_isPaused, "The contract is currently paused.");
        require(_tokensAvailable >= quantity, "The requested quantity is greater than the number of tokens available.");
        require(quantity > 0, "You have to mint at least one NFT per transaction.");

        uint256 newOwnerMintCount = _addressTotalMintCounts[msg.sender] + quantity;
        require(newOwnerMintCount <= _maxNftsPerWallet, "The requested quantity is over the Max Mints Per Wallet limit.");
        
        uint256 newOwnerFreeMintCount = _addressFreeMintCounts[msg.sender] + _howManyTokensWillBeFree(quantity);
        require(newOwnerFreeMintCount <= _maxFreeNFTsPerWallet, "The requested quantity is over the Max Free Mints Per Wallet limit.");
        
        require(msg.value >= getPriceForQuantity(quantity), "Not enough ETH sent.");

        bool makePayment = pay(msg.value);
        if(makePayment) {
            if(newOwnerFreeMintCount > 0) {
                //Only do this if the transaction included a free mint - to save gas
                _addressFreeMintCounts[msg.sender] = newOwnerFreeMintCount;
            }
            
            _addressTotalMintCounts[msg.sender] = newOwnerMintCount;

            for (uint256 i = 0; i < quantity; i++) {
                _masterMint(msg.sender);
            }
        } 
    }

    function _masterMint(address to)
    private
    {
        require(!_isPaused,"Mint is currently paused.");
        require(_tokensAvailable > 0, "All tokens have been minted already");

        //_tokenIds.increment();
        _tokenIds++;
        //uint256 newItemId = _tokenIds.current();
        _mint(to,_tokenIds);
        _tokensAvailable = _tokensAvailable - 1;
        _tokensMinted++;
        if(_freeTokensRemaining > 0)
        {
            _freeTokensRemaining--;
        }
        uint256 balance = balanceOf(to);
        emit NewMint(_tokenIds, to, balance, _tokensMinted);
    }

    function adminMint(uint256 quantity, address toAddress)
    public
    onlyOwner 
    {
        require(!_isPaused, "The contract is currently paused.");
        require(_tokensAvailable >= quantity, "The requested quantity is greater than the number of tokens available.");
        require(quantity > 0, "You have to mint at least one NFT per transaction.");

        for (uint256 i = 0; i < quantity; i++) {
            _masterMint(toAddress);
        }
    }

    function setMintPrice(uint256 newPrice)
    public
    onlyOwner 
    {
        _mintPrice = newPrice;
    }

    function setMaxSupply(uint256 newMaxSupply)
    public
    onlyOwner 
    {
        require(newMaxSupply >= _tokensMinted, "Max supply must be greater or equal to current number of minted tokens.");
        _maxSupply = newMaxSupply;
        _tokensAvailable = _maxSupply - _tokensMinted;
    }

    function setFreeSupply(uint256 newFreeSupply)
    public
    onlyOwner 
    {
        require(newFreeSupply <= _maxSupply, "Max free supply must be less or equal to max collection supply");
        require(newFreeSupply >= _tokensMinted, "Max free supply must be greater or equal to number of currently minted tokens.");

        _maxFreeSupply = newFreeSupply;
        _freeTokensRemaining = _maxFreeSupply - _tokensMinted; 
    }

    function getFreeTokensRemaining()
    public
    view
    returns(uint256)
    {
        return _freeTokensRemaining;
    }

    function setFreeLimitPerWallet(uint256 newLimit)
    public
    onlyOwner 
    {
        require(newLimit < _maxSupply, "Can't be greater than the collection max supply");
        _maxFreeNFTsPerWallet = newLimit;
    }

    function setPaymentWallet(address newAddress)
    public
    onlyOwner
    {
        _primaryColdStorage = newAddress;
    }

    function getMaxFreeSupply()
    public
    view
    returns (uint256)
    {
        return _maxFreeSupply;
    }

    function getMaxFreeLimit()
    public
    view
    returns(uint256)
    {
        return _maxFreeNFTsPerWallet;
    }

    function getMaxFreeTokensForWallet(address minter)
    public
    view
    returns(uint256)
    {
        uint256 freeTokensMinted = _addressFreeMintCounts[minter];
        uint256 freeTokensRemaining = _maxFreeNFTsPerWallet - freeTokensMinted;
        return freeTokensRemaining;
    }

    function getTokensMinted()
    public
    view
    returns(uint256)
    {
        return _tokensMinted;
    }

    function getPriceForQuantity(uint256 quantity)
    public
    view
    returns(uint256)
    {
        require(quantity > 0, "Must specify quantity.");
        if(_maxFreeSupply <= _tokensMinted)
        {
            return (quantity * _mintPrice);
        }

        if(quantity <= _freeTokensRemaining ) {
            //Asking for less tokens than free are remaining
            return 0;
        } else {
            //Fewer free tokens remaining than desired quantity
            if(_freeTokensRemaining > 0) {
                uint256 paidTokens = quantity - _freeTokensRemaining;
                return (paidTokens * _mintPrice);
            } else {
                //No free tokens remaining
                return (quantity * _mintPrice);
            }
        }
    }

    function _howManyTokensWillBeFree (uint256 quantity)
    view
    private
    returns (uint256)
    {
        if(_freeTokensRemaining == 0) {
            //no free tokens remaining
            return 0;
        } else {
            //some free tokens remaining

            if(_freeTokensRemaining >= quantity)
            {
                //All requested tokens will be free
                return quantity;
            } else {
                //Only the remaining free supply will be free
                return _freeTokensRemaining;
            }
        }
    }

    function pauseContract()
    public
    onlyOwner 
    {
        _isPaused = !_isPaused;
        emit ContractState(_isPaused);
    }

    function isPaused()
    public
    view
    returns(bool)
    {
        return _isPaused;
    }

    function getMaxSupply()
    public
    view
    returns(uint256)
    {
        return _maxSupply;
    }

    function getTokensAvailable()
    public
    view
    returns(uint256)
    {
        return _tokensAvailable;
    }

    function _setUriPrefix(string memory newBaseUri)
    public
    onlyOwner
    {
        _customUriPrefix = newBaseUri;
    }

    function _setUriExtension(string memory newUriExtension)
    public
    onlyOwner
    {
        _customUriExtension = newUriExtension;
    }

    function _baseURI() 
    internal 
    view 
    virtual 
    override (ERC721) 
    returns (string memory) 
    {
        return _customUriPrefix;
    }

    function tokenURI(uint256 tokenId) public view virtual override (ERC721,ERC721URIStorage) returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), _customUriExtension)) : "";
    }

    function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId,
    uint256 batchSize
    ) internal virtual override (ERC721, ERC721Enumerable)  {
        require(!_isPaused, "Contract is paused.");
        super._beforeTokenTransfer(from, to, tokenId, batchSize);
        

    }

    function _burn(uint256 tokenId) 
	internal 
	virtual 
	override (ERC721, ERC721URIStorage) 
    {
        super._burn(tokenId);

    }

    function supportsInterface(bytes4 interfaceId) 
    public 
    view 
    virtual 
    override(ERC721URIStorage, ERC721Enumerable) returns (bool) 
    {
        return super.supportsInterface(interfaceId);
    }

    function pay(uint256 amount)
    public 
    payable
    returns (bool) 
    {
        //Payment splitter for two addresses
        (bool csOne, ) = _primaryColdStorage.call{value: amount}("");

        if(csOne) {
            return true;
        } else {
            return false;
        }
    }
}
[{"inputs":[{"internalType":"address payable","name":"coldStoragePrimary","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"_isPaused","type":"bool"}],"name":"ContractState","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"address","name":"nftOwner","type":"address"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_tokensMinted","type":"uint256"}],"name":"NewMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"string","name":"newUriExtension","type":"string"}],"name":"_setUriExtension","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newBaseUri","type":"string"}],"name":"_setUriPrefix","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"address","name":"toAddress","type":"address"}],"name":"adminMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFreeTokensRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxFreeLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxFreeSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"minter","type":"address"}],"name":"getMaxFreeTokensForWallet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"getPriceForQuantity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTokensAvailable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTokensMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"pay","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"setFreeLimitPerWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFreeSupply","type":"uint256"}],"name":"setFreeSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMaxSupply","type":"uint256"}],"name":"setMaxSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newPrice","type":"uint256"}],"name":"setMintPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAddress","type":"address"}],"name":"setPaymentWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]