contract BarGangNFT is ERC721, Ownable {

    using Counters for Counters.Counter;
    using SafeMath for uint256;

    /**********  staging value for 
        initial state (value = 0)
        presale (value = 1), 
        public minting (value = 2)
        ended (value = 3)
    *******************************/
    uint _stagingValue = 0;
    string public _contractURI = "";

    Counters.Counter private _tokenIds;

    uint256 MAX_SUPPLY = 10000;

    uint GIVEAWAY_MAX_PER_USER = 1;
    uint PRIVATESALE_MAX_PER_USER = 5;
    uint PUBLICSALE_MAX_PER_USER = 10;

    uint256 _privateSalePrice = 65000000000000000; // 0.065 ETH
    uint256 _publicSalePrice = 85000000000000000; // 0.085 ETH
    
    mapping(address => bool) private _familyWhitelist;
    mapping(address => bool) private _presaleWhitelist;
    
    mapping(address => uint) private _familyCountlist;
    mapping(address => uint) private _presaleCountlist;
    mapping(address => uint) private _publicsaleCountlist;

    constructor() public ERC721("BARGANG", "BARGANG") payable {
        for (uint i = 0; i < 15; i++)
            airdrop();
    }

    function setBaseURI(string calldata baseURI) external onlyOwner {
        _setBaseURI(baseURI);
    }

    function setFamilyWhiteList(address[] memory params) public onlyOwner{
        require(params.length > 0, "Please input whiltelist array");
        for(uint i = 0; i < params.length; i++) {
            _familyWhitelist[params[i]] = true;
        }
    }

    function setPresaleWhiteList(address[] memory params) public onlyOwner{
        require(params.length > 0, "Please input whiltelist array");
        for(uint i = 0; i < params.length; i++) {
            _presaleWhitelist[params[i]] = true;
        }
    }

    function _tokenMint() internal {
        _tokenIds.increment();
        uint256 tokenId = _tokenIds.current();
        _safeMint(msg.sender, tokenId);
        _setTokenURI(tokenId, uint2str(tokenId));
    }

    function requestGiveAway() external payable {
        require(super.totalSupply() < MAX_SUPPLY, "Maximum supply reached");
        require(_familyWhitelist[msg.sender], "This address is not included in family/friend whitelist");
        require(_familyCountlist[msg.sender] < GIVEAWAY_MAX_PER_USER, "You can get a giveaway no more than one");

        _tokenMint();
        _familyCountlist[msg.sender] = _familyCountlist[msg.sender] + 1;
    }

    function requestPrivateSale(uint mintCount) external payable {
        require(super.totalSupply() + mintCount < MAX_SUPPLY, "Maximum supply reached");
        require(_stagingValue == 1, "Presale is not allowed");
        require(_presaleWhitelist[msg.sender], "This address is not included in whitelist");
        require(_presaleCountlist[msg.sender] + mintCount <= PRIVATESALE_MAX_PER_USER, "Overflow 5 tokens");
        require(msg.value >= _privateSalePrice.mul(mintCount), "Invalid funds");
        for (uint256 index = 0; index < mintCount; index++) {
            _tokenMint();
            _presaleCountlist[msg.sender] = _presaleCountlist[msg.sender] + 1;
        }
    }

    function requestPublicSale(uint mintCount) external payable {
        require(super.totalSupply() + mintCount <= MAX_SUPPLY, "Maximum supply reached.");
        require(_stagingValue == 2, "PublicSale is not allowed.");
        require(_publicsaleCountlist[msg.sender] + mintCount <= PUBLICSALE_MAX_PER_USER, "Overflow 10 tokens");
        require(msg.value >= _publicSalePrice.mul(mintCount), "Invalid funds");
        for (uint256 index = 0; index < mintCount; index++) {
            _tokenMint();
            _publicsaleCountlist[msg.sender] = _publicsaleCountlist[msg.sender] + 1;
        }
    }

    function airdrop() internal {
        // owner will mint the first 15 rare ones
        _tokenMint();
    }

    function ownerMint(uint mintCount) external onlyOwner{
        for (uint256 index = 0; index < mintCount; index++) {
            _tokenMint();
        }
    }

    function getCurrentStage() external view returns(uint256) {
        return _stagingValue;
    }

    function setStage(uint256 value) external onlyOwner{
        require(value >= 0, "Invalid staging value");
        require(value <= 3, "Invalid staging value");
        _stagingValue = value; 
    }

    // Function to withdraw all Ether from this contract.
    function withdraw() external onlyOwner{
        // send all Ether to owner
        // Owner can receive Ether since the address of owner is payable
        (bool success, ) = owner().call{value: address(this).balance}("");
        require(success, "Failed to send Ether");
    }

    function setContractURI(string calldata URI) external onlyOwner {
        _contractURI = URI;
    }

    function contractURI() public view returns (string memory) {
        return _contractURI;
    }

    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }
}
function withdraw() external onlyOwner{
    // send all Ether to owner
    // Owner can receive Ether since the address of owner is payable
    (bool success, ) = owner().call{value: address(this).balance}("");
    require(success, "Failed to send Ether");
}

// Function to withdraw all Ether from this contract.